@hasna/mementos 0.11.1 → 0.14.2
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/dashboard/dist/assets/index-DqyMbv89.js +270 -0
- package/dashboard/dist/assets/index-UBCddFo_.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/brains.d.ts +3 -0
- package/dist/cli/brains.d.ts.map +1 -0
- package/dist/cli/index.js +2093 -512
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/tool-events.d.ts +27 -0
- package/dist/db/tool-events.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +177 -5
- package/dist/lib/activation-matcher.d.ts +16 -0
- package/dist/lib/activation-matcher.d.ts.map +1 -0
- package/dist/lib/asmr/categorizer.d.ts +31 -0
- package/dist/lib/asmr/categorizer.d.ts.map +1 -0
- package/dist/lib/asmr/context-agent.d.ts +4 -0
- package/dist/lib/asmr/context-agent.d.ts.map +1 -0
- package/dist/lib/asmr/ensemble.d.ts +23 -0
- package/dist/lib/asmr/ensemble.d.ts.map +1 -0
- package/dist/lib/asmr/fact-agent.d.ts +4 -0
- package/dist/lib/asmr/fact-agent.d.ts.map +1 -0
- package/dist/lib/asmr/index.d.ts +7 -0
- package/dist/lib/asmr/index.d.ts.map +1 -0
- package/dist/lib/asmr/orchestrator.d.ts +4 -0
- package/dist/lib/asmr/orchestrator.d.ts.map +1 -0
- package/dist/lib/asmr/temporal-agent.d.ts +4 -0
- package/dist/lib/asmr/temporal-agent.d.ts.map +1 -0
- package/dist/lib/asmr/types.d.ts +27 -0
- package/dist/lib/asmr/types.d.ts.map +1 -0
- package/dist/lib/auto-inject-orchestrator.d.ts +57 -0
- package/dist/lib/auto-inject-orchestrator.d.ts.map +1 -0
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/channel-pusher.d.ts +39 -0
- package/dist/lib/channel-pusher.d.ts.map +1 -0
- package/dist/lib/connectors/files.d.ts +8 -0
- package/dist/lib/connectors/files.d.ts.map +1 -0
- package/dist/lib/connectors/github.d.ts +7 -0
- package/dist/lib/connectors/github.d.ts.map +1 -0
- package/dist/lib/connectors/index.d.ts +12 -0
- package/dist/lib/connectors/index.d.ts.map +1 -0
- package/dist/lib/connectors/notion.d.ts +7 -0
- package/dist/lib/connectors/notion.d.ts.map +1 -0
- package/dist/lib/connectors/types.d.ts +27 -0
- package/dist/lib/connectors/types.d.ts.map +1 -0
- package/dist/lib/context-extractor.d.ts +14 -0
- package/dist/lib/context-extractor.d.ts.map +1 -0
- package/dist/lib/extractors/audio.d.ts +8 -0
- package/dist/lib/extractors/audio.d.ts.map +1 -0
- package/dist/lib/extractors/index.d.ts +12 -0
- package/dist/lib/extractors/index.d.ts.map +1 -0
- package/dist/lib/extractors/ocr.d.ts +7 -0
- package/dist/lib/extractors/ocr.d.ts.map +1 -0
- package/dist/lib/extractors/pdf.d.ts +7 -0
- package/dist/lib/extractors/pdf.d.ts.map +1 -0
- package/dist/lib/extractors/types.d.ts +12 -0
- package/dist/lib/extractors/types.d.ts.map +1 -0
- package/dist/lib/gatherer.d.ts +16 -0
- package/dist/lib/gatherer.d.ts.map +1 -0
- package/dist/lib/injector.d.ts +48 -1
- package/dist/lib/injector.d.ts.map +1 -1
- package/dist/lib/matryoshka.d.ts +50 -0
- package/dist/lib/matryoshka.d.ts.map +1 -0
- package/dist/lib/model-config.d.ts +14 -0
- package/dist/lib/model-config.d.ts.map +1 -0
- package/dist/lib/procedural-extractor.d.ts +21 -0
- package/dist/lib/procedural-extractor.d.ts.map +1 -0
- package/dist/lib/profile-synthesizer.d.ts +20 -0
- package/dist/lib/profile-synthesizer.d.ts.map +1 -0
- package/dist/lib/session-processor.d.ts.map +1 -1
- package/dist/lib/session-registry.d.ts +47 -0
- package/dist/lib/session-registry.d.ts.map +1 -0
- package/dist/lib/session-start-briefing.d.ts +10 -0
- package/dist/lib/session-start-briefing.d.ts.map +1 -0
- package/dist/lib/session-watcher.d.ts +30 -0
- package/dist/lib/session-watcher.d.ts.map +1 -0
- package/dist/lib/tool-lesson-extractor.d.ts +24 -0
- package/dist/lib/tool-lesson-extractor.d.ts.map +1 -0
- package/dist/lib/tool-memory-synthesizer.d.ts +28 -0
- package/dist/lib/tool-memory-synthesizer.d.ts.map +1 -0
- package/dist/lib/topic-clusterer.d.ts +21 -0
- package/dist/lib/topic-clusterer.d.ts.map +1 -0
- package/dist/lib/when-to-use-generator.d.ts +22 -0
- package/dist/lib/when-to-use-generator.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +3 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +3825 -382
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +883 -18
- package/dist/types/index.d.ts +57 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -1
- package/dashboard/dist/assets/index-B1yiOEw3.js +0 -290
- package/dashboard/dist/assets/index-DnpbasSl.css +0 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toCommonJS = (from) => {
|
|
11
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
12
|
+
if (entry)
|
|
13
|
+
return entry;
|
|
14
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (var key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(entry, key))
|
|
18
|
+
__defProp(entry, key, {
|
|
19
|
+
get: __accessProp.bind(from, key),
|
|
20
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
__moduleCache.set(from, entry);
|
|
24
|
+
return entry;
|
|
25
|
+
};
|
|
26
|
+
var __moduleCache;
|
|
27
|
+
var __returnValue = (v) => v;
|
|
28
|
+
function __exportSetter(name, newValue) {
|
|
29
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
30
|
+
}
|
|
4
31
|
var __export = (target, all) => {
|
|
5
32
|
for (var name in all)
|
|
6
33
|
__defProp(target, name, {
|
|
7
34
|
get: all[name],
|
|
8
35
|
enumerable: true,
|
|
9
36
|
configurable: true,
|
|
10
|
-
set: (
|
|
37
|
+
set: __exportSetter.bind(all, name)
|
|
11
38
|
});
|
|
12
39
|
};
|
|
13
40
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -912,6 +939,44 @@ CREATE INDEX IF NOT EXISTS idx_memory_embeddings_model ON memory_embeddings(mode
|
|
|
912
939
|
|
|
913
940
|
PRAGMA foreign_keys = ON;
|
|
914
941
|
INSERT OR IGNORE INTO _migrations (id) VALUES (29);
|
|
942
|
+
`,
|
|
943
|
+
`
|
|
944
|
+
ALTER TABLE memories ADD COLUMN when_to_use TEXT DEFAULT NULL;
|
|
945
|
+
CREATE INDEX IF NOT EXISTS idx_memories_when_to_use ON memories(when_to_use) WHERE when_to_use IS NOT NULL;
|
|
946
|
+
ALTER TABLE memory_versions ADD COLUMN when_to_use TEXT;
|
|
947
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (30);
|
|
948
|
+
`,
|
|
949
|
+
`
|
|
950
|
+
CREATE TABLE IF NOT EXISTS tool_events (
|
|
951
|
+
id TEXT PRIMARY KEY,
|
|
952
|
+
tool_name TEXT NOT NULL,
|
|
953
|
+
action TEXT,
|
|
954
|
+
success INTEGER NOT NULL DEFAULT 1,
|
|
955
|
+
error_type TEXT CHECK(error_type IS NULL OR error_type IN ('timeout', 'permission', 'not_found', 'syntax', 'rate_limit', 'other')),
|
|
956
|
+
error_message TEXT,
|
|
957
|
+
tokens_used INTEGER,
|
|
958
|
+
latency_ms INTEGER,
|
|
959
|
+
context TEXT,
|
|
960
|
+
lesson TEXT,
|
|
961
|
+
when_to_use TEXT,
|
|
962
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
963
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
964
|
+
session_id TEXT,
|
|
965
|
+
metadata TEXT DEFAULT '{}',
|
|
966
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
967
|
+
);
|
|
968
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_tool_name ON tool_events(tool_name);
|
|
969
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_agent ON tool_events(agent_id);
|
|
970
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_project ON tool_events(project_id);
|
|
971
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_success ON tool_events(success);
|
|
972
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_created ON tool_events(created_at);
|
|
973
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (31);
|
|
974
|
+
`,
|
|
975
|
+
`
|
|
976
|
+
ALTER TABLE memories ADD COLUMN sequence_group TEXT DEFAULT NULL;
|
|
977
|
+
ALTER TABLE memories ADD COLUMN sequence_order INTEGER DEFAULT NULL;
|
|
978
|
+
CREATE INDEX IF NOT EXISTS idx_memories_sequence_group ON memories(sequence_group) WHERE sequence_group IS NOT NULL;
|
|
979
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (32);
|
|
915
980
|
`
|
|
916
981
|
];
|
|
917
982
|
});
|
|
@@ -1175,6 +1240,18 @@ var init_poisoning = __esm(() => {
|
|
|
1175
1240
|
});
|
|
1176
1241
|
|
|
1177
1242
|
// src/db/entity-memories.ts
|
|
1243
|
+
function parseEntityRow(row) {
|
|
1244
|
+
return {
|
|
1245
|
+
id: row["id"],
|
|
1246
|
+
name: row["name"],
|
|
1247
|
+
type: row["type"],
|
|
1248
|
+
description: row["description"] || null,
|
|
1249
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
1250
|
+
project_id: row["project_id"] || null,
|
|
1251
|
+
created_at: row["created_at"],
|
|
1252
|
+
updated_at: row["updated_at"]
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1178
1255
|
function parseEntityMemoryRow(row) {
|
|
1179
1256
|
return {
|
|
1180
1257
|
entity_id: row["entity_id"],
|
|
@@ -1203,6 +1280,14 @@ function getMemoriesForEntity(entityId, db) {
|
|
|
1203
1280
|
ORDER BY m.importance DESC, m.created_at DESC`).all(entityId);
|
|
1204
1281
|
return rows.map(parseMemoryRow);
|
|
1205
1282
|
}
|
|
1283
|
+
function getEntitiesForMemory(memoryId, db) {
|
|
1284
|
+
const d = db || getDatabase();
|
|
1285
|
+
const rows = d.query(`SELECT e.* FROM entities e
|
|
1286
|
+
INNER JOIN entity_memories em ON em.entity_id = e.id
|
|
1287
|
+
WHERE em.memory_id = ?
|
|
1288
|
+
ORDER BY e.name ASC`).all(memoryId);
|
|
1289
|
+
return rows.map(parseEntityRow);
|
|
1290
|
+
}
|
|
1206
1291
|
function getEntityMemoryLinks(entityId, memoryId, db) {
|
|
1207
1292
|
const d = db || getDatabase();
|
|
1208
1293
|
const conditions = [];
|
|
@@ -1266,6 +1351,9 @@ function parseMemoryRow(row) {
|
|
|
1266
1351
|
session_id: row["session_id"] || null,
|
|
1267
1352
|
machine_id: row["machine_id"] || null,
|
|
1268
1353
|
flag: row["flag"] || null,
|
|
1354
|
+
when_to_use: row["when_to_use"] || null,
|
|
1355
|
+
sequence_group: row["sequence_group"] || null,
|
|
1356
|
+
sequence_order: row["sequence_order"] ?? null,
|
|
1269
1357
|
content_type: row["content_type"] || "text",
|
|
1270
1358
|
namespace: row["namespace"] || null,
|
|
1271
1359
|
created_by_agent: row["created_by_agent"] || null,
|
|
@@ -1324,6 +1412,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
1324
1412
|
d.run(`UPDATE memories SET
|
|
1325
1413
|
value = ?, category = ?, summary = ?, tags = ?,
|
|
1326
1414
|
importance = ?, metadata = ?, expires_at = ?,
|
|
1415
|
+
when_to_use = ?,
|
|
1327
1416
|
pinned = COALESCE(pinned, 0),
|
|
1328
1417
|
version = version + 1, updated_at = ?
|
|
1329
1418
|
WHERE id = ?`, [
|
|
@@ -1334,6 +1423,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
1334
1423
|
input.importance ?? 5,
|
|
1335
1424
|
metadataJson,
|
|
1336
1425
|
expiresAt,
|
|
1426
|
+
input.when_to_use || null,
|
|
1337
1427
|
timestamp,
|
|
1338
1428
|
existing.id
|
|
1339
1429
|
]);
|
|
@@ -1358,8 +1448,8 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
1358
1448
|
return merged;
|
|
1359
1449
|
}
|
|
1360
1450
|
}
|
|
1361
|
-
d.run(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at)
|
|
1362
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 0, ?, ?, ?, ?, ?, ?, ?, 0, 1, ?, ?, ?, ?, ?, ?)`, [
|
|
1451
|
+
d.run(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, when_to_use, sequence_group, sequence_order, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at)
|
|
1452
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, ?, ?, ?, ?, ?, ?)`, [
|
|
1363
1453
|
id,
|
|
1364
1454
|
input.key,
|
|
1365
1455
|
input.value,
|
|
@@ -1375,6 +1465,9 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
1375
1465
|
input.machine_id || null,
|
|
1376
1466
|
input.namespace || null,
|
|
1377
1467
|
input.agent_id || null,
|
|
1468
|
+
input.when_to_use || null,
|
|
1469
|
+
input.sequence_group || null,
|
|
1470
|
+
input.sequence_order ?? null,
|
|
1378
1471
|
metadataJson,
|
|
1379
1472
|
expiresAt,
|
|
1380
1473
|
input.metadata?.valid_from ?? timestamp,
|
|
@@ -1585,8 +1678,8 @@ function updateMemory(id, input, db) {
|
|
|
1585
1678
|
throw new VersionConflictError(id, input.version, existing.version);
|
|
1586
1679
|
}
|
|
1587
1680
|
try {
|
|
1588
|
-
d.run(`INSERT OR IGNORE INTO memory_versions (id, memory_id, version, value, importance, scope, category, tags, summary, pinned, status, created_at)
|
|
1589
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1681
|
+
d.run(`INSERT OR IGNORE INTO memory_versions (id, memory_id, version, value, importance, scope, category, tags, summary, pinned, status, when_to_use, created_at)
|
|
1682
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1590
1683
|
uuid(),
|
|
1591
1684
|
existing.id,
|
|
1592
1685
|
existing.version,
|
|
@@ -1598,6 +1691,7 @@ function updateMemory(id, input, db) {
|
|
|
1598
1691
|
existing.summary,
|
|
1599
1692
|
existing.pinned ? 1 : 0,
|
|
1600
1693
|
existing.status,
|
|
1694
|
+
existing.when_to_use || null,
|
|
1601
1695
|
existing.updated_at
|
|
1602
1696
|
]);
|
|
1603
1697
|
} catch {}
|
|
@@ -1643,6 +1737,10 @@ function updateMemory(id, input, db) {
|
|
|
1643
1737
|
sets.push("flag = ?");
|
|
1644
1738
|
params.push(input.flag ?? null);
|
|
1645
1739
|
}
|
|
1740
|
+
if (input.when_to_use !== undefined) {
|
|
1741
|
+
sets.push("when_to_use = ?");
|
|
1742
|
+
params.push(input.when_to_use ?? null);
|
|
1743
|
+
}
|
|
1646
1744
|
if (input.tags !== undefined) {
|
|
1647
1745
|
sets.push("tags = ?");
|
|
1648
1746
|
params.push(JSON.stringify(input.tags));
|
|
@@ -1806,7 +1904,7 @@ var init_memories = __esm(() => {
|
|
|
1806
1904
|
var exports_entities = {};
|
|
1807
1905
|
__export(exports_entities, {
|
|
1808
1906
|
updateEntity: () => updateEntity,
|
|
1809
|
-
parseEntityRow: () =>
|
|
1907
|
+
parseEntityRow: () => parseEntityRow2,
|
|
1810
1908
|
mergeEntities: () => mergeEntities,
|
|
1811
1909
|
listEntities: () => listEntities,
|
|
1812
1910
|
graphTraverse: () => graphTraverse,
|
|
@@ -1816,7 +1914,7 @@ __export(exports_entities, {
|
|
|
1816
1914
|
deleteEntity: () => deleteEntity,
|
|
1817
1915
|
createEntity: () => createEntity
|
|
1818
1916
|
});
|
|
1819
|
-
function
|
|
1917
|
+
function parseEntityRow2(row) {
|
|
1820
1918
|
return {
|
|
1821
1919
|
id: row["id"],
|
|
1822
1920
|
name: row["name"],
|
|
@@ -1876,7 +1974,7 @@ function getEntity(id, db) {
|
|
|
1876
1974
|
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
1877
1975
|
if (!row)
|
|
1878
1976
|
throw new EntityNotFoundError(id);
|
|
1879
|
-
return
|
|
1977
|
+
return parseEntityRow2(row);
|
|
1880
1978
|
}
|
|
1881
1979
|
function getEntityByName(name, type, projectId, db) {
|
|
1882
1980
|
const d = db || getDatabase();
|
|
@@ -1894,7 +1992,7 @@ function getEntityByName(name, type, projectId, db) {
|
|
|
1894
1992
|
const row = d.query(sql).get(...params);
|
|
1895
1993
|
if (!row)
|
|
1896
1994
|
return null;
|
|
1897
|
-
return
|
|
1995
|
+
return parseEntityRow2(row);
|
|
1898
1996
|
}
|
|
1899
1997
|
function listEntities(filter = {}, db) {
|
|
1900
1998
|
const d = db || getDatabase();
|
|
@@ -1927,7 +2025,7 @@ function listEntities(filter = {}, db) {
|
|
|
1927
2025
|
params.push(filter.offset);
|
|
1928
2026
|
}
|
|
1929
2027
|
const rows = d.query(sql).all(...params);
|
|
1930
|
-
return rows.map(
|
|
2028
|
+
return rows.map(parseEntityRow2);
|
|
1931
2029
|
}
|
|
1932
2030
|
function updateEntity(id, input, db) {
|
|
1933
2031
|
const d = db || getDatabase();
|
|
@@ -2000,7 +2098,7 @@ function trigramSimilarity(a, b) {
|
|
|
2000
2098
|
function findDuplicateEntities(threshold = 0.8, db) {
|
|
2001
2099
|
const d = db || getDatabase();
|
|
2002
2100
|
const entities = d.query("SELECT * FROM entities ORDER BY type, project_id, name").all();
|
|
2003
|
-
const parsed = entities.map(
|
|
2101
|
+
const parsed = entities.map(parseEntityRow2);
|
|
2004
2102
|
const groups = new Map;
|
|
2005
2103
|
for (const e of parsed) {
|
|
2006
2104
|
const groupKey = `${e.type}:${e.project_id || ""}`;
|
|
@@ -2152,7 +2250,7 @@ function parseRelationRow(row) {
|
|
|
2152
2250
|
created_at: row["created_at"]
|
|
2153
2251
|
};
|
|
2154
2252
|
}
|
|
2155
|
-
function
|
|
2253
|
+
function parseEntityRow3(row) {
|
|
2156
2254
|
return {
|
|
2157
2255
|
id: row["id"],
|
|
2158
2256
|
name: row["name"],
|
|
@@ -2234,7 +2332,7 @@ function getEntityGraph(entityId, depth = 2, db) {
|
|
|
2234
2332
|
WHERE g.depth < ?
|
|
2235
2333
|
)
|
|
2236
2334
|
SELECT DISTINCT e.* FROM entities e JOIN graph g ON e.id = g.id`).all(entityId, depth);
|
|
2237
|
-
const entities = entityRows.map(
|
|
2335
|
+
const entities = entityRows.map(parseEntityRow3);
|
|
2238
2336
|
const entityIds = new Set(entities.map((e) => e.id));
|
|
2239
2337
|
if (entityIds.size === 0) {
|
|
2240
2338
|
return { entities: [], relations: [] };
|
|
@@ -2267,7 +2365,7 @@ function findPath(fromEntityId, toEntityId, maxDepth = 5, db) {
|
|
|
2267
2365
|
for (const id of ids) {
|
|
2268
2366
|
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
2269
2367
|
if (row)
|
|
2270
|
-
entities.push(
|
|
2368
|
+
entities.push(parseEntityRow3(row));
|
|
2271
2369
|
}
|
|
2272
2370
|
return entities.length > 0 ? entities : null;
|
|
2273
2371
|
}
|
|
@@ -2940,6 +3038,262 @@ var init_search = __esm(() => {
|
|
|
2940
3038
|
]);
|
|
2941
3039
|
});
|
|
2942
3040
|
|
|
3041
|
+
// src/lib/profile-synthesizer.ts
|
|
3042
|
+
var exports_profile_synthesizer = {};
|
|
3043
|
+
__export(exports_profile_synthesizer, {
|
|
3044
|
+
synthesizeProfile: () => synthesizeProfile,
|
|
3045
|
+
markProfileStale: () => markProfileStale,
|
|
3046
|
+
getProfileKey: () => getProfileKey
|
|
3047
|
+
});
|
|
3048
|
+
function getProfileKey(scope, id) {
|
|
3049
|
+
return `_profile_${scope}_${id}`;
|
|
3050
|
+
}
|
|
3051
|
+
async function synthesizeProfile(options) {
|
|
3052
|
+
const scope = options.scope || (options.project_id ? "project" : options.agent_id ? "agent" : "global");
|
|
3053
|
+
const id = options.project_id || options.agent_id || "global";
|
|
3054
|
+
const profileKey = getProfileKey(scope, id);
|
|
3055
|
+
if (!options.force_refresh) {
|
|
3056
|
+
const cached = getMemoryByKey(profileKey, "shared", undefined, options.project_id);
|
|
3057
|
+
if (cached) {
|
|
3058
|
+
const age = Date.now() - new Date(cached.updated_at).getTime();
|
|
3059
|
+
const maxAge = 24 * 60 * 60 * 1000;
|
|
3060
|
+
const isStale = cached.metadata?.stale === true;
|
|
3061
|
+
if (age < maxAge && !isStale) {
|
|
3062
|
+
return { profile: cached.value, memory_count: 0, from_cache: true };
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
const prefMemories = listMemories({
|
|
3067
|
+
category: "preference",
|
|
3068
|
+
project_id: options.project_id,
|
|
3069
|
+
status: "active",
|
|
3070
|
+
limit: 30
|
|
3071
|
+
});
|
|
3072
|
+
const factMemories = listMemories({
|
|
3073
|
+
category: "fact",
|
|
3074
|
+
project_id: options.project_id,
|
|
3075
|
+
status: "active",
|
|
3076
|
+
limit: 30
|
|
3077
|
+
});
|
|
3078
|
+
const allMemories = [...prefMemories, ...factMemories];
|
|
3079
|
+
if (allMemories.length === 0)
|
|
3080
|
+
return null;
|
|
3081
|
+
const apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
3082
|
+
if (!apiKey) {
|
|
3083
|
+
const lines = allMemories.map((m) => `- ${m.key}: ${m.value}`).join(`
|
|
3084
|
+
`);
|
|
3085
|
+
const fallbackProfile = `## Profile
|
|
3086
|
+
${lines}`;
|
|
3087
|
+
saveProfile(profileKey, fallbackProfile, allMemories.length, options);
|
|
3088
|
+
return { profile: fallbackProfile, memory_count: allMemories.length, from_cache: false };
|
|
3089
|
+
}
|
|
3090
|
+
try {
|
|
3091
|
+
const memoryList = allMemories.sort((a, b) => b.importance - a.importance).map((m) => `[${m.category}] ${m.key}: ${m.value}`).join(`
|
|
3092
|
+
`);
|
|
3093
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
3094
|
+
method: "POST",
|
|
3095
|
+
headers: {
|
|
3096
|
+
"x-api-key": apiKey,
|
|
3097
|
+
"anthropic-version": "2023-06-01",
|
|
3098
|
+
"content-type": "application/json"
|
|
3099
|
+
},
|
|
3100
|
+
body: JSON.stringify({
|
|
3101
|
+
model: "claude-haiku-4-5-20251001",
|
|
3102
|
+
max_tokens: 500,
|
|
3103
|
+
system: PROFILE_PROMPT,
|
|
3104
|
+
messages: [{ role: "user", content: `Synthesize a profile from these ${allMemories.length} memories:
|
|
3105
|
+
|
|
3106
|
+
${memoryList}` }]
|
|
3107
|
+
})
|
|
3108
|
+
});
|
|
3109
|
+
if (!response.ok)
|
|
3110
|
+
return null;
|
|
3111
|
+
const data = await response.json();
|
|
3112
|
+
const profile = data.content?.[0]?.text?.trim();
|
|
3113
|
+
if (!profile)
|
|
3114
|
+
return null;
|
|
3115
|
+
saveProfile(profileKey, profile, allMemories.length, options);
|
|
3116
|
+
return { profile, memory_count: allMemories.length, from_cache: false };
|
|
3117
|
+
} catch {
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
function saveProfile(key, value, memoryCount, options) {
|
|
3122
|
+
try {
|
|
3123
|
+
createMemory({
|
|
3124
|
+
key,
|
|
3125
|
+
value,
|
|
3126
|
+
category: "fact",
|
|
3127
|
+
scope: "shared",
|
|
3128
|
+
importance: 10,
|
|
3129
|
+
source: "auto",
|
|
3130
|
+
tags: ["profile", "synthesized"],
|
|
3131
|
+
when_to_use: "When needing to understand this agent's or project's preferences, style, and conventions",
|
|
3132
|
+
metadata: { memory_count: memoryCount, synthesized_at: new Date().toISOString(), stale: false },
|
|
3133
|
+
agent_id: options.agent_id,
|
|
3134
|
+
project_id: options.project_id
|
|
3135
|
+
});
|
|
3136
|
+
} catch {}
|
|
3137
|
+
}
|
|
3138
|
+
function markProfileStale(projectId, _agentId) {
|
|
3139
|
+
try {
|
|
3140
|
+
const { getDatabase: getDatabase2 } = (init_database(), __toCommonJS(exports_database));
|
|
3141
|
+
const db = getDatabase2();
|
|
3142
|
+
db.run(`UPDATE memories SET metadata = json_set(COALESCE(metadata, '{}'), '$.stale', json('true'))
|
|
3143
|
+
WHERE key LIKE '_profile_%' AND COALESCE(project_id, '') = ?`, [projectId || ""]);
|
|
3144
|
+
} catch {}
|
|
3145
|
+
}
|
|
3146
|
+
var PROFILE_PROMPT = `You synthesize a coherent agent/project profile from individual preference and fact memories.
|
|
3147
|
+
|
|
3148
|
+
Output a concise profile (200-300 words max) organized by:
|
|
3149
|
+
- **Stack & Tools**: Languages, frameworks, package managers, etc.
|
|
3150
|
+
- **Code Style**: Formatting, patterns, naming conventions
|
|
3151
|
+
- **Workflow**: Testing, deployment, git practices
|
|
3152
|
+
- **Communication**: Response style, verbosity, formatting preferences
|
|
3153
|
+
- **Key Facts**: Architecture decisions, constraints, team conventions
|
|
3154
|
+
|
|
3155
|
+
Only include sections that have relevant data. Be specific and actionable.
|
|
3156
|
+
Output in markdown format.`;
|
|
3157
|
+
var init_profile_synthesizer = __esm(() => {
|
|
3158
|
+
init_memories();
|
|
3159
|
+
});
|
|
3160
|
+
|
|
3161
|
+
// src/db/tool-events.ts
|
|
3162
|
+
function parseToolEventRow(row) {
|
|
3163
|
+
return {
|
|
3164
|
+
id: row["id"],
|
|
3165
|
+
tool_name: row["tool_name"],
|
|
3166
|
+
action: row["action"] || null,
|
|
3167
|
+
success: !!row["success"],
|
|
3168
|
+
error_type: row["error_type"] || null,
|
|
3169
|
+
error_message: row["error_message"] || null,
|
|
3170
|
+
tokens_used: row["tokens_used"] ?? null,
|
|
3171
|
+
latency_ms: row["latency_ms"] ?? null,
|
|
3172
|
+
context: row["context"] || null,
|
|
3173
|
+
lesson: row["lesson"] || null,
|
|
3174
|
+
when_to_use: row["when_to_use"] || null,
|
|
3175
|
+
agent_id: row["agent_id"] || null,
|
|
3176
|
+
project_id: row["project_id"] || null,
|
|
3177
|
+
session_id: row["session_id"] || null,
|
|
3178
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
3179
|
+
created_at: row["created_at"]
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
function saveToolEvent(input, db) {
|
|
3183
|
+
const d = db || getDatabase();
|
|
3184
|
+
const id = uuid();
|
|
3185
|
+
const timestamp = now();
|
|
3186
|
+
const metadataJson = JSON.stringify(input.metadata || {});
|
|
3187
|
+
d.run(`INSERT INTO tool_events (id, tool_name, action, success, error_type, error_message, tokens_used, latency_ms, context, lesson, when_to_use, agent_id, project_id, session_id, metadata, created_at)
|
|
3188
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3189
|
+
id,
|
|
3190
|
+
input.tool_name,
|
|
3191
|
+
input.action || null,
|
|
3192
|
+
input.success ? 1 : 0,
|
|
3193
|
+
input.error_type || null,
|
|
3194
|
+
input.error_message || null,
|
|
3195
|
+
input.tokens_used ?? null,
|
|
3196
|
+
input.latency_ms ?? null,
|
|
3197
|
+
input.context || null,
|
|
3198
|
+
input.lesson || null,
|
|
3199
|
+
input.when_to_use || null,
|
|
3200
|
+
input.agent_id || null,
|
|
3201
|
+
input.project_id || null,
|
|
3202
|
+
input.session_id || null,
|
|
3203
|
+
metadataJson,
|
|
3204
|
+
timestamp
|
|
3205
|
+
]);
|
|
3206
|
+
return getToolEvent(id, d);
|
|
3207
|
+
}
|
|
3208
|
+
function getToolEvent(id, db) {
|
|
3209
|
+
const d = db || getDatabase();
|
|
3210
|
+
const row = d.query("SELECT * FROM tool_events WHERE id = ?").get(id);
|
|
3211
|
+
if (!row)
|
|
3212
|
+
return null;
|
|
3213
|
+
return parseToolEventRow(row);
|
|
3214
|
+
}
|
|
3215
|
+
function getToolEvents(filters, db) {
|
|
3216
|
+
const d = db || getDatabase();
|
|
3217
|
+
const conditions = [];
|
|
3218
|
+
const params = [];
|
|
3219
|
+
if (filters.tool_name) {
|
|
3220
|
+
conditions.push("tool_name = ?");
|
|
3221
|
+
params.push(filters.tool_name);
|
|
3222
|
+
}
|
|
3223
|
+
if (filters.agent_id) {
|
|
3224
|
+
conditions.push("agent_id = ?");
|
|
3225
|
+
params.push(filters.agent_id);
|
|
3226
|
+
}
|
|
3227
|
+
if (filters.project_id) {
|
|
3228
|
+
conditions.push("project_id = ?");
|
|
3229
|
+
params.push(filters.project_id);
|
|
3230
|
+
}
|
|
3231
|
+
if (filters.success !== undefined) {
|
|
3232
|
+
conditions.push("success = ?");
|
|
3233
|
+
params.push(filters.success ? 1 : 0);
|
|
3234
|
+
}
|
|
3235
|
+
if (filters.from_date) {
|
|
3236
|
+
conditions.push("created_at >= ?");
|
|
3237
|
+
params.push(filters.from_date);
|
|
3238
|
+
}
|
|
3239
|
+
if (filters.to_date) {
|
|
3240
|
+
conditions.push("created_at <= ?");
|
|
3241
|
+
params.push(filters.to_date);
|
|
3242
|
+
}
|
|
3243
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3244
|
+
const limit = filters.limit || 50;
|
|
3245
|
+
const offset = filters.offset || 0;
|
|
3246
|
+
const rows = d.query(`SELECT * FROM tool_events ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
3247
|
+
return rows.map(parseToolEventRow);
|
|
3248
|
+
}
|
|
3249
|
+
function getToolStats(tool_name, project_id, db) {
|
|
3250
|
+
const d = db || getDatabase();
|
|
3251
|
+
let where = "WHERE tool_name = ?";
|
|
3252
|
+
const params = [tool_name];
|
|
3253
|
+
if (project_id) {
|
|
3254
|
+
where += " AND project_id = ?";
|
|
3255
|
+
params.push(project_id);
|
|
3256
|
+
}
|
|
3257
|
+
const stats = d.query(`SELECT
|
|
3258
|
+
COUNT(*) as total_calls,
|
|
3259
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success_count,
|
|
3260
|
+
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failure_count,
|
|
3261
|
+
AVG(CASE WHEN tokens_used IS NOT NULL THEN tokens_used END) as avg_tokens,
|
|
3262
|
+
AVG(CASE WHEN latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,
|
|
3263
|
+
MAX(created_at) as last_used
|
|
3264
|
+
FROM tool_events ${where}`).get(...params);
|
|
3265
|
+
const total = stats["total_calls"] || 0;
|
|
3266
|
+
const successCount = stats["success_count"] || 0;
|
|
3267
|
+
const errorRows = d.query(`SELECT error_type, COUNT(*) as count
|
|
3268
|
+
FROM tool_events ${where} AND error_type IS NOT NULL
|
|
3269
|
+
GROUP BY error_type ORDER BY count DESC LIMIT 5`).all(...params);
|
|
3270
|
+
return {
|
|
3271
|
+
tool_name,
|
|
3272
|
+
total_calls: total,
|
|
3273
|
+
success_count: successCount,
|
|
3274
|
+
failure_count: stats["failure_count"] || 0,
|
|
3275
|
+
success_rate: total > 0 ? successCount / total : 0,
|
|
3276
|
+
avg_tokens: stats["avg_tokens"] ?? null,
|
|
3277
|
+
avg_latency_ms: stats["avg_latency_ms"] ?? null,
|
|
3278
|
+
common_errors: errorRows,
|
|
3279
|
+
last_used: stats["last_used"] || ""
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
function getToolLessons(tool_name, project_id, limit, db) {
|
|
3283
|
+
const d = db || getDatabase();
|
|
3284
|
+
let where = "WHERE tool_name = ? AND lesson IS NOT NULL";
|
|
3285
|
+
const params = [tool_name];
|
|
3286
|
+
if (project_id) {
|
|
3287
|
+
where += " AND project_id = ?";
|
|
3288
|
+
params.push(project_id);
|
|
3289
|
+
}
|
|
3290
|
+
const rows = d.query(`SELECT lesson, when_to_use, created_at FROM tool_events ${where} ORDER BY created_at DESC LIMIT ?`).all(...params, limit || 20);
|
|
3291
|
+
return rows;
|
|
3292
|
+
}
|
|
3293
|
+
var init_tool_events = __esm(() => {
|
|
3294
|
+
init_database();
|
|
3295
|
+
});
|
|
3296
|
+
|
|
2943
3297
|
// src/db/webhook_hooks.ts
|
|
2944
3298
|
function parseRow(row) {
|
|
2945
3299
|
return {
|
|
@@ -4053,80 +4407,265 @@ var init_synthesis = __esm(() => {
|
|
|
4053
4407
|
init_database();
|
|
4054
4408
|
});
|
|
4055
4409
|
|
|
4056
|
-
// src/lib/
|
|
4057
|
-
var
|
|
4058
|
-
__export(
|
|
4059
|
-
|
|
4060
|
-
|
|
4410
|
+
// src/lib/when-to-use-generator.ts
|
|
4411
|
+
var exports_when_to_use_generator = {};
|
|
4412
|
+
__export(exports_when_to_use_generator, {
|
|
4413
|
+
generateWhenToUse: () => generateWhenToUse,
|
|
4414
|
+
autoGenerateWhenToUse: () => autoGenerateWhenToUse
|
|
4061
4415
|
});
|
|
4062
|
-
async function
|
|
4063
|
-
if (
|
|
4064
|
-
|
|
4065
|
-
|
|
4416
|
+
async function generateWhenToUse(key, value, category, tags) {
|
|
4417
|
+
if (process.env["MEMENTOS_AUTO_WHEN_TO_USE"] !== "true")
|
|
4418
|
+
return null;
|
|
4419
|
+
const apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
4420
|
+
if (!apiKey)
|
|
4421
|
+
return null;
|
|
4422
|
+
try {
|
|
4423
|
+
const userMessage = `Key: "${key}"
|
|
4424
|
+
Value: "${value}"
|
|
4425
|
+
Category: ${category}
|
|
4426
|
+
Tags: ${tags.join(", ") || "none"}
|
|
4427
|
+
|
|
4428
|
+
Generate the when_to_use activation context (1-2 sentences):`;
|
|
4429
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
4430
|
+
method: "POST",
|
|
4431
|
+
headers: {
|
|
4432
|
+
"x-api-key": apiKey,
|
|
4433
|
+
"anthropic-version": "2023-06-01",
|
|
4434
|
+
"content-type": "application/json"
|
|
4435
|
+
},
|
|
4436
|
+
body: JSON.stringify({
|
|
4437
|
+
model: "claude-haiku-4-5-20251001",
|
|
4438
|
+
max_tokens: 150,
|
|
4439
|
+
system: SYSTEM_PROMPT,
|
|
4440
|
+
messages: [{ role: "user", content: userMessage }]
|
|
4441
|
+
})
|
|
4442
|
+
});
|
|
4443
|
+
if (!response.ok)
|
|
4444
|
+
return null;
|
|
4445
|
+
const data = await response.json();
|
|
4446
|
+
const text = data.content?.[0]?.text?.trim();
|
|
4447
|
+
return text || null;
|
|
4448
|
+
} catch {
|
|
4449
|
+
return null;
|
|
4066
4450
|
}
|
|
4067
|
-
return _processConversationTurn;
|
|
4068
4451
|
}
|
|
4069
|
-
function
|
|
4070
|
-
if (
|
|
4452
|
+
async function autoGenerateWhenToUse(ctx) {
|
|
4453
|
+
if (ctx.memory.when_to_use)
|
|
4454
|
+
return;
|
|
4455
|
+
if (process.env["MEMENTOS_AUTO_WHEN_TO_USE"] !== "true")
|
|
4071
4456
|
return;
|
|
4072
|
-
_webhooksLoaded = true;
|
|
4073
4457
|
try {
|
|
4074
|
-
const
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
blocking: wh.blocking,
|
|
4079
|
-
priority: wh.priority,
|
|
4080
|
-
agentId: wh.agentId,
|
|
4081
|
-
projectId: wh.projectId,
|
|
4082
|
-
description: wh.description ?? `Webhook: ${wh.handlerUrl}`,
|
|
4083
|
-
handler: makeWebhookHandler(wh.id, wh.handlerUrl)
|
|
4084
|
-
});
|
|
4085
|
-
}
|
|
4086
|
-
if (webhooks.length > 0) {
|
|
4087
|
-
console.log(`[hooks] Loaded ${webhooks.length} webhook(s) from DB`);
|
|
4458
|
+
const whenToUse = await generateWhenToUse(ctx.memory.key, ctx.memory.value, ctx.memory.category, ctx.memory.tags);
|
|
4459
|
+
if (whenToUse) {
|
|
4460
|
+
const db = getDatabase();
|
|
4461
|
+
db.run("UPDATE memories SET when_to_use = ? WHERE id = ? AND when_to_use IS NULL", [whenToUse, ctx.memory.id]);
|
|
4088
4462
|
}
|
|
4089
|
-
} catch
|
|
4090
|
-
console.error("[hooks] Failed to load webhooks from DB:", err);
|
|
4091
|
-
}
|
|
4463
|
+
} catch {}
|
|
4092
4464
|
}
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4465
|
+
var SYSTEM_PROMPT = `You generate activation contexts for memory records. Given a memory's key, value, category, and tags, output a 1-2 sentence "when to use" description that describes the SITUATION or CONDITION under which an AI agent should retrieve this memory.
|
|
4466
|
+
|
|
4467
|
+
Rules:
|
|
4468
|
+
- Start with "When" or "If"
|
|
4469
|
+
- Describe the situation, not the content
|
|
4470
|
+
- Be specific enough to avoid false matches but general enough to catch relevant scenarios
|
|
4471
|
+
- Focus on the task/action the agent would be doing, not what the memory contains
|
|
4472
|
+
|
|
4473
|
+
Examples:
|
|
4474
|
+
- Key: "preferred-language", Value: "Always use TypeScript, never JavaScript" \u2192 "When choosing a programming language for a new file or project"
|
|
4475
|
+
- Key: "db-migration-order", Value: "Always run migrations before deploying" \u2192 "When deploying or updating database schema"
|
|
4476
|
+
- Key: "bash-chain-bug", Value: "Bash tool mangles && chains" \u2192 "When chaining commands with && in the Bash tool"`;
|
|
4477
|
+
var init_when_to_use_generator = __esm(() => {
|
|
4478
|
+
init_database();
|
|
4479
|
+
});
|
|
4480
|
+
|
|
4481
|
+
// src/lib/contradiction.ts
|
|
4482
|
+
var exports_contradiction = {};
|
|
4483
|
+
__export(exports_contradiction, {
|
|
4484
|
+
invalidateFact: () => invalidateFact,
|
|
4485
|
+
detectContradiction: () => detectContradiction
|
|
4486
|
+
});
|
|
4487
|
+
function invalidateFact(oldMemoryId, newMemoryId, db) {
|
|
4488
|
+
const d = db || getDatabase();
|
|
4489
|
+
const timestamp = now();
|
|
4490
|
+
d.run("UPDATE memories SET valid_until = ?, updated_at = ? WHERE id = ?", [timestamp, timestamp, oldMemoryId]);
|
|
4491
|
+
if (newMemoryId) {
|
|
4492
|
+
const row = d.query("SELECT metadata FROM memories WHERE id = ?").get(newMemoryId);
|
|
4493
|
+
if (row) {
|
|
4494
|
+
const metadata = JSON.parse(row.metadata || "{}");
|
|
4495
|
+
metadata.supersedes_id = oldMemoryId;
|
|
4496
|
+
d.run("UPDATE memories SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), timestamp, newMemoryId]);
|
|
4105
4497
|
}
|
|
4498
|
+
}
|
|
4499
|
+
return {
|
|
4500
|
+
invalidated_memory_id: oldMemoryId,
|
|
4501
|
+
new_memory_id: newMemoryId || null,
|
|
4502
|
+
valid_until: timestamp,
|
|
4503
|
+
supersedes_id: oldMemoryId
|
|
4106
4504
|
};
|
|
4107
4505
|
}
|
|
4108
|
-
function
|
|
4109
|
-
|
|
4110
|
-
|
|
4506
|
+
function heuristicContradictionScore(newValue, existingValue, newKey, existingKey) {
|
|
4507
|
+
if (newKey !== existingKey)
|
|
4508
|
+
return 0;
|
|
4509
|
+
const newLower = newValue.toLowerCase().trim();
|
|
4510
|
+
const existingLower = existingValue.toLowerCase().trim();
|
|
4511
|
+
if (newLower === existingLower)
|
|
4512
|
+
return 0;
|
|
4513
|
+
const newWords = new Set(newLower.split(/\s+/));
|
|
4514
|
+
const existingWords = new Set(existingLower.split(/\s+/));
|
|
4515
|
+
let overlap = 0;
|
|
4516
|
+
for (const w of newWords) {
|
|
4517
|
+
if (existingWords.has(w))
|
|
4518
|
+
overlap++;
|
|
4519
|
+
}
|
|
4520
|
+
const totalUnique = new Set([...newWords, ...existingWords]).size;
|
|
4521
|
+
const overlapRatio = totalUnique > 0 ? overlap / totalUnique : 0;
|
|
4522
|
+
if (overlapRatio < 0.3)
|
|
4523
|
+
return 0.7;
|
|
4524
|
+
if (overlapRatio < 0.5)
|
|
4525
|
+
return 0.4;
|
|
4526
|
+
return 0.1;
|
|
4111
4527
|
}
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4528
|
+
async function llmContradictionCheck(_newValue, _existingValue, _key) {
|
|
4529
|
+
const provider = providerRegistry.getAvailable();
|
|
4530
|
+
if (!provider) {
|
|
4531
|
+
return { contradicts: false, confidence: 0, reasoning: "No LLM provider available" };
|
|
4532
|
+
}
|
|
4533
|
+
try {
|
|
4534
|
+
return { contradicts: false, confidence: 0, reasoning: "LLM check skipped \u2014 using heuristic only" };
|
|
4535
|
+
} catch {
|
|
4536
|
+
return { contradicts: false, confidence: 0, reasoning: "LLM check failed" };
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
async function detectContradiction(newKey, newValue, options = {}, db) {
|
|
4540
|
+
const d = db || getDatabase();
|
|
4541
|
+
const { scope, project_id, min_importance = 7, use_llm = false } = options;
|
|
4542
|
+
const conditions = ["key = ?", "status = 'active'", "importance >= ?"];
|
|
4543
|
+
const params = [newKey, min_importance];
|
|
4544
|
+
if (scope) {
|
|
4545
|
+
conditions.push("scope = ?");
|
|
4546
|
+
params.push(scope);
|
|
4547
|
+
}
|
|
4548
|
+
if (project_id) {
|
|
4549
|
+
conditions.push("project_id = ?");
|
|
4550
|
+
params.push(project_id);
|
|
4551
|
+
}
|
|
4552
|
+
conditions.push("(valid_until IS NULL OR valid_until > datetime('now'))");
|
|
4553
|
+
const sql = `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY importance DESC LIMIT 10`;
|
|
4554
|
+
const rows = d.query(sql).all(...params);
|
|
4555
|
+
if (rows.length === 0) {
|
|
4556
|
+
return { contradicts: false, conflicting_memory: null, confidence: 0, reasoning: "No existing memories with this key" };
|
|
4557
|
+
}
|
|
4558
|
+
let bestContradiction = {
|
|
4559
|
+
contradicts: false,
|
|
4560
|
+
conflicting_memory: null,
|
|
4561
|
+
confidence: 0,
|
|
4562
|
+
reasoning: "No contradiction detected"
|
|
4563
|
+
};
|
|
4564
|
+
for (const row of rows) {
|
|
4565
|
+
const existing = parseMemoryRow(row);
|
|
4566
|
+
const heuristicScore = heuristicContradictionScore(newValue, existing.value, newKey, existing.key);
|
|
4567
|
+
if (heuristicScore > bestContradiction.confidence) {
|
|
4568
|
+
bestContradiction = {
|
|
4569
|
+
contradicts: heuristicScore >= 0.5,
|
|
4570
|
+
conflicting_memory: existing,
|
|
4571
|
+
confidence: heuristicScore,
|
|
4572
|
+
reasoning: heuristicScore >= 0.7 ? `New value for "${newKey}" significantly differs from existing high-importance memory (importance ${existing.importance})` : heuristicScore >= 0.5 ? `New value for "${newKey}" partially conflicts with existing memory (importance ${existing.importance})` : `Minor difference detected for "${newKey}"`
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
if (use_llm && bestContradiction.confidence >= 0.3 && bestContradiction.confidence < 0.7 && bestContradiction.conflicting_memory) {
|
|
4577
|
+
const llmResult = await llmContradictionCheck(newValue, bestContradiction.conflicting_memory.value, newKey);
|
|
4578
|
+
if (llmResult.confidence > bestContradiction.confidence) {
|
|
4579
|
+
bestContradiction = {
|
|
4580
|
+
...bestContradiction,
|
|
4581
|
+
contradicts: llmResult.contradicts,
|
|
4582
|
+
confidence: llmResult.confidence,
|
|
4583
|
+
reasoning: llmResult.reasoning
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
return bestContradiction;
|
|
4588
|
+
}
|
|
4589
|
+
var init_contradiction = __esm(() => {
|
|
4590
|
+
init_database();
|
|
4591
|
+
init_memories();
|
|
4592
|
+
init_registry();
|
|
4593
|
+
});
|
|
4594
|
+
|
|
4595
|
+
// src/lib/built-in-hooks.ts
|
|
4596
|
+
var exports_built_in_hooks = {};
|
|
4597
|
+
__export(exports_built_in_hooks, {
|
|
4598
|
+
reloadWebhooks: () => reloadWebhooks,
|
|
4599
|
+
loadWebhooksFromDb: () => loadWebhooksFromDb
|
|
4600
|
+
});
|
|
4601
|
+
async function getAutoMemory() {
|
|
4602
|
+
if (!_processConversationTurn) {
|
|
4603
|
+
const mod = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
|
|
4604
|
+
_processConversationTurn = mod.processConversationTurn;
|
|
4605
|
+
}
|
|
4606
|
+
return _processConversationTurn;
|
|
4607
|
+
}
|
|
4608
|
+
function loadWebhooksFromDb() {
|
|
4609
|
+
if (_webhooksLoaded)
|
|
4610
|
+
return;
|
|
4611
|
+
_webhooksLoaded = true;
|
|
4612
|
+
try {
|
|
4613
|
+
const webhooks = listWebhookHooks({ enabled: true });
|
|
4614
|
+
for (const wh of webhooks) {
|
|
4615
|
+
hookRegistry.register({
|
|
4616
|
+
type: wh.type,
|
|
4617
|
+
blocking: wh.blocking,
|
|
4618
|
+
priority: wh.priority,
|
|
4619
|
+
agentId: wh.agentId,
|
|
4620
|
+
projectId: wh.projectId,
|
|
4621
|
+
description: wh.description ?? `Webhook: ${wh.handlerUrl}`,
|
|
4622
|
+
handler: makeWebhookHandler(wh.id, wh.handlerUrl)
|
|
4623
|
+
});
|
|
4624
|
+
}
|
|
4625
|
+
if (webhooks.length > 0) {
|
|
4626
|
+
console.log(`[hooks] Loaded ${webhooks.length} webhook(s) from DB`);
|
|
4627
|
+
}
|
|
4628
|
+
} catch (err) {
|
|
4629
|
+
console.error("[hooks] Failed to load webhooks from DB:", err);
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
function makeWebhookHandler(webhookId, url) {
|
|
4633
|
+
return async (context) => {
|
|
4634
|
+
try {
|
|
4635
|
+
const res = await fetch(url, {
|
|
4636
|
+
method: "POST",
|
|
4637
|
+
headers: { "Content-Type": "application/json" },
|
|
4638
|
+
body: JSON.stringify(context),
|
|
4639
|
+
signal: AbortSignal.timeout(1e4)
|
|
4640
|
+
});
|
|
4641
|
+
recordWebhookInvocation(webhookId, res.ok);
|
|
4642
|
+
} catch {
|
|
4643
|
+
recordWebhookInvocation(webhookId, false);
|
|
4644
|
+
}
|
|
4645
|
+
};
|
|
4646
|
+
}
|
|
4647
|
+
function reloadWebhooks() {
|
|
4648
|
+
_webhooksLoaded = false;
|
|
4649
|
+
loadWebhooksFromDb();
|
|
4650
|
+
}
|
|
4651
|
+
var _processConversationTurn = null, _webhooksLoaded = false;
|
|
4652
|
+
var init_built_in_hooks = __esm(() => {
|
|
4653
|
+
init_hooks();
|
|
4654
|
+
init_webhook_hooks();
|
|
4655
|
+
hookRegistry.register({
|
|
4656
|
+
type: "PostMemorySave",
|
|
4657
|
+
blocking: false,
|
|
4658
|
+
builtin: true,
|
|
4659
|
+
priority: 100,
|
|
4660
|
+
description: "Trigger async LLM entity extraction when a memory is saved",
|
|
4661
|
+
handler: async (ctx) => {
|
|
4662
|
+
if (ctx.wasUpdated)
|
|
4663
|
+
return;
|
|
4664
|
+
const processConversationTurn2 = await getAutoMemory();
|
|
4665
|
+
processConversationTurn2(`${ctx.memory.key}: ${ctx.memory.value}`, {
|
|
4666
|
+
agentId: ctx.agentId,
|
|
4667
|
+
projectId: ctx.projectId,
|
|
4668
|
+
sessionId: ctx.sessionId
|
|
4130
4669
|
});
|
|
4131
4670
|
}
|
|
4132
4671
|
});
|
|
@@ -4179,10 +4718,76 @@ var init_built_in_hooks = __esm(() => {
|
|
|
4179
4718
|
description: "Generate and store vector embedding for semantic memory search",
|
|
4180
4719
|
handler: async (ctx) => {
|
|
4181
4720
|
const { indexMemoryEmbedding: indexMemoryEmbedding2 } = await Promise.resolve().then(() => (init_memories(), exports_memories));
|
|
4182
|
-
const text = [ctx.memory.value, ctx.memory.summary].filter(Boolean).join(" ");
|
|
4721
|
+
const text = ctx.memory.when_to_use || [ctx.memory.value, ctx.memory.summary].filter(Boolean).join(" ");
|
|
4183
4722
|
indexMemoryEmbedding2(ctx.memory.id, text);
|
|
4184
4723
|
}
|
|
4185
4724
|
});
|
|
4725
|
+
hookRegistry.register({
|
|
4726
|
+
type: "PostMemorySave",
|
|
4727
|
+
blocking: false,
|
|
4728
|
+
builtin: true,
|
|
4729
|
+
priority: 60,
|
|
4730
|
+
description: "Auto-generate when_to_use activation context via LLM if missing",
|
|
4731
|
+
handler: async (ctx) => {
|
|
4732
|
+
const { autoGenerateWhenToUse: autoGenerateWhenToUse2 } = await Promise.resolve().then(() => (init_when_to_use_generator(), exports_when_to_use_generator));
|
|
4733
|
+
await autoGenerateWhenToUse2(ctx);
|
|
4734
|
+
}
|
|
4735
|
+
});
|
|
4736
|
+
hookRegistry.register({
|
|
4737
|
+
type: "PostMemorySave",
|
|
4738
|
+
blocking: false,
|
|
4739
|
+
builtin: true,
|
|
4740
|
+
priority: 65,
|
|
4741
|
+
description: "Mark synthesized profile as stale when a preference or fact memory is saved",
|
|
4742
|
+
handler: async (ctx) => {
|
|
4743
|
+
const category = ctx.memory?.category;
|
|
4744
|
+
if (category !== "preference" && category !== "fact")
|
|
4745
|
+
return;
|
|
4746
|
+
try {
|
|
4747
|
+
const { markProfileStale: markProfileStale2 } = await Promise.resolve().then(() => (init_profile_synthesizer(), exports_profile_synthesizer));
|
|
4748
|
+
markProfileStale2(ctx.projectId, ctx.agentId);
|
|
4749
|
+
} catch {}
|
|
4750
|
+
}
|
|
4751
|
+
});
|
|
4752
|
+
hookRegistry.register({
|
|
4753
|
+
type: "PostMemorySave",
|
|
4754
|
+
blocking: false,
|
|
4755
|
+
builtin: true,
|
|
4756
|
+
priority: 70,
|
|
4757
|
+
description: "Auto-decay importance of existing memories contradicted by the newly saved memory",
|
|
4758
|
+
handler: async (ctx) => {
|
|
4759
|
+
if (ctx.wasUpdated)
|
|
4760
|
+
return;
|
|
4761
|
+
const memory = ctx.memory;
|
|
4762
|
+
if (memory.category !== "fact" && memory.category !== "knowledge")
|
|
4763
|
+
return;
|
|
4764
|
+
try {
|
|
4765
|
+
const { detectContradiction: detectContradiction2 } = await Promise.resolve().then(() => (init_contradiction(), exports_contradiction));
|
|
4766
|
+
const { updateMemory: updateMemory2, getMemory: getMemory2 } = await Promise.resolve().then(() => (init_memories(), exports_memories));
|
|
4767
|
+
const result = await detectContradiction2(memory.key, memory.value, {
|
|
4768
|
+
scope: memory.scope,
|
|
4769
|
+
project_id: ctx.projectId,
|
|
4770
|
+
min_importance: 1
|
|
4771
|
+
});
|
|
4772
|
+
if (!result.contradicts || !result.conflicting_memory)
|
|
4773
|
+
return;
|
|
4774
|
+
const conflicting = result.conflicting_memory;
|
|
4775
|
+
if (conflicting.id === memory.id)
|
|
4776
|
+
return;
|
|
4777
|
+
const fresh = getMemory2(conflicting.id);
|
|
4778
|
+
if (!fresh || fresh.status !== "active")
|
|
4779
|
+
return;
|
|
4780
|
+
const halvedImportance = Math.max(1, Math.floor(fresh.importance / 2));
|
|
4781
|
+
const metadata = { ...fresh.metadata || {}, contradicted_by: memory.id };
|
|
4782
|
+
updateMemory2(fresh.id, {
|
|
4783
|
+
importance: halvedImportance,
|
|
4784
|
+
flag: "contradicted",
|
|
4785
|
+
metadata,
|
|
4786
|
+
version: fresh.version
|
|
4787
|
+
});
|
|
4788
|
+
} catch {}
|
|
4789
|
+
}
|
|
4790
|
+
});
|
|
4186
4791
|
hookRegistry.register({
|
|
4187
4792
|
type: "PostMemoryInject",
|
|
4188
4793
|
blocking: false,
|
|
@@ -4236,156 +4841,42 @@ var init_memory_broadcast = __esm(() => {
|
|
|
4236
4841
|
CONVERSATIONS_API = process.env.CONVERSATIONS_API_URL || "http://localhost:7020";
|
|
4237
4842
|
});
|
|
4238
4843
|
|
|
4239
|
-
// src/
|
|
4240
|
-
var
|
|
4241
|
-
__export(
|
|
4242
|
-
|
|
4243
|
-
|
|
4844
|
+
// src/db/audit.ts
|
|
4845
|
+
var exports_audit = {};
|
|
4846
|
+
__export(exports_audit, {
|
|
4847
|
+
getMemoryAuditTrail: () => getMemoryAuditTrail,
|
|
4848
|
+
getAuditStats: () => getAuditStats,
|
|
4849
|
+
exportAuditLog: () => exportAuditLog
|
|
4244
4850
|
});
|
|
4245
|
-
function
|
|
4246
|
-
const d = db || getDatabase();
|
|
4247
|
-
const timestamp = now();
|
|
4248
|
-
d.run("UPDATE memories SET valid_until = ?, updated_at = ? WHERE id = ?", [timestamp, timestamp, oldMemoryId]);
|
|
4249
|
-
if (newMemoryId) {
|
|
4250
|
-
const row = d.query("SELECT metadata FROM memories WHERE id = ?").get(newMemoryId);
|
|
4251
|
-
if (row) {
|
|
4252
|
-
const metadata = JSON.parse(row.metadata || "{}");
|
|
4253
|
-
metadata.supersedes_id = oldMemoryId;
|
|
4254
|
-
d.run("UPDATE memories SET metadata = ?, updated_at = ? WHERE id = ?", [JSON.stringify(metadata), timestamp, newMemoryId]);
|
|
4255
|
-
}
|
|
4256
|
-
}
|
|
4851
|
+
function parseAuditRow(row) {
|
|
4257
4852
|
return {
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4853
|
+
id: row["id"],
|
|
4854
|
+
memory_id: row["memory_id"],
|
|
4855
|
+
memory_key: row["memory_key"] || null,
|
|
4856
|
+
operation: row["operation"],
|
|
4857
|
+
agent_id: row["agent_id"] || null,
|
|
4858
|
+
old_value_hash: row["old_value_hash"] || null,
|
|
4859
|
+
new_value_hash: row["new_value_hash"] || null,
|
|
4860
|
+
changes: JSON.parse(row["changes"] || "{}"),
|
|
4861
|
+
created_at: row["created_at"]
|
|
4262
4862
|
};
|
|
4263
4863
|
}
|
|
4264
|
-
function
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
const existingLower = existingValue.toLowerCase().trim();
|
|
4269
|
-
if (newLower === existingLower)
|
|
4270
|
-
return 0;
|
|
4271
|
-
const newWords = new Set(newLower.split(/\s+/));
|
|
4272
|
-
const existingWords = new Set(existingLower.split(/\s+/));
|
|
4273
|
-
let overlap = 0;
|
|
4274
|
-
for (const w of newWords) {
|
|
4275
|
-
if (existingWords.has(w))
|
|
4276
|
-
overlap++;
|
|
4277
|
-
}
|
|
4278
|
-
const totalUnique = new Set([...newWords, ...existingWords]).size;
|
|
4279
|
-
const overlapRatio = totalUnique > 0 ? overlap / totalUnique : 0;
|
|
4280
|
-
if (overlapRatio < 0.3)
|
|
4281
|
-
return 0.7;
|
|
4282
|
-
if (overlapRatio < 0.5)
|
|
4283
|
-
return 0.4;
|
|
4284
|
-
return 0.1;
|
|
4864
|
+
function getMemoryAuditTrail(memoryId, limit = 50, db) {
|
|
4865
|
+
const d = db || getDatabase();
|
|
4866
|
+
const rows = d.query("SELECT * FROM memory_audit_log WHERE memory_id = ? ORDER BY created_at DESC LIMIT ?").all(memoryId, limit);
|
|
4867
|
+
return rows.map(parseAuditRow);
|
|
4285
4868
|
}
|
|
4286
|
-
|
|
4287
|
-
const
|
|
4288
|
-
|
|
4289
|
-
|
|
4869
|
+
function exportAuditLog(options = {}, db) {
|
|
4870
|
+
const d = db || getDatabase();
|
|
4871
|
+
const conditions = [];
|
|
4872
|
+
const params = [];
|
|
4873
|
+
if (options.since) {
|
|
4874
|
+
conditions.push("created_at >= ?");
|
|
4875
|
+
params.push(options.since);
|
|
4290
4876
|
}
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
return { contradicts: false, confidence: 0, reasoning: "LLM check failed" };
|
|
4295
|
-
}
|
|
4296
|
-
}
|
|
4297
|
-
async function detectContradiction(newKey, newValue, options = {}, db) {
|
|
4298
|
-
const d = db || getDatabase();
|
|
4299
|
-
const { scope, project_id, min_importance = 7, use_llm = false } = options;
|
|
4300
|
-
const conditions = ["key = ?", "status = 'active'", "importance >= ?"];
|
|
4301
|
-
const params = [newKey, min_importance];
|
|
4302
|
-
if (scope) {
|
|
4303
|
-
conditions.push("scope = ?");
|
|
4304
|
-
params.push(scope);
|
|
4305
|
-
}
|
|
4306
|
-
if (project_id) {
|
|
4307
|
-
conditions.push("project_id = ?");
|
|
4308
|
-
params.push(project_id);
|
|
4309
|
-
}
|
|
4310
|
-
conditions.push("(valid_until IS NULL OR valid_until > datetime('now'))");
|
|
4311
|
-
const sql = `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY importance DESC LIMIT 10`;
|
|
4312
|
-
const rows = d.query(sql).all(...params);
|
|
4313
|
-
if (rows.length === 0) {
|
|
4314
|
-
return { contradicts: false, conflicting_memory: null, confidence: 0, reasoning: "No existing memories with this key" };
|
|
4315
|
-
}
|
|
4316
|
-
let bestContradiction = {
|
|
4317
|
-
contradicts: false,
|
|
4318
|
-
conflicting_memory: null,
|
|
4319
|
-
confidence: 0,
|
|
4320
|
-
reasoning: "No contradiction detected"
|
|
4321
|
-
};
|
|
4322
|
-
for (const row of rows) {
|
|
4323
|
-
const existing = parseMemoryRow(row);
|
|
4324
|
-
const heuristicScore = heuristicContradictionScore(newValue, existing.value, newKey, existing.key);
|
|
4325
|
-
if (heuristicScore > bestContradiction.confidence) {
|
|
4326
|
-
bestContradiction = {
|
|
4327
|
-
contradicts: heuristicScore >= 0.5,
|
|
4328
|
-
conflicting_memory: existing,
|
|
4329
|
-
confidence: heuristicScore,
|
|
4330
|
-
reasoning: heuristicScore >= 0.7 ? `New value for "${newKey}" significantly differs from existing high-importance memory (importance ${existing.importance})` : heuristicScore >= 0.5 ? `New value for "${newKey}" partially conflicts with existing memory (importance ${existing.importance})` : `Minor difference detected for "${newKey}"`
|
|
4331
|
-
};
|
|
4332
|
-
}
|
|
4333
|
-
}
|
|
4334
|
-
if (use_llm && bestContradiction.confidence >= 0.3 && bestContradiction.confidence < 0.7 && bestContradiction.conflicting_memory) {
|
|
4335
|
-
const llmResult = await llmContradictionCheck(newValue, bestContradiction.conflicting_memory.value, newKey);
|
|
4336
|
-
if (llmResult.confidence > bestContradiction.confidence) {
|
|
4337
|
-
bestContradiction = {
|
|
4338
|
-
...bestContradiction,
|
|
4339
|
-
contradicts: llmResult.contradicts,
|
|
4340
|
-
confidence: llmResult.confidence,
|
|
4341
|
-
reasoning: llmResult.reasoning
|
|
4342
|
-
};
|
|
4343
|
-
}
|
|
4344
|
-
}
|
|
4345
|
-
return bestContradiction;
|
|
4346
|
-
}
|
|
4347
|
-
var init_contradiction = __esm(() => {
|
|
4348
|
-
init_database();
|
|
4349
|
-
init_memories();
|
|
4350
|
-
init_registry();
|
|
4351
|
-
});
|
|
4352
|
-
|
|
4353
|
-
// src/db/audit.ts
|
|
4354
|
-
var exports_audit = {};
|
|
4355
|
-
__export(exports_audit, {
|
|
4356
|
-
getMemoryAuditTrail: () => getMemoryAuditTrail,
|
|
4357
|
-
getAuditStats: () => getAuditStats,
|
|
4358
|
-
exportAuditLog: () => exportAuditLog
|
|
4359
|
-
});
|
|
4360
|
-
function parseAuditRow(row) {
|
|
4361
|
-
return {
|
|
4362
|
-
id: row["id"],
|
|
4363
|
-
memory_id: row["memory_id"],
|
|
4364
|
-
memory_key: row["memory_key"] || null,
|
|
4365
|
-
operation: row["operation"],
|
|
4366
|
-
agent_id: row["agent_id"] || null,
|
|
4367
|
-
old_value_hash: row["old_value_hash"] || null,
|
|
4368
|
-
new_value_hash: row["new_value_hash"] || null,
|
|
4369
|
-
changes: JSON.parse(row["changes"] || "{}"),
|
|
4370
|
-
created_at: row["created_at"]
|
|
4371
|
-
};
|
|
4372
|
-
}
|
|
4373
|
-
function getMemoryAuditTrail(memoryId, limit = 50, db) {
|
|
4374
|
-
const d = db || getDatabase();
|
|
4375
|
-
const rows = d.query("SELECT * FROM memory_audit_log WHERE memory_id = ? ORDER BY created_at DESC LIMIT ?").all(memoryId, limit);
|
|
4376
|
-
return rows.map(parseAuditRow);
|
|
4377
|
-
}
|
|
4378
|
-
function exportAuditLog(options = {}, db) {
|
|
4379
|
-
const d = db || getDatabase();
|
|
4380
|
-
const conditions = [];
|
|
4381
|
-
const params = [];
|
|
4382
|
-
if (options.since) {
|
|
4383
|
-
conditions.push("created_at >= ?");
|
|
4384
|
-
params.push(options.since);
|
|
4385
|
-
}
|
|
4386
|
-
if (options.until) {
|
|
4387
|
-
conditions.push("created_at <= ?");
|
|
4388
|
-
params.push(options.until);
|
|
4877
|
+
if (options.until) {
|
|
4878
|
+
conditions.push("created_at <= ?");
|
|
4879
|
+
params.push(options.until);
|
|
4389
4880
|
}
|
|
4390
4881
|
if (options.operation) {
|
|
4391
4882
|
conditions.push("operation = ?");
|
|
@@ -4560,55 +5051,712 @@ var init_export_v1 = __esm(() => {
|
|
|
4560
5051
|
init_database();
|
|
4561
5052
|
});
|
|
4562
5053
|
|
|
4563
|
-
// src/lib/
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
}
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
const coreFacts = listMemories({ ...baseFilter, category: ["fact", "preference"], min_importance: 8, limit: max_per_section }, db);
|
|
4578
|
-
if (coreFacts.length > 0) {
|
|
4579
|
-
sections.push({ title: "Core Facts", memories: coreFacts });
|
|
5054
|
+
// src/lib/config.ts
|
|
5055
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, writeFileSync, unlinkSync } from "fs";
|
|
5056
|
+
import { homedir } from "os";
|
|
5057
|
+
import { basename as basename3, dirname as dirname5, join as join6, resolve as resolve4 } from "path";
|
|
5058
|
+
function deepMerge(target, source) {
|
|
5059
|
+
const result = { ...target };
|
|
5060
|
+
for (const key of Object.keys(source)) {
|
|
5061
|
+
const sourceVal = source[key];
|
|
5062
|
+
const targetVal = result[key];
|
|
5063
|
+
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
5064
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
5065
|
+
} else {
|
|
5066
|
+
result[key] = sourceVal;
|
|
5067
|
+
}
|
|
4580
5068
|
}
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
5069
|
+
return result;
|
|
5070
|
+
}
|
|
5071
|
+
function isValidScope(value) {
|
|
5072
|
+
return VALID_SCOPES.includes(value);
|
|
5073
|
+
}
|
|
5074
|
+
function isValidCategory(value) {
|
|
5075
|
+
return VALID_CATEGORIES.includes(value);
|
|
5076
|
+
}
|
|
5077
|
+
function loadConfig() {
|
|
5078
|
+
const configPath = join6(homedir(), ".mementos", "config.json");
|
|
5079
|
+
let fileConfig = {};
|
|
5080
|
+
if (existsSync6(configPath)) {
|
|
5081
|
+
try {
|
|
5082
|
+
const raw = readFileSync3(configPath, "utf-8");
|
|
5083
|
+
fileConfig = JSON.parse(raw);
|
|
5084
|
+
} catch {}
|
|
4586
5085
|
}
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
5086
|
+
const merged = deepMerge(DEFAULT_CONFIG2, fileConfig);
|
|
5087
|
+
const envScope = process.env["MEMENTOS_DEFAULT_SCOPE"];
|
|
5088
|
+
if (envScope && isValidScope(envScope)) {
|
|
5089
|
+
merged.default_scope = envScope;
|
|
5090
|
+
}
|
|
5091
|
+
const envCategory = process.env["MEMENTOS_DEFAULT_CATEGORY"];
|
|
5092
|
+
if (envCategory && isValidCategory(envCategory)) {
|
|
5093
|
+
merged.default_category = envCategory;
|
|
5094
|
+
}
|
|
5095
|
+
const envImportance = process.env["MEMENTOS_DEFAULT_IMPORTANCE"];
|
|
5096
|
+
if (envImportance) {
|
|
5097
|
+
const parsed = parseInt(envImportance, 10);
|
|
5098
|
+
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
5099
|
+
merged.default_importance = parsed;
|
|
4591
5100
|
}
|
|
4592
5101
|
}
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
5102
|
+
return merged;
|
|
5103
|
+
}
|
|
5104
|
+
var DEFAULT_CONFIG2, VALID_SCOPES, VALID_CATEGORIES;
|
|
5105
|
+
var init_config = __esm(() => {
|
|
5106
|
+
DEFAULT_CONFIG2 = {
|
|
5107
|
+
default_scope: "private",
|
|
5108
|
+
default_category: "knowledge",
|
|
5109
|
+
default_importance: 5,
|
|
5110
|
+
max_entries: 1000,
|
|
5111
|
+
max_entries_per_scope: {
|
|
5112
|
+
global: 500,
|
|
5113
|
+
shared: 300,
|
|
5114
|
+
private: 200,
|
|
5115
|
+
working: 100
|
|
5116
|
+
},
|
|
5117
|
+
injection: {
|
|
5118
|
+
max_tokens: 500,
|
|
5119
|
+
min_importance: 5,
|
|
5120
|
+
categories: ["preference", "fact"],
|
|
5121
|
+
refresh_interval: 5
|
|
5122
|
+
},
|
|
5123
|
+
extraction: {
|
|
5124
|
+
enabled: true,
|
|
5125
|
+
min_confidence: 0.5
|
|
5126
|
+
},
|
|
5127
|
+
sync_agents: ["claude", "codex", "gemini"],
|
|
5128
|
+
auto_cleanup: {
|
|
5129
|
+
enabled: true,
|
|
5130
|
+
expired_check_interval: 3600,
|
|
5131
|
+
unused_archive_days: 7,
|
|
5132
|
+
stale_deprioritize_days: 14
|
|
5133
|
+
}
|
|
5134
|
+
};
|
|
5135
|
+
VALID_SCOPES = ["global", "shared", "private", "working"];
|
|
5136
|
+
VALID_CATEGORIES = [
|
|
5137
|
+
"preference",
|
|
5138
|
+
"fact",
|
|
5139
|
+
"knowledge",
|
|
5140
|
+
"history"
|
|
5141
|
+
];
|
|
5142
|
+
});
|
|
5143
|
+
|
|
5144
|
+
// src/lib/injector.ts
|
|
5145
|
+
var exports_injector = {};
|
|
5146
|
+
__export(exports_injector, {
|
|
5147
|
+
smartInject: () => smartInject,
|
|
5148
|
+
MemoryInjector: () => MemoryInjector
|
|
5149
|
+
});
|
|
5150
|
+
|
|
5151
|
+
class MemoryInjector {
|
|
5152
|
+
config;
|
|
5153
|
+
injectedIds = new Set;
|
|
5154
|
+
constructor(config) {
|
|
5155
|
+
this.config = config || loadConfig();
|
|
4598
5156
|
}
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
5157
|
+
getInjectionContext(options = {}) {
|
|
5158
|
+
const maxTokens = options.max_tokens || this.config.injection.max_tokens;
|
|
5159
|
+
const minImportance = options.min_importance || this.config.injection.min_importance;
|
|
5160
|
+
const categories = options.categories || this.config.injection.categories;
|
|
5161
|
+
const db = options.db;
|
|
5162
|
+
const allMemories = [];
|
|
5163
|
+
const globalMems = listMemories({
|
|
5164
|
+
scope: "global",
|
|
5165
|
+
category: categories,
|
|
5166
|
+
min_importance: minImportance,
|
|
5167
|
+
status: "active",
|
|
5168
|
+
limit: 100
|
|
5169
|
+
}, db);
|
|
5170
|
+
allMemories.push(...globalMems);
|
|
5171
|
+
if (options.project_id) {
|
|
5172
|
+
const sharedMems = listMemories({
|
|
5173
|
+
scope: "shared",
|
|
5174
|
+
category: categories,
|
|
5175
|
+
min_importance: minImportance,
|
|
5176
|
+
status: "active",
|
|
5177
|
+
project_id: options.project_id,
|
|
5178
|
+
limit: 100
|
|
5179
|
+
}, db);
|
|
5180
|
+
allMemories.push(...sharedMems);
|
|
5181
|
+
}
|
|
5182
|
+
if (options.agent_id) {
|
|
5183
|
+
const privateMems = listMemories({
|
|
5184
|
+
scope: "private",
|
|
5185
|
+
category: categories,
|
|
5186
|
+
min_importance: minImportance,
|
|
5187
|
+
status: "active",
|
|
5188
|
+
agent_id: options.agent_id,
|
|
5189
|
+
limit: 100
|
|
5190
|
+
}, db);
|
|
5191
|
+
allMemories.push(...privateMems);
|
|
5192
|
+
}
|
|
5193
|
+
if (options.session_id || options.agent_id) {
|
|
5194
|
+
const workingMems = listMemories({
|
|
5195
|
+
scope: "working",
|
|
5196
|
+
status: "active",
|
|
5197
|
+
...options.session_id ? { session_id: options.session_id } : {},
|
|
5198
|
+
...options.agent_id ? { agent_id: options.agent_id } : {},
|
|
5199
|
+
...options.project_id ? { project_id: options.project_id } : {},
|
|
5200
|
+
limit: 100
|
|
5201
|
+
}, db);
|
|
5202
|
+
allMemories.push(...workingMems);
|
|
5203
|
+
}
|
|
5204
|
+
const seen = new Set;
|
|
5205
|
+
const unique = allMemories.filter((m) => {
|
|
4602
5206
|
if (seen.has(m.id))
|
|
4603
5207
|
return false;
|
|
4604
5208
|
seen.add(m.id);
|
|
4605
5209
|
return true;
|
|
4606
5210
|
});
|
|
5211
|
+
if (unique.length === 0) {
|
|
5212
|
+
return "";
|
|
5213
|
+
}
|
|
5214
|
+
const totalCharBudget = maxTokens * 4;
|
|
5215
|
+
const footer = "Tip: Use memory_search for deeper lookup on specific topics.";
|
|
5216
|
+
const footerChars = footer.length;
|
|
5217
|
+
const keyBudget = Math.floor((totalCharBudget - footerChars) * 0.67);
|
|
5218
|
+
const recentBudget = Math.floor((totalCharBudget - footerChars) * 0.33);
|
|
5219
|
+
const keyRanked = [...unique].sort((a, b) => {
|
|
5220
|
+
if (a.pinned !== b.pinned)
|
|
5221
|
+
return a.pinned ? -1 : 1;
|
|
5222
|
+
if (b.importance !== a.importance)
|
|
5223
|
+
return b.importance - a.importance;
|
|
5224
|
+
return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
|
|
5225
|
+
});
|
|
5226
|
+
const keyLines = [];
|
|
5227
|
+
const keyIds = new Set;
|
|
5228
|
+
let keyChars = 0;
|
|
5229
|
+
for (const m of keyRanked) {
|
|
5230
|
+
if (this.injectedIds.has(m.id))
|
|
5231
|
+
continue;
|
|
5232
|
+
const line = `- [${m.scope}/${m.category}] ${m.key}: ${m.value}`;
|
|
5233
|
+
if (keyChars + line.length > keyBudget)
|
|
5234
|
+
break;
|
|
5235
|
+
keyLines.push(line);
|
|
5236
|
+
keyIds.add(m.id);
|
|
5237
|
+
keyChars += line.length;
|
|
5238
|
+
}
|
|
5239
|
+
const recentRanked = [...unique].sort((a, b) => {
|
|
5240
|
+
const aTime = a.accessed_at ? new Date(a.accessed_at).getTime() : 0;
|
|
5241
|
+
const bTime = b.accessed_at ? new Date(b.accessed_at).getTime() : 0;
|
|
5242
|
+
return bTime - aTime;
|
|
5243
|
+
});
|
|
5244
|
+
const recentLines = [];
|
|
5245
|
+
let recentChars = 0;
|
|
5246
|
+
const maxRecent = 5;
|
|
5247
|
+
for (const m of recentRanked) {
|
|
5248
|
+
if (recentLines.length >= maxRecent)
|
|
5249
|
+
break;
|
|
5250
|
+
if (keyIds.has(m.id))
|
|
5251
|
+
continue;
|
|
5252
|
+
if (this.injectedIds.has(m.id))
|
|
5253
|
+
continue;
|
|
5254
|
+
const line = `- [${m.scope}/${m.category}] ${m.key}: ${m.value}`;
|
|
5255
|
+
if (recentChars + line.length > recentBudget)
|
|
5256
|
+
break;
|
|
5257
|
+
recentLines.push(line);
|
|
5258
|
+
recentChars += line.length;
|
|
5259
|
+
}
|
|
5260
|
+
if (keyLines.length === 0 && recentLines.length === 0) {
|
|
5261
|
+
return "";
|
|
5262
|
+
}
|
|
5263
|
+
const allInjectedMemoryIds = [
|
|
5264
|
+
...keyIds,
|
|
5265
|
+
...recentLines.map((_, i) => {
|
|
5266
|
+
let idx = 0;
|
|
5267
|
+
for (const m of recentRanked) {
|
|
5268
|
+
if (keyIds.has(m.id) || this.injectedIds.has(m.id))
|
|
5269
|
+
continue;
|
|
5270
|
+
if (idx === i)
|
|
5271
|
+
return m.id;
|
|
5272
|
+
idx++;
|
|
5273
|
+
}
|
|
5274
|
+
return "";
|
|
5275
|
+
})
|
|
5276
|
+
].filter(Boolean);
|
|
5277
|
+
for (const id of allInjectedMemoryIds) {
|
|
5278
|
+
this.injectedIds.add(id);
|
|
5279
|
+
touchMemory(id, db);
|
|
5280
|
+
}
|
|
5281
|
+
const sections = [];
|
|
5282
|
+
if (keyLines.length > 0) {
|
|
5283
|
+
sections.push(`## Key Memories
|
|
5284
|
+
${keyLines.join(`
|
|
5285
|
+
`)}`);
|
|
5286
|
+
}
|
|
5287
|
+
if (recentLines.length > 0) {
|
|
5288
|
+
sections.push(`## Recent Context
|
|
5289
|
+
${recentLines.join(`
|
|
5290
|
+
`)}`);
|
|
5291
|
+
}
|
|
5292
|
+
sections.push(footer);
|
|
5293
|
+
return `<agent-memories>
|
|
5294
|
+
${sections.join(`
|
|
5295
|
+
|
|
5296
|
+
`)}
|
|
5297
|
+
</agent-memories>`;
|
|
4607
5298
|
}
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
5299
|
+
async getSmartInjectionContext(options = {}) {
|
|
5300
|
+
if (!options.query) {
|
|
5301
|
+
return this.getInjectionContext(options);
|
|
5302
|
+
}
|
|
5303
|
+
const maxTokens = options.max_tokens || this.config.injection.max_tokens;
|
|
5304
|
+
const minImportance = options.min_importance || this.config.injection.min_importance;
|
|
5305
|
+
const categories = options.categories || this.config.injection.categories;
|
|
5306
|
+
const db = options.db;
|
|
5307
|
+
const allMemories = [];
|
|
5308
|
+
const globalMems = listMemories({
|
|
5309
|
+
scope: "global",
|
|
5310
|
+
category: categories,
|
|
5311
|
+
min_importance: minImportance,
|
|
5312
|
+
status: "active",
|
|
5313
|
+
limit: 100
|
|
5314
|
+
}, db);
|
|
5315
|
+
allMemories.push(...globalMems);
|
|
5316
|
+
if (options.project_id) {
|
|
5317
|
+
const sharedMems = listMemories({
|
|
5318
|
+
scope: "shared",
|
|
5319
|
+
category: categories,
|
|
5320
|
+
min_importance: minImportance,
|
|
5321
|
+
status: "active",
|
|
5322
|
+
project_id: options.project_id,
|
|
5323
|
+
limit: 100
|
|
5324
|
+
}, db);
|
|
5325
|
+
allMemories.push(...sharedMems);
|
|
5326
|
+
}
|
|
5327
|
+
if (options.agent_id) {
|
|
5328
|
+
const privateMems = listMemories({
|
|
5329
|
+
scope: "private",
|
|
5330
|
+
category: categories,
|
|
5331
|
+
min_importance: minImportance,
|
|
5332
|
+
status: "active",
|
|
5333
|
+
agent_id: options.agent_id,
|
|
5334
|
+
limit: 100
|
|
5335
|
+
}, db);
|
|
5336
|
+
allMemories.push(...privateMems);
|
|
5337
|
+
}
|
|
5338
|
+
if (options.session_id || options.agent_id) {
|
|
5339
|
+
const workingMems = listMemories({
|
|
5340
|
+
scope: "working",
|
|
5341
|
+
status: "active",
|
|
5342
|
+
...options.session_id ? { session_id: options.session_id } : {},
|
|
5343
|
+
...options.agent_id ? { agent_id: options.agent_id } : {},
|
|
5344
|
+
...options.project_id ? { project_id: options.project_id } : {},
|
|
5345
|
+
limit: 100
|
|
5346
|
+
}, db);
|
|
5347
|
+
allMemories.push(...workingMems);
|
|
5348
|
+
}
|
|
5349
|
+
const seen = new Set;
|
|
5350
|
+
const unique = allMemories.filter((m) => {
|
|
5351
|
+
if (seen.has(m.id))
|
|
5352
|
+
return false;
|
|
5353
|
+
seen.add(m.id);
|
|
5354
|
+
return true;
|
|
5355
|
+
});
|
|
5356
|
+
if (unique.length === 0) {
|
|
5357
|
+
return "";
|
|
5358
|
+
}
|
|
5359
|
+
const { embedding: queryEmbedding } = await generateEmbedding(options.query);
|
|
5360
|
+
const d = db || (await Promise.resolve().then(() => (init_database(), exports_database))).getDatabase();
|
|
5361
|
+
const embeddingRows = d.prepare(`SELECT memory_id, embedding FROM memory_embeddings WHERE memory_id IN (${unique.map(() => "?").join(",")})`).all(...unique.map((m) => m.id));
|
|
5362
|
+
const embeddingMap = new Map;
|
|
5363
|
+
for (const row of embeddingRows) {
|
|
5364
|
+
try {
|
|
5365
|
+
embeddingMap.set(row.memory_id, deserializeEmbedding(row.embedding));
|
|
5366
|
+
} catch {}
|
|
5367
|
+
}
|
|
5368
|
+
if (embeddingMap.size === 0) {
|
|
5369
|
+
return this.getInjectionContext(options);
|
|
5370
|
+
}
|
|
5371
|
+
const nowMs = Date.now();
|
|
5372
|
+
const oldestMs = Math.min(...unique.map((m) => new Date(m.updated_at).getTime()));
|
|
5373
|
+
const timeRange = nowMs - oldestMs || 1;
|
|
5374
|
+
const scored = unique.filter((m) => !this.injectedIds.has(m.id)).map((m) => {
|
|
5375
|
+
const memEmbedding = embeddingMap.get(m.id);
|
|
5376
|
+
const similarity = memEmbedding ? cosineSimilarity(queryEmbedding, memEmbedding) : 0;
|
|
5377
|
+
const importanceScore = m.importance / 10;
|
|
5378
|
+
const recencyScore = (new Date(m.updated_at).getTime() - oldestMs) / timeRange;
|
|
5379
|
+
const pinBonus = m.pinned ? 0.5 : 0;
|
|
5380
|
+
const score = similarity * 0.4 + importanceScore * 0.3 + recencyScore * 0.3 + pinBonus;
|
|
5381
|
+
return { memory: m, score };
|
|
5382
|
+
});
|
|
5383
|
+
scored.sort((a, b) => b.score - a.score);
|
|
5384
|
+
const totalCharBudget = maxTokens * 4;
|
|
5385
|
+
const footer = "Tip: Use memory_search for deeper lookup on specific topics.";
|
|
5386
|
+
const footerChars = footer.length;
|
|
5387
|
+
const contentBudget = totalCharBudget - footerChars;
|
|
5388
|
+
const lines = [];
|
|
5389
|
+
const injectedIds = [];
|
|
5390
|
+
let totalChars = 0;
|
|
5391
|
+
for (const { memory: m } of scored) {
|
|
5392
|
+
const line = `- [${m.scope}/${m.category}] ${m.key}: ${m.value}`;
|
|
5393
|
+
if (totalChars + line.length > contentBudget)
|
|
5394
|
+
break;
|
|
5395
|
+
lines.push(line);
|
|
5396
|
+
injectedIds.push(m.id);
|
|
5397
|
+
totalChars += line.length;
|
|
5398
|
+
}
|
|
5399
|
+
if (lines.length === 0) {
|
|
5400
|
+
return "";
|
|
5401
|
+
}
|
|
5402
|
+
for (const id of injectedIds) {
|
|
5403
|
+
this.injectedIds.add(id);
|
|
5404
|
+
touchMemory(id, db);
|
|
5405
|
+
}
|
|
5406
|
+
const sections = [];
|
|
5407
|
+
sections.push(`## Relevant Memories
|
|
5408
|
+
${lines.join(`
|
|
5409
|
+
`)}`);
|
|
5410
|
+
sections.push(footer);
|
|
5411
|
+
return `<agent-memories>
|
|
5412
|
+
${sections.join(`
|
|
5413
|
+
|
|
5414
|
+
`)}
|
|
5415
|
+
</agent-memories>`;
|
|
5416
|
+
}
|
|
5417
|
+
resetDedup() {
|
|
5418
|
+
this.injectedIds.clear();
|
|
5419
|
+
}
|
|
5420
|
+
getInjectedCount() {
|
|
5421
|
+
return this.injectedIds.size;
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
function detectToolMentions(taskContext) {
|
|
5425
|
+
const tools = new Set;
|
|
5426
|
+
const mcpToolPattern = /\b(memory_\w+|entity_\w+|graph_\w+|relation_\w+|session_\w+|webhook_\w+|hook_\w+)\b/gi;
|
|
5427
|
+
let match;
|
|
5428
|
+
while ((match = mcpToolPattern.exec(taskContext)) !== null) {
|
|
5429
|
+
tools.add(match[1].toLowerCase());
|
|
5430
|
+
}
|
|
5431
|
+
const cliPattern = /\b(git|npm|bun|curl|docker|kubectl|mementos)\b/gi;
|
|
5432
|
+
while ((match = cliPattern.exec(taskContext)) !== null) {
|
|
5433
|
+
tools.add(match[1].toLowerCase());
|
|
5434
|
+
}
|
|
5435
|
+
return Array.from(tools);
|
|
5436
|
+
}
|
|
5437
|
+
function hasToolContext(taskContext) {
|
|
5438
|
+
return TOOL_KEYWORD_PATTERNS.some((p) => p.test(taskContext));
|
|
5439
|
+
}
|
|
5440
|
+
function categoryToSection(category, key) {
|
|
5441
|
+
if (key.startsWith("_profile_"))
|
|
5442
|
+
return "Profile";
|
|
5443
|
+
switch (category) {
|
|
5444
|
+
case "fact":
|
|
5445
|
+
return "Core Facts";
|
|
5446
|
+
case "procedural":
|
|
5447
|
+
return "Procedures";
|
|
5448
|
+
case "preference":
|
|
5449
|
+
return "Preferences";
|
|
5450
|
+
case "history":
|
|
5451
|
+
return "Recent History";
|
|
5452
|
+
case "knowledge":
|
|
5453
|
+
return "Core Facts";
|
|
5454
|
+
case "resource":
|
|
5455
|
+
return "Core Facts";
|
|
5456
|
+
default:
|
|
5457
|
+
return "Core Facts";
|
|
5458
|
+
}
|
|
5459
|
+
}
|
|
5460
|
+
function collectVisibleMemories(options, db) {
|
|
5461
|
+
const allMemories = [];
|
|
5462
|
+
const minImportance = options.min_importance ?? 1;
|
|
5463
|
+
allMemories.push(...listMemories({ scope: "global", min_importance: minImportance, status: "active", limit: 100 }, db));
|
|
5464
|
+
if (options.project_id) {
|
|
5465
|
+
allMemories.push(...listMemories({
|
|
5466
|
+
scope: "shared",
|
|
5467
|
+
min_importance: minImportance,
|
|
5468
|
+
status: "active",
|
|
5469
|
+
project_id: options.project_id,
|
|
5470
|
+
limit: 100
|
|
5471
|
+
}, db));
|
|
5472
|
+
}
|
|
5473
|
+
if (options.agent_id) {
|
|
5474
|
+
allMemories.push(...listMemories({
|
|
5475
|
+
scope: "private",
|
|
5476
|
+
min_importance: minImportance,
|
|
5477
|
+
status: "active",
|
|
5478
|
+
agent_id: options.agent_id,
|
|
5479
|
+
limit: 100
|
|
5480
|
+
}, db));
|
|
5481
|
+
}
|
|
5482
|
+
if (options.session_id || options.agent_id) {
|
|
5483
|
+
allMemories.push(...listMemories({
|
|
5484
|
+
scope: "working",
|
|
5485
|
+
status: "active",
|
|
5486
|
+
...options.session_id ? { session_id: options.session_id } : {},
|
|
5487
|
+
...options.agent_id ? { agent_id: options.agent_id } : {},
|
|
5488
|
+
...options.project_id ? { project_id: options.project_id } : {},
|
|
5489
|
+
limit: 100
|
|
5490
|
+
}, db));
|
|
5491
|
+
}
|
|
5492
|
+
const seen = new Set;
|
|
5493
|
+
return allMemories.filter((m) => {
|
|
5494
|
+
if (seen.has(m.id))
|
|
5495
|
+
return false;
|
|
5496
|
+
seen.add(m.id);
|
|
5497
|
+
return true;
|
|
5498
|
+
});
|
|
5499
|
+
}
|
|
5500
|
+
function formatMemoryLine(m) {
|
|
5501
|
+
const value = m.value.length > 200 ? m.value.slice(0, 197) + "..." : m.value;
|
|
5502
|
+
return `- **${m.key}**: ${value}`;
|
|
5503
|
+
}
|
|
5504
|
+
async function smartInject(options) {
|
|
5505
|
+
const config = loadConfig();
|
|
5506
|
+
const maxTokens = options.max_tokens || config.injection.max_tokens;
|
|
5507
|
+
const minImportance = options.min_importance ?? config.injection.min_importance;
|
|
5508
|
+
const db = options.db;
|
|
5509
|
+
const taskContext = options.task_context;
|
|
5510
|
+
const totalCharBudget = maxTokens * 4;
|
|
5511
|
+
let profileText = null;
|
|
5512
|
+
let profileFromCache = false;
|
|
5513
|
+
try {
|
|
5514
|
+
const profileResult = await synthesizeProfile({
|
|
5515
|
+
project_id: options.project_id,
|
|
5516
|
+
agent_id: options.agent_id,
|
|
5517
|
+
scope: options.project_id ? "project" : options.agent_id ? "agent" : "global",
|
|
5518
|
+
force_refresh: options.force_profile_refresh
|
|
5519
|
+
});
|
|
5520
|
+
if (profileResult) {
|
|
5521
|
+
profileText = profileResult.profile;
|
|
5522
|
+
profileFromCache = profileResult.from_cache;
|
|
5523
|
+
}
|
|
5524
|
+
} catch {}
|
|
5525
|
+
const candidates = collectVisibleMemories({
|
|
5526
|
+
project_id: options.project_id,
|
|
5527
|
+
agent_id: options.agent_id,
|
|
5528
|
+
session_id: options.session_id,
|
|
5529
|
+
min_importance: minImportance
|
|
5530
|
+
}, db);
|
|
5531
|
+
const activationMap = new Map;
|
|
5532
|
+
try {
|
|
5533
|
+
const semanticResults = await semanticSearch(taskContext, {
|
|
5534
|
+
threshold: 0.25,
|
|
5535
|
+
limit: 50,
|
|
5536
|
+
project_id: options.project_id,
|
|
5537
|
+
agent_id: options.agent_id
|
|
5538
|
+
}, db);
|
|
5539
|
+
for (const result of semanticResults) {
|
|
5540
|
+
activationMap.set(result.memory.id, result.score);
|
|
5541
|
+
}
|
|
5542
|
+
} catch {}
|
|
5543
|
+
const detectedTools = detectToolMentions(taskContext);
|
|
5544
|
+
const includeToolGuides = hasToolContext(taskContext) || detectedTools.length > 0;
|
|
5545
|
+
const toolGuides = [];
|
|
5546
|
+
if (includeToolGuides) {
|
|
5547
|
+
for (const toolName of detectedTools.slice(0, 5)) {
|
|
5548
|
+
try {
|
|
5549
|
+
const stats = getToolStats(toolName, options.project_id, db);
|
|
5550
|
+
const lessons = getToolLessons(toolName, options.project_id, 5, db);
|
|
5551
|
+
if (stats.total_calls > 0 || lessons.length > 0) {
|
|
5552
|
+
const statsLine = stats.total_calls > 0 ? `${stats.total_calls} calls, ${Math.round(stats.success_rate * 100)}% success` + (stats.avg_latency_ms ? `, ~${Math.round(stats.avg_latency_ms)}ms avg` : "") : "no usage data";
|
|
5553
|
+
const lessonLines = lessons.map((l) => ` - ${l.lesson}${l.when_to_use ? ` (when: ${l.when_to_use})` : ""}`);
|
|
5554
|
+
toolGuides.push({ tool_name: toolName, stats_line: statsLine, lessons: lessonLines });
|
|
5555
|
+
}
|
|
5556
|
+
} catch {}
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
const scored = candidates.map((m) => {
|
|
5560
|
+
const decay = computeDecayScore(m);
|
|
5561
|
+
const activation = activationMap.get(m.id) ?? 0;
|
|
5562
|
+
const normalizedImportance = m.importance / 10;
|
|
5563
|
+
const pinBonus = m.pinned ? 1 : 0;
|
|
5564
|
+
const normalizedDecay = Math.min(decay / 10, 1);
|
|
5565
|
+
const score = activation * 0.35 + normalizedDecay * 0.35 + normalizedImportance * 0.2 + pinBonus * 0.1;
|
|
5566
|
+
return { memory: m, score, activation, decay };
|
|
5567
|
+
});
|
|
5568
|
+
scored.sort((a, b) => b.score - a.score);
|
|
5569
|
+
const footer = "Tip: Use memory_search for deeper lookup on specific topics.";
|
|
5570
|
+
const headerOverhead = 200;
|
|
5571
|
+
const availableBudget = totalCharBudget - footer.length - headerOverhead;
|
|
5572
|
+
const profileBudget = profileText ? Math.floor(availableBudget * 0.15) : 0;
|
|
5573
|
+
const toolGuideBudget = toolGuides.length > 0 ? Math.floor(availableBudget * 0.15) : 0;
|
|
5574
|
+
const memoryBudget = availableBudget - profileBudget - toolGuideBudget;
|
|
5575
|
+
const sectionWeights = {
|
|
5576
|
+
Profile: 0,
|
|
5577
|
+
"Core Facts": 0.3,
|
|
5578
|
+
"Tool Guides": 0,
|
|
5579
|
+
Procedures: 0.25,
|
|
5580
|
+
Preferences: 0.25,
|
|
5581
|
+
"Recent History": 0.2
|
|
5582
|
+
};
|
|
5583
|
+
const sectionBuckets = new Map;
|
|
5584
|
+
for (const s of scored) {
|
|
5585
|
+
if (s.memory.key.startsWith("_profile_"))
|
|
5586
|
+
continue;
|
|
5587
|
+
if (profileText && s.memory.category === "preference")
|
|
5588
|
+
continue;
|
|
5589
|
+
const section = categoryToSection(s.memory.category, s.memory.key);
|
|
5590
|
+
if (section === "Profile")
|
|
5591
|
+
continue;
|
|
5592
|
+
if (!sectionBuckets.has(section)) {
|
|
5593
|
+
sectionBuckets.set(section, []);
|
|
5594
|
+
}
|
|
5595
|
+
sectionBuckets.get(section).push(s);
|
|
5596
|
+
}
|
|
5597
|
+
const sectionOutput = new Map;
|
|
5598
|
+
const injectedIds = [];
|
|
5599
|
+
let totalMemoryCount = 0;
|
|
5600
|
+
const memorySections = ["Core Facts", "Procedures", "Preferences", "Recent History"];
|
|
5601
|
+
for (const sectionName of memorySections) {
|
|
5602
|
+
const bucket = sectionBuckets.get(sectionName) || [];
|
|
5603
|
+
if (bucket.length === 0)
|
|
5604
|
+
continue;
|
|
5605
|
+
const weight = sectionWeights[sectionName];
|
|
5606
|
+
const budget = Math.floor(memoryBudget * weight);
|
|
5607
|
+
const lines = [];
|
|
5608
|
+
let chars = 0;
|
|
5609
|
+
for (const { memory: m } of bucket) {
|
|
5610
|
+
const line = formatMemoryLine(m);
|
|
5611
|
+
if (chars + line.length > budget)
|
|
5612
|
+
break;
|
|
5613
|
+
lines.push(line);
|
|
5614
|
+
injectedIds.push(m.id);
|
|
5615
|
+
chars += line.length;
|
|
5616
|
+
totalMemoryCount++;
|
|
5617
|
+
}
|
|
5618
|
+
if (lines.length > 0) {
|
|
5619
|
+
sectionOutput.set(sectionName, lines);
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
const outputSections = [];
|
|
5623
|
+
if (profileText) {
|
|
5624
|
+
const trimmedProfile = profileText.length > profileBudget ? profileText.slice(0, profileBudget - 3) + "..." : profileText;
|
|
5625
|
+
outputSections.push(`## Profile
|
|
5626
|
+
${trimmedProfile}`);
|
|
5627
|
+
}
|
|
5628
|
+
if (sectionOutput.has("Core Facts")) {
|
|
5629
|
+
outputSections.push(`## Core Facts
|
|
5630
|
+
${sectionOutput.get("Core Facts").join(`
|
|
5631
|
+
`)}`);
|
|
5632
|
+
}
|
|
5633
|
+
if (toolGuides.length > 0) {
|
|
5634
|
+
const toolLines = [];
|
|
5635
|
+
let toolChars = 0;
|
|
5636
|
+
for (const guide of toolGuides) {
|
|
5637
|
+
const header = `- **${guide.tool_name}**: ${guide.stats_line}`;
|
|
5638
|
+
if (toolChars + header.length > toolGuideBudget)
|
|
5639
|
+
break;
|
|
5640
|
+
toolLines.push(header);
|
|
5641
|
+
toolChars += header.length;
|
|
5642
|
+
for (const lesson of guide.lessons) {
|
|
5643
|
+
if (toolChars + lesson.length > toolGuideBudget)
|
|
5644
|
+
break;
|
|
5645
|
+
toolLines.push(lesson);
|
|
5646
|
+
toolChars += lesson.length;
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
if (toolLines.length > 0) {
|
|
5650
|
+
outputSections.push(`## Tool Guides
|
|
5651
|
+
${toolLines.join(`
|
|
5652
|
+
`)}`);
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
if (sectionOutput.has("Procedures")) {
|
|
5656
|
+
outputSections.push(`## Procedures
|
|
5657
|
+
${sectionOutput.get("Procedures").join(`
|
|
5658
|
+
`)}`);
|
|
5659
|
+
}
|
|
5660
|
+
if (sectionOutput.has("Preferences")) {
|
|
5661
|
+
outputSections.push(`## Preferences
|
|
5662
|
+
${sectionOutput.get("Preferences").join(`
|
|
5663
|
+
`)}`);
|
|
5664
|
+
}
|
|
5665
|
+
if (sectionOutput.has("Recent History")) {
|
|
5666
|
+
outputSections.push(`## Recent History
|
|
5667
|
+
${sectionOutput.get("Recent History").join(`
|
|
5668
|
+
`)}`);
|
|
5669
|
+
}
|
|
5670
|
+
if (outputSections.length === 0) {
|
|
5671
|
+
return {
|
|
5672
|
+
output: "",
|
|
5673
|
+
token_estimate: 0,
|
|
5674
|
+
memory_count: 0,
|
|
5675
|
+
profile_from_cache: profileFromCache,
|
|
5676
|
+
detected_tools: detectedTools
|
|
5677
|
+
};
|
|
5678
|
+
}
|
|
5679
|
+
outputSections.push(footer);
|
|
5680
|
+
for (const id of injectedIds) {
|
|
5681
|
+
touchMemory(id, db);
|
|
5682
|
+
}
|
|
5683
|
+
const output = `<agent-memories>
|
|
5684
|
+
${outputSections.join(`
|
|
5685
|
+
|
|
5686
|
+
`)}
|
|
5687
|
+
</agent-memories>`;
|
|
5688
|
+
const tokenEstimate = Math.ceil(output.length / 4);
|
|
5689
|
+
return {
|
|
5690
|
+
output,
|
|
5691
|
+
token_estimate: tokenEstimate,
|
|
5692
|
+
memory_count: totalMemoryCount,
|
|
5693
|
+
profile_from_cache: profileFromCache,
|
|
5694
|
+
detected_tools: detectedTools
|
|
5695
|
+
};
|
|
5696
|
+
}
|
|
5697
|
+
var TOOL_KEYWORD_PATTERNS;
|
|
5698
|
+
var init_injector = __esm(() => {
|
|
5699
|
+
init_memories();
|
|
5700
|
+
init_config();
|
|
5701
|
+
init_profile_synthesizer();
|
|
5702
|
+
init_tool_events();
|
|
5703
|
+
TOOL_KEYWORD_PATTERNS = [
|
|
5704
|
+
/\b(mcp|tool|command|cli|server|endpoint|api)\b/i,
|
|
5705
|
+
/\b(memory_\w+|entity_\w+|graph_\w+|relation_\w+)\b/i,
|
|
5706
|
+
/\b(git|npm|bun|curl|docker|kubectl)\b/i,
|
|
5707
|
+
/\bmementos[\s-]?\w*/i
|
|
5708
|
+
];
|
|
5709
|
+
});
|
|
5710
|
+
|
|
5711
|
+
// src/lib/context.ts
|
|
5712
|
+
var exports_context = {};
|
|
5713
|
+
__export(exports_context, {
|
|
5714
|
+
formatLayeredContext: () => formatLayeredContext,
|
|
5715
|
+
assembleContext: () => assembleContext
|
|
5716
|
+
});
|
|
5717
|
+
function assembleContext(options = {}, db) {
|
|
5718
|
+
const { project_id, agent_id, scope, max_per_section = 10 } = options;
|
|
5719
|
+
const baseFilter = {
|
|
5720
|
+
project_id,
|
|
5721
|
+
agent_id,
|
|
5722
|
+
scope
|
|
5723
|
+
};
|
|
5724
|
+
const sections = [];
|
|
5725
|
+
const coreFacts = listMemories({ ...baseFilter, category: ["fact", "preference"], min_importance: 8, limit: max_per_section }, db);
|
|
5726
|
+
if (coreFacts.length > 0) {
|
|
5727
|
+
sections.push({ title: "Core Facts", memories: coreFacts });
|
|
5728
|
+
}
|
|
5729
|
+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
5730
|
+
const allRecent = listMemories({ ...baseFilter, limit: max_per_section * 3 }, db).filter((m) => m.created_at > oneDayAgo || m.accessed_at && m.accessed_at > oneDayAgo);
|
|
5731
|
+
const recentSlice = allRecent.slice(0, max_per_section);
|
|
5732
|
+
if (recentSlice.length > 0) {
|
|
5733
|
+
sections.push({ title: "Recent History", memories: recentSlice });
|
|
5734
|
+
}
|
|
5735
|
+
if (options.query) {
|
|
5736
|
+
const knowledge = listMemories({ ...baseFilter, category: "knowledge", search: options.query, limit: max_per_section }, db);
|
|
5737
|
+
if (knowledge.length > 0) {
|
|
5738
|
+
sections.push({ title: "Relevant Knowledge", memories: knowledge });
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5741
|
+
const decisions = listMemories({ ...baseFilter, category: "fact", limit: max_per_section }, db);
|
|
5742
|
+
const coreFactIds = new Set(coreFacts.map((m) => m.id));
|
|
5743
|
+
const newDecisions = decisions.filter((m) => !coreFactIds.has(m.id));
|
|
5744
|
+
if (newDecisions.length > 0) {
|
|
5745
|
+
sections.push({ title: "Active Decisions", memories: newDecisions.slice(0, max_per_section) });
|
|
5746
|
+
}
|
|
5747
|
+
const seen = new Set;
|
|
5748
|
+
for (const section of sections) {
|
|
5749
|
+
section.memories = section.memories.filter((m) => {
|
|
5750
|
+
if (seen.has(m.id))
|
|
5751
|
+
return false;
|
|
5752
|
+
seen.add(m.id);
|
|
5753
|
+
return true;
|
|
5754
|
+
});
|
|
5755
|
+
}
|
|
5756
|
+
const allMemories = sections.flatMap((s) => s.memories);
|
|
5757
|
+
const tokenEstimate = allMemories.reduce((acc, m) => acc + Math.ceil((m.key.length + m.value.length) / 4), 0);
|
|
5758
|
+
return {
|
|
5759
|
+
sections: sections.filter((s) => s.memories.length > 0),
|
|
4612
5760
|
total_memories: allMemories.length,
|
|
4613
5761
|
token_estimate: tokenEstimate
|
|
4614
5762
|
};
|
|
@@ -4787,96 +5935,6 @@ var init_acl = __esm(() => {
|
|
|
4787
5935
|
init_database();
|
|
4788
5936
|
});
|
|
4789
5937
|
|
|
4790
|
-
// src/lib/config.ts
|
|
4791
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, writeFileSync, unlinkSync } from "fs";
|
|
4792
|
-
import { homedir } from "os";
|
|
4793
|
-
import { basename as basename3, dirname as dirname4, join as join4, resolve as resolve4 } from "path";
|
|
4794
|
-
function deepMerge(target, source) {
|
|
4795
|
-
const result = { ...target };
|
|
4796
|
-
for (const key of Object.keys(source)) {
|
|
4797
|
-
const sourceVal = source[key];
|
|
4798
|
-
const targetVal = result[key];
|
|
4799
|
-
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
4800
|
-
result[key] = deepMerge(targetVal, sourceVal);
|
|
4801
|
-
} else {
|
|
4802
|
-
result[key] = sourceVal;
|
|
4803
|
-
}
|
|
4804
|
-
}
|
|
4805
|
-
return result;
|
|
4806
|
-
}
|
|
4807
|
-
function isValidScope(value) {
|
|
4808
|
-
return VALID_SCOPES.includes(value);
|
|
4809
|
-
}
|
|
4810
|
-
function isValidCategory(value) {
|
|
4811
|
-
return VALID_CATEGORIES.includes(value);
|
|
4812
|
-
}
|
|
4813
|
-
function loadConfig() {
|
|
4814
|
-
const configPath = join4(homedir(), ".mementos", "config.json");
|
|
4815
|
-
let fileConfig = {};
|
|
4816
|
-
if (existsSync4(configPath)) {
|
|
4817
|
-
try {
|
|
4818
|
-
const raw = readFileSync2(configPath, "utf-8");
|
|
4819
|
-
fileConfig = JSON.parse(raw);
|
|
4820
|
-
} catch {}
|
|
4821
|
-
}
|
|
4822
|
-
const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
|
|
4823
|
-
const envScope = process.env["MEMENTOS_DEFAULT_SCOPE"];
|
|
4824
|
-
if (envScope && isValidScope(envScope)) {
|
|
4825
|
-
merged.default_scope = envScope;
|
|
4826
|
-
}
|
|
4827
|
-
const envCategory = process.env["MEMENTOS_DEFAULT_CATEGORY"];
|
|
4828
|
-
if (envCategory && isValidCategory(envCategory)) {
|
|
4829
|
-
merged.default_category = envCategory;
|
|
4830
|
-
}
|
|
4831
|
-
const envImportance = process.env["MEMENTOS_DEFAULT_IMPORTANCE"];
|
|
4832
|
-
if (envImportance) {
|
|
4833
|
-
const parsed = parseInt(envImportance, 10);
|
|
4834
|
-
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
4835
|
-
merged.default_importance = parsed;
|
|
4836
|
-
}
|
|
4837
|
-
}
|
|
4838
|
-
return merged;
|
|
4839
|
-
}
|
|
4840
|
-
var DEFAULT_CONFIG, VALID_SCOPES, VALID_CATEGORIES;
|
|
4841
|
-
var init_config = __esm(() => {
|
|
4842
|
-
DEFAULT_CONFIG = {
|
|
4843
|
-
default_scope: "private",
|
|
4844
|
-
default_category: "knowledge",
|
|
4845
|
-
default_importance: 5,
|
|
4846
|
-
max_entries: 1000,
|
|
4847
|
-
max_entries_per_scope: {
|
|
4848
|
-
global: 500,
|
|
4849
|
-
shared: 300,
|
|
4850
|
-
private: 200,
|
|
4851
|
-
working: 100
|
|
4852
|
-
},
|
|
4853
|
-
injection: {
|
|
4854
|
-
max_tokens: 500,
|
|
4855
|
-
min_importance: 5,
|
|
4856
|
-
categories: ["preference", "fact"],
|
|
4857
|
-
refresh_interval: 5
|
|
4858
|
-
},
|
|
4859
|
-
extraction: {
|
|
4860
|
-
enabled: true,
|
|
4861
|
-
min_confidence: 0.5
|
|
4862
|
-
},
|
|
4863
|
-
sync_agents: ["claude", "codex", "gemini"],
|
|
4864
|
-
auto_cleanup: {
|
|
4865
|
-
enabled: true,
|
|
4866
|
-
expired_check_interval: 3600,
|
|
4867
|
-
unused_archive_days: 7,
|
|
4868
|
-
stale_deprioritize_days: 14
|
|
4869
|
-
}
|
|
4870
|
-
};
|
|
4871
|
-
VALID_SCOPES = ["global", "shared", "private", "working"];
|
|
4872
|
-
VALID_CATEGORIES = [
|
|
4873
|
-
"preference",
|
|
4874
|
-
"fact",
|
|
4875
|
-
"knowledge",
|
|
4876
|
-
"history"
|
|
4877
|
-
];
|
|
4878
|
-
});
|
|
4879
|
-
|
|
4880
5938
|
// src/lib/retention.ts
|
|
4881
5939
|
var exports_retention = {};
|
|
4882
5940
|
__export(exports_retention, {
|
|
@@ -9612,6 +10670,8 @@ async function buildFileDependencyGraph(opts, db) {
|
|
|
9612
10670
|
}
|
|
9613
10671
|
|
|
9614
10672
|
// src/mcp/index.ts
|
|
10673
|
+
init_profile_synthesizer();
|
|
10674
|
+
init_tool_events();
|
|
9615
10675
|
init_built_in_hooks();
|
|
9616
10676
|
init_webhook_hooks();
|
|
9617
10677
|
|
|
@@ -10641,6 +11701,207 @@ init_database();
|
|
|
10641
11701
|
// src/lib/session-processor.ts
|
|
10642
11702
|
init_memories();
|
|
10643
11703
|
init_registry();
|
|
11704
|
+
|
|
11705
|
+
// src/lib/tool-lesson-extractor.ts
|
|
11706
|
+
init_tool_events();
|
|
11707
|
+
init_memories();
|
|
11708
|
+
var SYSTEM_PROMPT2 = `You are a tool usage analyst. Given a session transcript containing tool calls and their results, extract actionable lessons about tool usage.
|
|
11709
|
+
|
|
11710
|
+
For each tool lesson, output a JSON array of objects with these fields:
|
|
11711
|
+
- tool_name: name of the tool
|
|
11712
|
+
- lesson: the insight (1-2 sentences)
|
|
11713
|
+
- when_to_use: activation context \u2014 when should an agent recall this lesson (start with "When" or "If")
|
|
11714
|
+
- success: boolean \u2014 was the tool call that taught this lesson successful?
|
|
11715
|
+
- error_type: if failed, one of: timeout, permission, not_found, syntax, rate_limit, other (or null if success)
|
|
11716
|
+
|
|
11717
|
+
Focus on:
|
|
11718
|
+
1. Successful patterns: what worked and why
|
|
11719
|
+
2. Failure lessons: what went wrong and how to avoid it
|
|
11720
|
+
3. Parameter insights: optimal settings discovered
|
|
11721
|
+
4. Alternative tools: when one tool is better than another
|
|
11722
|
+
5. Error recovery: what to do when a specific error occurs
|
|
11723
|
+
|
|
11724
|
+
Only extract genuinely useful, non-obvious lessons. Skip trivial observations.
|
|
11725
|
+
Output ONLY the JSON array, no markdown or explanation.`;
|
|
11726
|
+
async function extractToolLessons(transcript, options) {
|
|
11727
|
+
const apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
11728
|
+
if (!apiKey)
|
|
11729
|
+
return [];
|
|
11730
|
+
try {
|
|
11731
|
+
const truncated = transcript.length > 8000 ? transcript.slice(0, 8000) + `
|
|
11732
|
+
[...truncated]` : transcript;
|
|
11733
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
11734
|
+
method: "POST",
|
|
11735
|
+
headers: {
|
|
11736
|
+
"x-api-key": apiKey,
|
|
11737
|
+
"anthropic-version": "2023-06-01",
|
|
11738
|
+
"content-type": "application/json"
|
|
11739
|
+
},
|
|
11740
|
+
body: JSON.stringify({
|
|
11741
|
+
model: "claude-haiku-4-5-20251001",
|
|
11742
|
+
max_tokens: 1500,
|
|
11743
|
+
system: SYSTEM_PROMPT2,
|
|
11744
|
+
messages: [{ role: "user", content: `Extract tool lessons from this session transcript:
|
|
11745
|
+
|
|
11746
|
+
${truncated}` }]
|
|
11747
|
+
})
|
|
11748
|
+
});
|
|
11749
|
+
if (!response.ok)
|
|
11750
|
+
return [];
|
|
11751
|
+
const data = await response.json();
|
|
11752
|
+
const text = data.content?.[0]?.text?.trim();
|
|
11753
|
+
if (!text)
|
|
11754
|
+
return [];
|
|
11755
|
+
const lessons = JSON.parse(text);
|
|
11756
|
+
if (!Array.isArray(lessons))
|
|
11757
|
+
return [];
|
|
11758
|
+
for (const lesson of lessons) {
|
|
11759
|
+
if (!lesson.tool_name || !lesson.lesson)
|
|
11760
|
+
continue;
|
|
11761
|
+
try {
|
|
11762
|
+
saveToolEvent({
|
|
11763
|
+
tool_name: lesson.tool_name,
|
|
11764
|
+
success: lesson.success,
|
|
11765
|
+
error_type: lesson.error_type || undefined,
|
|
11766
|
+
lesson: lesson.lesson,
|
|
11767
|
+
when_to_use: lesson.when_to_use,
|
|
11768
|
+
context: "extracted from session transcript",
|
|
11769
|
+
agent_id: options?.agent_id,
|
|
11770
|
+
project_id: options?.project_id,
|
|
11771
|
+
session_id: options?.session_id
|
|
11772
|
+
});
|
|
11773
|
+
} catch {}
|
|
11774
|
+
try {
|
|
11775
|
+
createMemory({
|
|
11776
|
+
key: `tool-lesson-${lesson.tool_name}-${Date.now()}`,
|
|
11777
|
+
value: lesson.lesson,
|
|
11778
|
+
category: "knowledge",
|
|
11779
|
+
scope: "shared",
|
|
11780
|
+
importance: 7,
|
|
11781
|
+
source: "auto",
|
|
11782
|
+
tags: ["tool-memory", lesson.tool_name, "auto-extracted"],
|
|
11783
|
+
when_to_use: lesson.when_to_use,
|
|
11784
|
+
agent_id: options?.agent_id,
|
|
11785
|
+
project_id: options?.project_id,
|
|
11786
|
+
session_id: options?.session_id
|
|
11787
|
+
});
|
|
11788
|
+
} catch {}
|
|
11789
|
+
}
|
|
11790
|
+
return lessons;
|
|
11791
|
+
} catch {
|
|
11792
|
+
return [];
|
|
11793
|
+
}
|
|
11794
|
+
}
|
|
11795
|
+
|
|
11796
|
+
// src/lib/procedural-extractor.ts
|
|
11797
|
+
init_memories();
|
|
11798
|
+
init_database();
|
|
11799
|
+
var SYSTEM_PROMPT3 = `You extract procedural knowledge from session transcripts \u2014 workflows, step sequences, and problem-solution patterns.
|
|
11800
|
+
|
|
11801
|
+
For each procedure found, output a JSON array of objects:
|
|
11802
|
+
{
|
|
11803
|
+
"title": "short name for the workflow",
|
|
11804
|
+
"steps": [
|
|
11805
|
+
{"action": "what to do", "when_to_use": "activation context for this step"},
|
|
11806
|
+
{"action": "next step", "when_to_use": "activation context"}
|
|
11807
|
+
],
|
|
11808
|
+
"failure_patterns": ["what to avoid and why"],
|
|
11809
|
+
"when_to_use": "overall activation context for the whole procedure"
|
|
11810
|
+
}
|
|
11811
|
+
|
|
11812
|
+
Focus on:
|
|
11813
|
+
1. Multi-step workflows that were completed successfully
|
|
11814
|
+
2. Step sequences where order matters
|
|
11815
|
+
3. Failure \u2192 recovery patterns (what went wrong, how it was fixed)
|
|
11816
|
+
4. Problem-solution pairs (when X happens, do Y)
|
|
11817
|
+
|
|
11818
|
+
Only extract non-trivial procedures (3+ steps or genuinely useful patterns).
|
|
11819
|
+
Output ONLY the JSON array.`;
|
|
11820
|
+
async function extractProcedures(transcript, options) {
|
|
11821
|
+
const apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
11822
|
+
if (!apiKey)
|
|
11823
|
+
return [];
|
|
11824
|
+
try {
|
|
11825
|
+
const truncated = transcript.length > 8000 ? transcript.slice(0, 8000) + `
|
|
11826
|
+
[...truncated]` : transcript;
|
|
11827
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
11828
|
+
method: "POST",
|
|
11829
|
+
headers: {
|
|
11830
|
+
"x-api-key": apiKey,
|
|
11831
|
+
"anthropic-version": "2023-06-01",
|
|
11832
|
+
"content-type": "application/json"
|
|
11833
|
+
},
|
|
11834
|
+
body: JSON.stringify({
|
|
11835
|
+
model: "claude-haiku-4-5-20251001",
|
|
11836
|
+
max_tokens: 2000,
|
|
11837
|
+
system: SYSTEM_PROMPT3,
|
|
11838
|
+
messages: [{ role: "user", content: `Extract procedures from this session:
|
|
11839
|
+
|
|
11840
|
+
${truncated}` }]
|
|
11841
|
+
})
|
|
11842
|
+
});
|
|
11843
|
+
if (!response.ok)
|
|
11844
|
+
return [];
|
|
11845
|
+
const data = await response.json();
|
|
11846
|
+
const text = data.content?.[0]?.text?.trim();
|
|
11847
|
+
if (!text)
|
|
11848
|
+
return [];
|
|
11849
|
+
const procedures = JSON.parse(text);
|
|
11850
|
+
if (!Array.isArray(procedures))
|
|
11851
|
+
return [];
|
|
11852
|
+
for (const proc of procedures) {
|
|
11853
|
+
if (!proc.title || !proc.steps?.length)
|
|
11854
|
+
continue;
|
|
11855
|
+
const sequenceGroup = `proc-${shortUuid()}`;
|
|
11856
|
+
for (let i = 0;i < proc.steps.length; i++) {
|
|
11857
|
+
const step = proc.steps[i];
|
|
11858
|
+
if (!step)
|
|
11859
|
+
continue;
|
|
11860
|
+
try {
|
|
11861
|
+
createMemory({
|
|
11862
|
+
key: `${sequenceGroup}-step-${i + 1}`,
|
|
11863
|
+
value: step.action,
|
|
11864
|
+
category: "procedural",
|
|
11865
|
+
scope: "shared",
|
|
11866
|
+
importance: 7,
|
|
11867
|
+
source: "auto",
|
|
11868
|
+
tags: ["procedure", "auto-extracted", proc.title.toLowerCase().replace(/\s+/g, "-")],
|
|
11869
|
+
when_to_use: step.when_to_use || proc.when_to_use,
|
|
11870
|
+
sequence_group: sequenceGroup,
|
|
11871
|
+
sequence_order: i + 1,
|
|
11872
|
+
agent_id: options?.agent_id,
|
|
11873
|
+
project_id: options?.project_id,
|
|
11874
|
+
session_id: options?.session_id
|
|
11875
|
+
});
|
|
11876
|
+
} catch {}
|
|
11877
|
+
}
|
|
11878
|
+
for (const pattern of proc.failure_patterns || []) {
|
|
11879
|
+
try {
|
|
11880
|
+
createMemory({
|
|
11881
|
+
key: `${sequenceGroup}-warning-${shortUuid()}`,
|
|
11882
|
+
value: `WARNING: ${pattern}`,
|
|
11883
|
+
category: "procedural",
|
|
11884
|
+
scope: "shared",
|
|
11885
|
+
importance: 8,
|
|
11886
|
+
source: "auto",
|
|
11887
|
+
tags: ["procedure", "failure-pattern", "auto-extracted"],
|
|
11888
|
+
when_to_use: proc.when_to_use,
|
|
11889
|
+
sequence_group: sequenceGroup,
|
|
11890
|
+
sequence_order: 999,
|
|
11891
|
+
agent_id: options?.agent_id,
|
|
11892
|
+
project_id: options?.project_id,
|
|
11893
|
+
session_id: options?.session_id
|
|
11894
|
+
});
|
|
11895
|
+
} catch {}
|
|
11896
|
+
}
|
|
11897
|
+
}
|
|
11898
|
+
return procedures;
|
|
11899
|
+
} catch {
|
|
11900
|
+
return [];
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
|
|
11904
|
+
// src/lib/session-processor.ts
|
|
10644
11905
|
var SESSION_EXTRACTION_USER_TEMPLATE = (chunk, sessionId) => `Extract memories from this session chunk (session: ${sessionId}):
|
|
10645
11906
|
|
|
10646
11907
|
${chunk}
|
|
@@ -10802,6 +12063,20 @@ async function processSessionJob(jobId, db) {
|
|
|
10802
12063
|
}
|
|
10803
12064
|
}
|
|
10804
12065
|
result.memoriesExtracted = totalMemories;
|
|
12066
|
+
try {
|
|
12067
|
+
await extractToolLessons(job.transcript, {
|
|
12068
|
+
agent_id: job.agent_id ?? undefined,
|
|
12069
|
+
project_id: job.project_id ?? undefined,
|
|
12070
|
+
session_id: job.session_id
|
|
12071
|
+
});
|
|
12072
|
+
} catch {}
|
|
12073
|
+
try {
|
|
12074
|
+
await extractProcedures(job.transcript, {
|
|
12075
|
+
agent_id: job.agent_id ?? undefined,
|
|
12076
|
+
project_id: job.project_id ?? undefined,
|
|
12077
|
+
session_id: job.session_id
|
|
12078
|
+
});
|
|
12079
|
+
} catch {}
|
|
10805
12080
|
try {
|
|
10806
12081
|
if (result.errors.length > 0 && result.chunksProcessed === 0) {
|
|
10807
12082
|
updateSessionJob(jobId, {
|
|
@@ -10862,17 +12137,1430 @@ async function _processNext() {
|
|
|
10862
12137
|
}
|
|
10863
12138
|
}
|
|
10864
12139
|
}
|
|
10865
|
-
|
|
12140
|
+
|
|
12141
|
+
// src/lib/session-watcher.ts
|
|
12142
|
+
import { watch, existsSync as existsSync4, statSync as statSync2, readFileSync as readFileSync2 } from "fs";
|
|
12143
|
+
import { join as join4 } from "path";
|
|
12144
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
12145
|
+
function encodeCwd(cwd) {
|
|
12146
|
+
return cwd.replace(/\//g, "-");
|
|
12147
|
+
}
|
|
12148
|
+
function getProjectsDir() {
|
|
12149
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
12150
|
+
return join4(home, ".claude", "projects");
|
|
12151
|
+
}
|
|
12152
|
+
function findActiveSession(projectDir) {
|
|
12153
|
+
if (!existsSync4(projectDir))
|
|
12154
|
+
return null;
|
|
12155
|
+
const files = readdirSync2(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({
|
|
12156
|
+
name: f,
|
|
12157
|
+
path: join4(projectDir, f),
|
|
12158
|
+
mtime: statSync2(join4(projectDir, f)).mtimeMs
|
|
12159
|
+
})).sort((a, b) => b.mtime - a.mtime);
|
|
12160
|
+
return files[0]?.path || null;
|
|
12161
|
+
}
|
|
12162
|
+
var _watcher = null;
|
|
12163
|
+
var _lastOffset = 0;
|
|
12164
|
+
var _watchedFile = null;
|
|
12165
|
+
var _callback = null;
|
|
12166
|
+
var _pollInterval = null;
|
|
12167
|
+
function processNewLines(filePath, callback) {
|
|
12168
|
+
try {
|
|
12169
|
+
const stat = statSync2(filePath);
|
|
12170
|
+
if (stat.size <= _lastOffset)
|
|
12171
|
+
return;
|
|
12172
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
12173
|
+
const newContent = content.slice(_lastOffset);
|
|
12174
|
+
_lastOffset = stat.size;
|
|
12175
|
+
const lines = newContent.split(`
|
|
12176
|
+
`).filter((l) => l.trim());
|
|
12177
|
+
for (const line of lines) {
|
|
12178
|
+
try {
|
|
12179
|
+
const parsed = JSON.parse(line);
|
|
12180
|
+
const msg = parsed?.message;
|
|
12181
|
+
if (!msg)
|
|
12182
|
+
continue;
|
|
12183
|
+
const sessionMsg = {
|
|
12184
|
+
role: msg.role || "user",
|
|
12185
|
+
content: msg.content || "",
|
|
12186
|
+
timestamp: new Date().toISOString()
|
|
12187
|
+
};
|
|
12188
|
+
if (Array.isArray(msg.content)) {
|
|
12189
|
+
const toolUses = msg.content.filter((c) => c.type === "tool_use").map((c) => ({ name: c.name, input: c.input }));
|
|
12190
|
+
if (toolUses.length > 0) {
|
|
12191
|
+
sessionMsg.tool_use = toolUses;
|
|
12192
|
+
}
|
|
12193
|
+
}
|
|
12194
|
+
callback(sessionMsg);
|
|
12195
|
+
} catch {}
|
|
12196
|
+
}
|
|
12197
|
+
} catch {}
|
|
12198
|
+
}
|
|
12199
|
+
function startSessionWatcher(cwd, callback) {
|
|
12200
|
+
stopSessionWatcher();
|
|
12201
|
+
const projectDir = join4(getProjectsDir(), encodeCwd(cwd));
|
|
12202
|
+
const sessionFile = findActiveSession(projectDir);
|
|
12203
|
+
if (!sessionFile) {
|
|
12204
|
+
return { sessionFile: null };
|
|
12205
|
+
}
|
|
12206
|
+
_watchedFile = sessionFile;
|
|
12207
|
+
_callback = callback;
|
|
12208
|
+
_lastOffset = statSync2(sessionFile).size;
|
|
12209
|
+
try {
|
|
12210
|
+
_watcher = watch(sessionFile, () => {
|
|
12211
|
+
if (_callback && _watchedFile) {
|
|
12212
|
+
processNewLines(_watchedFile, _callback);
|
|
12213
|
+
}
|
|
12214
|
+
});
|
|
12215
|
+
} catch {}
|
|
12216
|
+
_pollInterval = setInterval(() => {
|
|
12217
|
+
if (_callback && _watchedFile) {
|
|
12218
|
+
processNewLines(_watchedFile, _callback);
|
|
12219
|
+
}
|
|
12220
|
+
}, 2000);
|
|
12221
|
+
return { sessionFile };
|
|
12222
|
+
}
|
|
12223
|
+
function stopSessionWatcher() {
|
|
12224
|
+
if (_watcher) {
|
|
12225
|
+
_watcher.close();
|
|
12226
|
+
_watcher = null;
|
|
12227
|
+
}
|
|
12228
|
+
if (_pollInterval) {
|
|
12229
|
+
clearInterval(_pollInterval);
|
|
12230
|
+
_pollInterval = null;
|
|
12231
|
+
}
|
|
12232
|
+
_watchedFile = null;
|
|
12233
|
+
_callback = null;
|
|
12234
|
+
_lastOffset = 0;
|
|
12235
|
+
}
|
|
12236
|
+
function getWatcherStatus() {
|
|
12237
|
+
return {
|
|
12238
|
+
active: _watcher !== null || _pollInterval !== null,
|
|
12239
|
+
watching_file: _watchedFile,
|
|
12240
|
+
last_offset: _lastOffset
|
|
12241
|
+
};
|
|
12242
|
+
}
|
|
12243
|
+
|
|
12244
|
+
// src/lib/context-extractor.ts
|
|
12245
|
+
var _recentContexts = [];
|
|
12246
|
+
var MAX_RECENT = 5;
|
|
12247
|
+
function wordSimilarity(a, b) {
|
|
12248
|
+
const wordsA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
12249
|
+
const wordsB = new Set(b.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
12250
|
+
if (wordsA.size === 0 || wordsB.size === 0)
|
|
12251
|
+
return 0;
|
|
12252
|
+
let intersection = 0;
|
|
12253
|
+
for (const w of wordsA) {
|
|
12254
|
+
if (wordsB.has(w))
|
|
12255
|
+
intersection++;
|
|
12256
|
+
}
|
|
12257
|
+
const union = new Set([...wordsA, ...wordsB]).size;
|
|
12258
|
+
return union > 0 ? intersection / union : 0;
|
|
12259
|
+
}
|
|
12260
|
+
function isDuplicate2(text) {
|
|
12261
|
+
for (const recent of _recentContexts) {
|
|
12262
|
+
if (wordSimilarity(text, recent) > 0.9)
|
|
12263
|
+
return true;
|
|
12264
|
+
}
|
|
12265
|
+
return false;
|
|
12266
|
+
}
|
|
12267
|
+
function addToRecent(text) {
|
|
12268
|
+
_recentContexts.push(text);
|
|
12269
|
+
if (_recentContexts.length > MAX_RECENT) {
|
|
12270
|
+
_recentContexts.shift();
|
|
12271
|
+
}
|
|
12272
|
+
}
|
|
12273
|
+
var TOOL_NAMES = new Set([
|
|
12274
|
+
"bash",
|
|
12275
|
+
"read",
|
|
12276
|
+
"write",
|
|
12277
|
+
"edit",
|
|
12278
|
+
"glob",
|
|
12279
|
+
"grep",
|
|
12280
|
+
"agent",
|
|
12281
|
+
"memory_save",
|
|
12282
|
+
"memory_recall",
|
|
12283
|
+
"memory_search",
|
|
12284
|
+
"memory_inject",
|
|
12285
|
+
"memory_context",
|
|
12286
|
+
"memory_profile",
|
|
12287
|
+
"memory_save_tool_event",
|
|
12288
|
+
"git",
|
|
12289
|
+
"npm",
|
|
12290
|
+
"bun",
|
|
12291
|
+
"docker",
|
|
12292
|
+
"kubectl",
|
|
12293
|
+
"curl"
|
|
12294
|
+
]);
|
|
12295
|
+
function detectTools(text) {
|
|
12296
|
+
const found = [];
|
|
12297
|
+
const lower = text.toLowerCase();
|
|
12298
|
+
for (const tool of TOOL_NAMES) {
|
|
12299
|
+
if (lower.includes(tool))
|
|
12300
|
+
found.push(tool);
|
|
12301
|
+
}
|
|
12302
|
+
return found;
|
|
12303
|
+
}
|
|
12304
|
+
function extractContext(message) {
|
|
12305
|
+
let contextText = "";
|
|
12306
|
+
let source = "user";
|
|
12307
|
+
let isSignificant = false;
|
|
12308
|
+
if (message.role === "user") {
|
|
12309
|
+
if (typeof message.content === "string") {
|
|
12310
|
+
contextText = message.content;
|
|
12311
|
+
} else if (Array.isArray(message.content)) {
|
|
12312
|
+
contextText = message.content.filter((c) => c.type === "text").map((c) => c.text || "").join(" ");
|
|
12313
|
+
}
|
|
12314
|
+
source = "user";
|
|
12315
|
+
isSignificant = contextText.length > 10;
|
|
12316
|
+
} else if (message.role === "assistant") {
|
|
12317
|
+
if (message.tool_use && message.tool_use.length > 0) {
|
|
12318
|
+
const parts = message.tool_use.map((tu) => {
|
|
12319
|
+
const inputSummary = tu.input ? Object.entries(tu.input).filter(([k]) => ["command", "pattern", "query", "file_path", "description", "prompt", "key", "value"].includes(k)).map(([k, v]) => `${k}=${String(v).slice(0, 100)}`).join(", ") : "";
|
|
12320
|
+
return `${tu.name}(${inputSummary})`;
|
|
12321
|
+
});
|
|
12322
|
+
contextText = parts.join("; ");
|
|
12323
|
+
source = "tool_use";
|
|
12324
|
+
isSignificant = false;
|
|
12325
|
+
} else if (typeof message.content === "string") {
|
|
12326
|
+
contextText = message.content.slice(0, 500);
|
|
12327
|
+
source = "assistant";
|
|
12328
|
+
isSignificant = false;
|
|
12329
|
+
} else if (Array.isArray(message.content)) {
|
|
12330
|
+
contextText = message.content.filter((c) => c.type === "text").map((c) => (c.text || "").slice(0, 200)).join(" ");
|
|
12331
|
+
source = "assistant";
|
|
12332
|
+
isSignificant = false;
|
|
12333
|
+
}
|
|
12334
|
+
}
|
|
12335
|
+
const errorPatterns = ["error", "failed", "exception", "enoent", "permission denied", "not found", "timeout"];
|
|
12336
|
+
if (errorPatterns.some((p) => contextText.toLowerCase().includes(p))) {
|
|
12337
|
+
isSignificant = true;
|
|
12338
|
+
}
|
|
12339
|
+
if (isSignificant && isDuplicate2(contextText)) {
|
|
12340
|
+
isSignificant = false;
|
|
12341
|
+
}
|
|
12342
|
+
if (isSignificant) {
|
|
12343
|
+
addToRecent(contextText);
|
|
12344
|
+
}
|
|
12345
|
+
return {
|
|
12346
|
+
context_text: contextText,
|
|
12347
|
+
tools_mentioned: detectTools(contextText),
|
|
12348
|
+
is_significant: isSignificant,
|
|
12349
|
+
source
|
|
12350
|
+
};
|
|
12351
|
+
}
|
|
12352
|
+
function resetContext() {
|
|
12353
|
+
_recentContexts.length = 0;
|
|
12354
|
+
}
|
|
12355
|
+
|
|
12356
|
+
// src/lib/activation-matcher.ts
|
|
12357
|
+
init_memories();
|
|
12358
|
+
var _recentlyPushed = new Map;
|
|
12359
|
+
var DEDUP_WINDOW_MS = 5 * 60 * 1000;
|
|
12360
|
+
function cleanRecentlyPushed() {
|
|
12361
|
+
const cutoff = Date.now() - DEDUP_WINDOW_MS;
|
|
12362
|
+
for (const [id, ts] of _recentlyPushed) {
|
|
12363
|
+
if (ts < cutoff)
|
|
12364
|
+
_recentlyPushed.delete(id);
|
|
12365
|
+
}
|
|
12366
|
+
}
|
|
12367
|
+
function markAsPushed(memoryIds) {
|
|
12368
|
+
const now2 = Date.now();
|
|
12369
|
+
for (const id of memoryIds) {
|
|
12370
|
+
_recentlyPushed.set(id, now2);
|
|
12371
|
+
}
|
|
12372
|
+
}
|
|
12373
|
+
async function findActivatedMemories(contextText, options) {
|
|
12374
|
+
if (!contextText || contextText.length < 10)
|
|
12375
|
+
return [];
|
|
12376
|
+
const minSimilarity = options?.min_similarity || 0.4;
|
|
12377
|
+
const maxResults = options?.max_results || 5;
|
|
12378
|
+
try {
|
|
12379
|
+
const results = await semanticSearch(contextText, {
|
|
12380
|
+
threshold: minSimilarity,
|
|
12381
|
+
limit: maxResults * 2,
|
|
12382
|
+
project_id: options?.project_id,
|
|
12383
|
+
agent_id: options?.agent_id
|
|
12384
|
+
});
|
|
12385
|
+
if (!results || results.length === 0) {
|
|
12386
|
+
return fallbackKeywordMatch(contextText, options);
|
|
12387
|
+
}
|
|
12388
|
+
cleanRecentlyPushed();
|
|
12389
|
+
let filtered = results.map((r) => r.memory).filter((m) => !_recentlyPushed.has(m.id)).filter((m) => m.status === "active");
|
|
12390
|
+
filtered = filtered.map((m) => ({
|
|
12391
|
+
memory: m,
|
|
12392
|
+
score: computeDecayScore({
|
|
12393
|
+
importance: m.importance,
|
|
12394
|
+
access_count: m.access_count,
|
|
12395
|
+
accessed_at: m.accessed_at,
|
|
12396
|
+
created_at: m.created_at,
|
|
12397
|
+
pinned: m.pinned
|
|
12398
|
+
})
|
|
12399
|
+
})).sort((a, b) => b.score - a.score).slice(0, maxResults).map((r) => r.memory);
|
|
12400
|
+
return filtered;
|
|
12401
|
+
} catch {
|
|
12402
|
+
return fallbackKeywordMatch(contextText, options);
|
|
12403
|
+
}
|
|
12404
|
+
}
|
|
12405
|
+
function fallbackKeywordMatch(contextText, options) {
|
|
12406
|
+
try {
|
|
12407
|
+
const allMemories = listMemories({
|
|
12408
|
+
project_id: options?.project_id,
|
|
12409
|
+
status: "active",
|
|
12410
|
+
limit: 100
|
|
12411
|
+
});
|
|
12412
|
+
const contextWords = new Set(contextText.toLowerCase().split(/\s+/).filter((w) => w.length > 3));
|
|
12413
|
+
const scored = allMemories.filter((m) => m.when_to_use).filter((m) => !_recentlyPushed.has(m.id)).map((m) => {
|
|
12414
|
+
const wtuWords = m.when_to_use.toLowerCase().split(/\s+/);
|
|
12415
|
+
const overlap = wtuWords.filter((w) => contextWords.has(w)).length;
|
|
12416
|
+
return { memory: m, score: overlap };
|
|
12417
|
+
}).filter((r) => r.score > 0).sort((a, b) => b.score - a.score).slice(0, options?.max_results || 5);
|
|
12418
|
+
return scored.map((r) => r.memory);
|
|
12419
|
+
} catch {
|
|
12420
|
+
return [];
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12423
|
+
function resetRecentlyPushed() {
|
|
12424
|
+
_recentlyPushed.clear();
|
|
12425
|
+
}
|
|
12426
|
+
function getRecentlyPushedCount() {
|
|
12427
|
+
cleanRecentlyPushed();
|
|
12428
|
+
return _recentlyPushed.size;
|
|
12429
|
+
}
|
|
12430
|
+
|
|
12431
|
+
// src/lib/session-registry.ts
|
|
12432
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
12433
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
12434
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
12435
|
+
var DB_PATH = join5(process.env["HOME"] || process.env["USERPROFILE"] || "~", ".open-sessions-registry.db");
|
|
12436
|
+
var _db2 = null;
|
|
12437
|
+
function getDb() {
|
|
12438
|
+
if (_db2)
|
|
12439
|
+
return _db2;
|
|
12440
|
+
const dir = dirname4(DB_PATH);
|
|
12441
|
+
if (!existsSync5(dir))
|
|
12442
|
+
mkdirSync2(dir, { recursive: true });
|
|
12443
|
+
_db2 = new Database2(DB_PATH, { create: true });
|
|
12444
|
+
_db2.run("PRAGMA journal_mode = WAL");
|
|
12445
|
+
_db2.run("PRAGMA busy_timeout = 3000");
|
|
12446
|
+
_db2.exec(`
|
|
12447
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
12448
|
+
id TEXT PRIMARY KEY,
|
|
12449
|
+
pid INTEGER NOT NULL,
|
|
12450
|
+
cwd TEXT NOT NULL,
|
|
12451
|
+
git_root TEXT,
|
|
12452
|
+
agent_name TEXT,
|
|
12453
|
+
project_name TEXT,
|
|
12454
|
+
tty TEXT,
|
|
12455
|
+
mcp_server TEXT NOT NULL,
|
|
12456
|
+
metadata TEXT DEFAULT '{}',
|
|
12457
|
+
registered_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
12458
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
12459
|
+
);
|
|
12460
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_pid_mcp
|
|
12461
|
+
ON sessions(pid, mcp_server);
|
|
12462
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
12463
|
+
ON sessions(project_name);
|
|
12464
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_agent
|
|
12465
|
+
ON sessions(agent_name);
|
|
12466
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_git_root
|
|
12467
|
+
ON sessions(git_root);
|
|
12468
|
+
`);
|
|
12469
|
+
return _db2;
|
|
12470
|
+
}
|
|
12471
|
+
function generateId() {
|
|
12472
|
+
return crypto.randomUUID().slice(0, 8);
|
|
12473
|
+
}
|
|
12474
|
+
function now2() {
|
|
12475
|
+
return new Date().toISOString();
|
|
12476
|
+
}
|
|
12477
|
+
function parseRow2(row) {
|
|
12478
|
+
return {
|
|
12479
|
+
id: row["id"],
|
|
12480
|
+
pid: row["pid"],
|
|
12481
|
+
cwd: row["cwd"],
|
|
12482
|
+
git_root: row["git_root"] || null,
|
|
12483
|
+
agent_name: row["agent_name"] || null,
|
|
12484
|
+
project_name: row["project_name"] || null,
|
|
12485
|
+
tty: row["tty"] || null,
|
|
12486
|
+
mcp_server: row["mcp_server"],
|
|
12487
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
12488
|
+
registered_at: row["registered_at"],
|
|
12489
|
+
last_seen_at: row["last_seen_at"]
|
|
12490
|
+
};
|
|
12491
|
+
}
|
|
12492
|
+
function isProcessAlive(pid) {
|
|
12493
|
+
try {
|
|
12494
|
+
process.kill(pid, 0);
|
|
12495
|
+
return true;
|
|
12496
|
+
} catch {
|
|
12497
|
+
return false;
|
|
12498
|
+
}
|
|
12499
|
+
}
|
|
12500
|
+
function registerSession(opts) {
|
|
12501
|
+
const db = getDb();
|
|
12502
|
+
const pid = process.pid;
|
|
12503
|
+
const cwd = opts.cwd || process.cwd();
|
|
12504
|
+
const id = generateId();
|
|
12505
|
+
const timestamp = now2();
|
|
12506
|
+
const existing = db.query("SELECT id FROM sessions WHERE pid = ? AND mcp_server = ?").get(pid, opts.mcp_server);
|
|
12507
|
+
if (existing) {
|
|
12508
|
+
db.run(`UPDATE sessions SET agent_name = ?, project_name = ?, cwd = ?,
|
|
12509
|
+
git_root = ?, tty = ?, metadata = ?, last_seen_at = ? WHERE id = ?`, [
|
|
12510
|
+
opts.agent_name || null,
|
|
12511
|
+
opts.project_name || null,
|
|
12512
|
+
cwd,
|
|
12513
|
+
opts.git_root || null,
|
|
12514
|
+
opts.tty || null,
|
|
12515
|
+
JSON.stringify(opts.metadata || {}),
|
|
12516
|
+
timestamp,
|
|
12517
|
+
existing.id
|
|
12518
|
+
]);
|
|
12519
|
+
return getSession(existing.id);
|
|
12520
|
+
}
|
|
12521
|
+
db.run(`INSERT INTO sessions (id, pid, cwd, git_root, agent_name, project_name, tty, mcp_server, metadata, registered_at, last_seen_at)
|
|
12522
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
12523
|
+
id,
|
|
12524
|
+
pid,
|
|
12525
|
+
cwd,
|
|
12526
|
+
opts.git_root || null,
|
|
12527
|
+
opts.agent_name || null,
|
|
12528
|
+
opts.project_name || null,
|
|
12529
|
+
opts.tty || null,
|
|
12530
|
+
opts.mcp_server,
|
|
12531
|
+
JSON.stringify(opts.metadata || {}),
|
|
12532
|
+
timestamp,
|
|
12533
|
+
timestamp
|
|
12534
|
+
]);
|
|
12535
|
+
return getSession(id);
|
|
12536
|
+
}
|
|
12537
|
+
function heartbeatSession(id) {
|
|
12538
|
+
const db = getDb();
|
|
12539
|
+
db.run("UPDATE sessions SET last_seen_at = ? WHERE id = ?", [now2(), id]);
|
|
12540
|
+
}
|
|
12541
|
+
function unregisterSession(id) {
|
|
12542
|
+
const db = getDb();
|
|
12543
|
+
db.run("DELETE FROM sessions WHERE id = ?", [id]);
|
|
12544
|
+
}
|
|
12545
|
+
function getSession(id) {
|
|
12546
|
+
const db = getDb();
|
|
12547
|
+
const row = db.query("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
12548
|
+
return row ? parseRow2(row) : null;
|
|
12549
|
+
}
|
|
12550
|
+
function cleanStaleSessions() {
|
|
12551
|
+
const db = getDb();
|
|
12552
|
+
const rows = db.query("SELECT id, pid FROM sessions").all();
|
|
12553
|
+
let cleaned = 0;
|
|
12554
|
+
for (const row of rows) {
|
|
12555
|
+
if (!isProcessAlive(row.pid)) {
|
|
12556
|
+
db.run("DELETE FROM sessions WHERE id = ?", [row.id]);
|
|
12557
|
+
cleaned++;
|
|
12558
|
+
}
|
|
12559
|
+
}
|
|
12560
|
+
return cleaned;
|
|
12561
|
+
}
|
|
12562
|
+
|
|
12563
|
+
// src/lib/channel-pusher.ts
|
|
12564
|
+
var _serverRef = null;
|
|
12565
|
+
function setServerRef(server) {
|
|
12566
|
+
_serverRef = server;
|
|
12567
|
+
}
|
|
12568
|
+
async function pushMemoryNotification(memories, context, type = "auto-inject") {
|
|
12569
|
+
if (!_serverRef)
|
|
12570
|
+
return false;
|
|
12571
|
+
if (memories.length === 0)
|
|
12572
|
+
return false;
|
|
12573
|
+
const formatted = formatMemories(memories, context, type);
|
|
12574
|
+
try {
|
|
12575
|
+
const server = _serverRef.server || _serverRef;
|
|
12576
|
+
await server.notification({
|
|
12577
|
+
method: "notifications/claude/channel",
|
|
12578
|
+
params: {
|
|
12579
|
+
content: formatted,
|
|
12580
|
+
meta: {
|
|
12581
|
+
source: "mementos",
|
|
12582
|
+
type,
|
|
12583
|
+
memory_count: memories.length,
|
|
12584
|
+
context: context.slice(0, 200)
|
|
12585
|
+
}
|
|
12586
|
+
}
|
|
12587
|
+
});
|
|
12588
|
+
return true;
|
|
12589
|
+
} catch (e) {
|
|
12590
|
+
return false;
|
|
12591
|
+
}
|
|
12592
|
+
}
|
|
12593
|
+
async function pushRawNotification(text, type = "info") {
|
|
12594
|
+
if (!_serverRef)
|
|
12595
|
+
return false;
|
|
12596
|
+
try {
|
|
12597
|
+
const server = _serverRef.server || _serverRef;
|
|
12598
|
+
await server.notification({
|
|
12599
|
+
method: "notifications/claude/channel",
|
|
12600
|
+
params: {
|
|
12601
|
+
content: text,
|
|
12602
|
+
meta: { source: "mementos", type }
|
|
12603
|
+
}
|
|
12604
|
+
});
|
|
12605
|
+
return true;
|
|
12606
|
+
} catch {
|
|
12607
|
+
return false;
|
|
12608
|
+
}
|
|
12609
|
+
}
|
|
12610
|
+
function formatMemories(memories, context, type) {
|
|
12611
|
+
const header = type === "session-briefing" ? "\uD83D\uDCCB Mementos Session Briefing" : `\u26A1 Mementos activated (${context.slice(0, 100)})`;
|
|
12612
|
+
const lines = memories.map((m) => {
|
|
12613
|
+
const importance = m.importance >= 8 ? "\u2757" : "";
|
|
12614
|
+
const flag = m.flag ? ` \u26A0\uFE0F ${m.flag}` : "";
|
|
12615
|
+
return `\u2022 [${m.category}] ${m.key}: ${m.value.slice(0, 200)}${m.value.length > 200 ? "..." : ""} (importance: ${m.importance})${importance}${flag}`;
|
|
12616
|
+
});
|
|
12617
|
+
const footer = memories.length > 3 ? `
|
|
12618
|
+
Use memory_recall(key="...") for full details on any of these.` : "";
|
|
12619
|
+
return `${header}
|
|
12620
|
+
|
|
12621
|
+
${lines.join(`
|
|
12622
|
+
`)}${footer}`;
|
|
12623
|
+
}
|
|
12624
|
+
function formatBriefing(sections) {
|
|
12625
|
+
const parts = [];
|
|
12626
|
+
parts.push(`\uD83D\uDCCB Mementos Session Briefing${sections.projectName ? ` \u2014 project: ${sections.projectName}` : ""}`);
|
|
12627
|
+
if (sections.profile) {
|
|
12628
|
+
parts.push(`
|
|
12629
|
+
## Profile
|
|
12630
|
+
${sections.profile}`);
|
|
12631
|
+
}
|
|
12632
|
+
if (sections.memories && sections.memories.length > 0) {
|
|
12633
|
+
parts.push(`
|
|
12634
|
+
## Key Memories (${sections.memories.length})`);
|
|
12635
|
+
for (const m of sections.memories) {
|
|
12636
|
+
parts.push(`\u2022 [${m.category}] ${m.key}: ${m.value.slice(0, 150)}${m.value.length > 150 ? "..." : ""}`);
|
|
12637
|
+
}
|
|
12638
|
+
}
|
|
12639
|
+
if (sections.lastSession) {
|
|
12640
|
+
parts.push(`
|
|
12641
|
+
## Last Session
|
|
12642
|
+
${sections.lastSession}`);
|
|
12643
|
+
}
|
|
12644
|
+
if (sections.flagged && sections.flagged.length > 0) {
|
|
12645
|
+
parts.push(`
|
|
12646
|
+
## Needs Attention`);
|
|
12647
|
+
for (const m of sections.flagged) {
|
|
12648
|
+
parts.push(`\u26A0\uFE0F [${m.flag}] ${m.key}: ${m.value.slice(0, 150)}`);
|
|
12649
|
+
}
|
|
12650
|
+
}
|
|
12651
|
+
parts.push(`
|
|
12652
|
+
Use memory_recall, memory_search, or memory_inject for more details.`);
|
|
12653
|
+
return parts.join(`
|
|
12654
|
+
`);
|
|
12655
|
+
}
|
|
12656
|
+
|
|
12657
|
+
// src/lib/session-start-briefing.ts
|
|
12658
|
+
init_memories();
|
|
12659
|
+
async function pushSessionBriefing(options) {
|
|
12660
|
+
if (!options.project_id)
|
|
12661
|
+
return false;
|
|
12662
|
+
try {
|
|
12663
|
+
let profile = null;
|
|
12664
|
+
try {
|
|
12665
|
+
const { synthesizeProfile: synthesizeProfile2 } = await Promise.resolve().then(() => (init_profile_synthesizer(), exports_profile_synthesizer));
|
|
12666
|
+
const result = await synthesizeProfile2({
|
|
12667
|
+
project_id: options.project_id,
|
|
12668
|
+
scope: "project"
|
|
12669
|
+
});
|
|
12670
|
+
if (result)
|
|
12671
|
+
profile = result.profile;
|
|
12672
|
+
} catch {}
|
|
12673
|
+
const pinnedMemories = listMemories({
|
|
12674
|
+
project_id: options.project_id,
|
|
12675
|
+
pinned: true,
|
|
12676
|
+
status: "active",
|
|
12677
|
+
limit: 5
|
|
12678
|
+
});
|
|
12679
|
+
const topMemories = listMemories({
|
|
12680
|
+
project_id: options.project_id,
|
|
12681
|
+
status: "active",
|
|
12682
|
+
min_importance: 7,
|
|
12683
|
+
limit: 10
|
|
12684
|
+
});
|
|
12685
|
+
const seenIds = new Set;
|
|
12686
|
+
const memories = [];
|
|
12687
|
+
for (const m of [...pinnedMemories, ...topMemories]) {
|
|
12688
|
+
if (!seenIds.has(m.id) && !m.key.startsWith("_profile_")) {
|
|
12689
|
+
seenIds.add(m.id);
|
|
12690
|
+
memories.push(m);
|
|
12691
|
+
if (memories.length >= 10)
|
|
12692
|
+
break;
|
|
12693
|
+
}
|
|
12694
|
+
}
|
|
12695
|
+
let lastSession = null;
|
|
12696
|
+
try {
|
|
12697
|
+
const sessionMemories = listMemories({
|
|
12698
|
+
project_id: options.project_id,
|
|
12699
|
+
category: "history",
|
|
12700
|
+
status: "active",
|
|
12701
|
+
limit: 5
|
|
12702
|
+
});
|
|
12703
|
+
const summary = sessionMemories.find((m) => m.key.includes("summary"));
|
|
12704
|
+
if (summary)
|
|
12705
|
+
lastSession = summary.value;
|
|
12706
|
+
} catch {}
|
|
12707
|
+
const allMemories = listMemories({
|
|
12708
|
+
project_id: options.project_id,
|
|
12709
|
+
status: "active",
|
|
12710
|
+
limit: 100
|
|
12711
|
+
});
|
|
12712
|
+
const flagged = allMemories.filter((m) => m.flag && m.flag !== "");
|
|
12713
|
+
if (!profile && memories.length === 0 && !lastSession && flagged.length === 0) {
|
|
12714
|
+
return false;
|
|
12715
|
+
}
|
|
12716
|
+
const briefingText = formatBriefing({
|
|
12717
|
+
profile: profile || undefined,
|
|
12718
|
+
memories: memories.length > 0 ? memories : undefined,
|
|
12719
|
+
lastSession: lastSession || undefined,
|
|
12720
|
+
flagged: flagged.length > 0 ? flagged : undefined,
|
|
12721
|
+
projectName: options.project_name
|
|
12722
|
+
});
|
|
12723
|
+
return await pushRawNotification(briefingText, "session-briefing");
|
|
12724
|
+
} catch {
|
|
12725
|
+
return false;
|
|
12726
|
+
}
|
|
12727
|
+
}
|
|
12728
|
+
|
|
12729
|
+
// src/lib/auto-inject-orchestrator.ts
|
|
12730
|
+
var DEFAULT_CONFIG = {
|
|
12731
|
+
enabled: true,
|
|
12732
|
+
throttle_ms: 30000,
|
|
12733
|
+
debounce_ms: 2000,
|
|
12734
|
+
max_pushes_per_5min: 5,
|
|
12735
|
+
min_similarity: 0.4,
|
|
12736
|
+
session_briefing: true
|
|
12737
|
+
};
|
|
12738
|
+
var _config = { ...DEFAULT_CONFIG };
|
|
12739
|
+
var _running = false;
|
|
12740
|
+
var _sessionId = null;
|
|
12741
|
+
var _heartbeatInterval = null;
|
|
12742
|
+
var _debounceTimer = null;
|
|
12743
|
+
var _lastPushTime = 0;
|
|
12744
|
+
var _pushCount5min = [];
|
|
12745
|
+
var _pendingMessages = [];
|
|
12746
|
+
var _pushHistory = [];
|
|
12747
|
+
var MAX_HISTORY = 20;
|
|
12748
|
+
async function processMessages() {
|
|
12749
|
+
if (!_running || !_config.enabled)
|
|
12750
|
+
return;
|
|
12751
|
+
const now3 = Date.now();
|
|
12752
|
+
if (now3 - _lastPushTime < _config.throttle_ms)
|
|
12753
|
+
return;
|
|
12754
|
+
_pushCount5min = _pushCount5min.filter((p) => now3 - p.ts < 5 * 60 * 1000);
|
|
12755
|
+
if (_pushCount5min.length >= _config.max_pushes_per_5min)
|
|
12756
|
+
return;
|
|
12757
|
+
let bestContext = null;
|
|
12758
|
+
for (const msg of _pendingMessages) {
|
|
12759
|
+
const extracted = extractContext(msg);
|
|
12760
|
+
if (extracted.is_significant && extracted.context_text.length > 10) {
|
|
12761
|
+
bestContext = { context_text: extracted.context_text, tools_mentioned: extracted.tools_mentioned };
|
|
12762
|
+
}
|
|
12763
|
+
}
|
|
12764
|
+
_pendingMessages = [];
|
|
12765
|
+
if (!bestContext)
|
|
12766
|
+
return;
|
|
12767
|
+
try {
|
|
12768
|
+
const memories = await findActivatedMemories(bestContext.context_text, {
|
|
12769
|
+
min_similarity: _config.min_similarity,
|
|
12770
|
+
max_results: 5
|
|
12771
|
+
});
|
|
12772
|
+
if (memories.length === 0)
|
|
12773
|
+
return;
|
|
12774
|
+
const pushed = await pushMemoryNotification(memories, bestContext.context_text);
|
|
12775
|
+
if (pushed) {
|
|
12776
|
+
markAsPushed(memories.map((m) => m.id));
|
|
12777
|
+
_lastPushTime = Date.now();
|
|
12778
|
+
_pushCount5min.push({ ts: Date.now() });
|
|
12779
|
+
_pushHistory.unshift({
|
|
12780
|
+
timestamp: new Date().toISOString(),
|
|
12781
|
+
context: bestContext.context_text.slice(0, 200),
|
|
12782
|
+
memory_count: memories.length
|
|
12783
|
+
});
|
|
12784
|
+
if (_pushHistory.length > MAX_HISTORY)
|
|
12785
|
+
_pushHistory.pop();
|
|
12786
|
+
}
|
|
12787
|
+
} catch {}
|
|
12788
|
+
}
|
|
12789
|
+
function onNewMessage(message) {
|
|
12790
|
+
if (!_running || !_config.enabled)
|
|
12791
|
+
return;
|
|
12792
|
+
_pendingMessages.push(message);
|
|
12793
|
+
if (_debounceTimer)
|
|
12794
|
+
clearTimeout(_debounceTimer);
|
|
12795
|
+
_debounceTimer = setTimeout(() => {
|
|
12796
|
+
processMessages();
|
|
12797
|
+
}, _config.debounce_ms);
|
|
12798
|
+
}
|
|
12799
|
+
async function startAutoInject(options) {
|
|
12800
|
+
const envEnabled = process.env["MEMENTOS_AUTO_INJECT"] === "true";
|
|
12801
|
+
if (!envEnabled && !options.config?.enabled) {
|
|
12802
|
+
return { started: false, session_id: null, watcher_file: null, briefing_pushed: false };
|
|
12803
|
+
}
|
|
12804
|
+
_config = { ...DEFAULT_CONFIG, ...options.config };
|
|
12805
|
+
if (envEnabled)
|
|
12806
|
+
_config.enabled = true;
|
|
12807
|
+
setServerRef(options.server);
|
|
12808
|
+
const cwd = options.cwd || process.cwd();
|
|
12809
|
+
const session = registerSession({
|
|
12810
|
+
mcp_server: "mementos",
|
|
12811
|
+
agent_name: options.agent_name,
|
|
12812
|
+
project_name: options.project_name,
|
|
12813
|
+
cwd,
|
|
12814
|
+
git_root: options.git_root
|
|
12815
|
+
});
|
|
12816
|
+
_sessionId = session.id;
|
|
12817
|
+
_heartbeatInterval = setInterval(() => {
|
|
12818
|
+
if (_sessionId)
|
|
12819
|
+
heartbeatSession(_sessionId);
|
|
12820
|
+
cleanStaleSessions();
|
|
12821
|
+
}, 15000);
|
|
12822
|
+
let briefingPushed = false;
|
|
12823
|
+
if (_config.session_briefing) {
|
|
12824
|
+
try {
|
|
12825
|
+
briefingPushed = await pushSessionBriefing({
|
|
12826
|
+
project_id: options.project_id,
|
|
12827
|
+
project_name: options.project_name,
|
|
12828
|
+
agent_id: options.agent_id
|
|
12829
|
+
});
|
|
12830
|
+
} catch {}
|
|
12831
|
+
}
|
|
12832
|
+
const { sessionFile } = startSessionWatcher(cwd, onNewMessage);
|
|
12833
|
+
_running = true;
|
|
12834
|
+
return {
|
|
12835
|
+
started: true,
|
|
12836
|
+
session_id: _sessionId,
|
|
12837
|
+
watcher_file: sessionFile,
|
|
12838
|
+
briefing_pushed: briefingPushed
|
|
12839
|
+
};
|
|
12840
|
+
}
|
|
12841
|
+
function stopAutoInject() {
|
|
12842
|
+
_running = false;
|
|
12843
|
+
if (_debounceTimer) {
|
|
12844
|
+
clearTimeout(_debounceTimer);
|
|
12845
|
+
_debounceTimer = null;
|
|
12846
|
+
}
|
|
12847
|
+
if (_heartbeatInterval) {
|
|
12848
|
+
clearInterval(_heartbeatInterval);
|
|
12849
|
+
_heartbeatInterval = null;
|
|
12850
|
+
}
|
|
12851
|
+
stopSessionWatcher();
|
|
12852
|
+
resetContext();
|
|
12853
|
+
resetRecentlyPushed();
|
|
12854
|
+
if (_sessionId) {
|
|
12855
|
+
try {
|
|
12856
|
+
unregisterSession(_sessionId);
|
|
12857
|
+
} catch {}
|
|
12858
|
+
_sessionId = null;
|
|
12859
|
+
}
|
|
12860
|
+
_pendingMessages = [];
|
|
12861
|
+
}
|
|
12862
|
+
function getAutoInjectConfig() {
|
|
12863
|
+
return { ..._config };
|
|
12864
|
+
}
|
|
12865
|
+
function updateAutoInjectConfig(updates) {
|
|
12866
|
+
_config = { ..._config, ...updates };
|
|
12867
|
+
return { ..._config };
|
|
12868
|
+
}
|
|
12869
|
+
function getAutoInjectStatus() {
|
|
12870
|
+
const now3 = Date.now();
|
|
12871
|
+
const recent5min = _pushCount5min.filter((p) => now3 - p.ts < 5 * 60 * 1000);
|
|
12872
|
+
const nextAvailable = Math.max(0, _config.throttle_ms - (now3 - _lastPushTime));
|
|
12873
|
+
return {
|
|
12874
|
+
running: _running,
|
|
12875
|
+
config: { ..._config },
|
|
12876
|
+
session_id: _sessionId,
|
|
12877
|
+
watcher: getWatcherStatus(),
|
|
12878
|
+
pushes: {
|
|
12879
|
+
total: _pushHistory.length,
|
|
12880
|
+
last_5min: recent5min.length,
|
|
12881
|
+
recently_pushed_memories: getRecentlyPushedCount(),
|
|
12882
|
+
next_available_in_ms: nextAvailable
|
|
12883
|
+
},
|
|
12884
|
+
history: [..._pushHistory]
|
|
12885
|
+
};
|
|
12886
|
+
}
|
|
12887
|
+
|
|
12888
|
+
// src/lib/asmr/fact-agent.ts
|
|
12889
|
+
init_memories();
|
|
12890
|
+
function escapeFts5Token(token) {
|
|
12891
|
+
return `"${token.replace(/"/g, '""')}"`;
|
|
12892
|
+
}
|
|
12893
|
+
function hasFts5Table2(db) {
|
|
12894
|
+
try {
|
|
12895
|
+
const row = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
|
|
12896
|
+
return !!row;
|
|
12897
|
+
} catch {
|
|
12898
|
+
return false;
|
|
12899
|
+
}
|
|
12900
|
+
}
|
|
12901
|
+
function buildScopeFilter(opts) {
|
|
12902
|
+
const conditions = [
|
|
12903
|
+
"m.status = 'active'",
|
|
12904
|
+
"(m.expires_at IS NULL OR m.expires_at >= datetime('now'))",
|
|
12905
|
+
"m.category IN ('preference', 'fact', 'knowledge')"
|
|
12906
|
+
];
|
|
12907
|
+
const params = [];
|
|
12908
|
+
if (opts.project_id) {
|
|
12909
|
+
conditions.push("m.project_id = ?");
|
|
12910
|
+
params.push(opts.project_id);
|
|
12911
|
+
}
|
|
12912
|
+
if (opts.agent_id) {
|
|
12913
|
+
conditions.push("m.agent_id = ?");
|
|
12914
|
+
params.push(opts.agent_id);
|
|
12915
|
+
}
|
|
12916
|
+
return { conditions, params };
|
|
12917
|
+
}
|
|
12918
|
+
function scoreFactMemory(memory, queryLower) {
|
|
12919
|
+
const keyLower = memory.key.toLowerCase();
|
|
12920
|
+
const valueLower = memory.value.toLowerCase();
|
|
12921
|
+
let score = 0;
|
|
12922
|
+
let matchField = "value";
|
|
12923
|
+
if (keyLower === queryLower) {
|
|
12924
|
+
score = 10;
|
|
12925
|
+
matchField = "key (exact)";
|
|
12926
|
+
} else if (keyLower.includes(queryLower)) {
|
|
12927
|
+
score = 7;
|
|
12928
|
+
matchField = "key (partial)";
|
|
12929
|
+
} else if (valueLower.includes(queryLower)) {
|
|
12930
|
+
score = 3;
|
|
12931
|
+
matchField = "value";
|
|
12932
|
+
}
|
|
12933
|
+
if (score === 0) {
|
|
12934
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
12935
|
+
if (tokens.length > 1) {
|
|
12936
|
+
let tokenHits = 0;
|
|
12937
|
+
for (const token of tokens) {
|
|
12938
|
+
if (keyLower.includes(token))
|
|
12939
|
+
tokenHits += 2;
|
|
12940
|
+
else if (valueLower.includes(token))
|
|
12941
|
+
tokenHits += 1;
|
|
12942
|
+
}
|
|
12943
|
+
if (tokenHits > 0) {
|
|
12944
|
+
score = tokenHits / tokens.length * 5;
|
|
12945
|
+
matchField = "tokens";
|
|
12946
|
+
}
|
|
12947
|
+
}
|
|
12948
|
+
}
|
|
12949
|
+
if (memory.pinned)
|
|
12950
|
+
score *= 1.3;
|
|
12951
|
+
const effectiveImportance = computeDecayScore(memory);
|
|
12952
|
+
score = score * effectiveImportance / 10;
|
|
12953
|
+
if (memory.importance >= 7)
|
|
12954
|
+
score *= 1.2;
|
|
12955
|
+
return { score, matchField };
|
|
12956
|
+
}
|
|
12957
|
+
async function runFactAgent(db, query, opts) {
|
|
12958
|
+
const queryLower = query.toLowerCase().trim();
|
|
12959
|
+
if (!queryLower)
|
|
12960
|
+
return { memories: [], reasoning: "Empty query" };
|
|
12961
|
+
const { conditions, params } = buildScopeFilter(opts);
|
|
12962
|
+
const limit = (opts.max_results ?? 20) * 2;
|
|
12963
|
+
let rows;
|
|
12964
|
+
if (hasFts5Table2(db)) {
|
|
12965
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
12966
|
+
const ftsQuery = tokens.map(escapeFts5Token).join(" ");
|
|
12967
|
+
try {
|
|
12968
|
+
const ftsCondition = `m.rowid IN (SELECT f.rowid FROM memories_fts f WHERE memories_fts MATCH ?)`;
|
|
12969
|
+
const sql = `SELECT m.* FROM memories m WHERE ${ftsCondition} AND ${conditions.join(" AND ")} LIMIT ?`;
|
|
12970
|
+
rows = db.query(sql).all(ftsQuery, ...params, limit);
|
|
12971
|
+
} catch {
|
|
12972
|
+
rows = [];
|
|
12973
|
+
}
|
|
12974
|
+
} else {
|
|
12975
|
+
rows = [];
|
|
12976
|
+
}
|
|
12977
|
+
if (rows.length === 0) {
|
|
12978
|
+
const likePattern = `%${queryLower}%`;
|
|
12979
|
+
const likeSql = `SELECT m.* FROM memories m WHERE (m.key LIKE ? OR m.value LIKE ?) AND ${conditions.join(" AND ")} LIMIT ?`;
|
|
12980
|
+
rows = db.query(likeSql).all(likePattern, likePattern, ...params, limit);
|
|
12981
|
+
}
|
|
12982
|
+
const results = [];
|
|
12983
|
+
for (const row of rows) {
|
|
12984
|
+
const memory = parseMemoryRow(row);
|
|
12985
|
+
const { score, matchField } = scoreFactMemory(memory, queryLower);
|
|
12986
|
+
if (score <= 0)
|
|
12987
|
+
continue;
|
|
12988
|
+
results.push({
|
|
12989
|
+
memory,
|
|
12990
|
+
score,
|
|
12991
|
+
source_agent: "facts",
|
|
12992
|
+
reasoning: `Direct fact match on ${matchField}`,
|
|
12993
|
+
verbatim_excerpt: memory.value
|
|
12994
|
+
});
|
|
12995
|
+
}
|
|
12996
|
+
results.sort((a, b) => b.score - a.score);
|
|
12997
|
+
const trimmed = results.slice(0, opts.max_results ?? 20);
|
|
12998
|
+
return {
|
|
12999
|
+
memories: trimmed,
|
|
13000
|
+
reasoning: `Fact agent searched ${rows.length} candidate memories, returned ${trimmed.length} factual matches`
|
|
13001
|
+
};
|
|
13002
|
+
}
|
|
13003
|
+
|
|
13004
|
+
// src/lib/asmr/context-agent.ts
|
|
13005
|
+
init_memories();
|
|
13006
|
+
init_entities();
|
|
13007
|
+
init_entity_memories();
|
|
13008
|
+
function deduplicateCandidates(candidates) {
|
|
13009
|
+
const seen = new Map;
|
|
13010
|
+
for (const c of candidates) {
|
|
13011
|
+
const existing = seen.get(c.memory.id);
|
|
13012
|
+
if (!existing) {
|
|
13013
|
+
seen.set(c.memory.id, c);
|
|
13014
|
+
} else {
|
|
13015
|
+
const existingTotal = existing.semanticScore + existing.entityLinkStrength;
|
|
13016
|
+
const currentTotal = c.semanticScore + c.entityLinkStrength;
|
|
13017
|
+
if (currentTotal > existingTotal) {
|
|
13018
|
+
seen.set(c.memory.id, c);
|
|
13019
|
+
}
|
|
13020
|
+
}
|
|
13021
|
+
}
|
|
13022
|
+
return Array.from(seen.values());
|
|
13023
|
+
}
|
|
13024
|
+
async function runContextAgent(db, query, opts) {
|
|
13025
|
+
const queryLower = query.toLowerCase().trim();
|
|
13026
|
+
if (!queryLower)
|
|
13027
|
+
return { memories: [], reasoning: "Empty query" };
|
|
13028
|
+
const maxResults = opts.max_results ?? 20;
|
|
13029
|
+
const candidates = [];
|
|
13030
|
+
let semanticCount = 0;
|
|
13031
|
+
let entityQueryCount = 0;
|
|
13032
|
+
try {
|
|
13033
|
+
const semanticResults = await semanticSearch(query, {
|
|
13034
|
+
threshold: 0.3,
|
|
13035
|
+
limit: maxResults * 2,
|
|
13036
|
+
project_id: opts.project_id,
|
|
13037
|
+
agent_id: opts.agent_id
|
|
13038
|
+
}, db);
|
|
13039
|
+
for (const sr of semanticResults) {
|
|
13040
|
+
candidates.push({
|
|
13041
|
+
memory: sr.memory,
|
|
13042
|
+
semanticScore: sr.score,
|
|
13043
|
+
entityLinkStrength: 0,
|
|
13044
|
+
entityName: null,
|
|
13045
|
+
entityRelation: null
|
|
13046
|
+
});
|
|
13047
|
+
}
|
|
13048
|
+
semanticCount = semanticResults.length;
|
|
13049
|
+
} catch {}
|
|
13050
|
+
const topSemantic = candidates.slice(0, 10);
|
|
13051
|
+
const entityMemoryIds = new Set;
|
|
13052
|
+
for (const candidate of topSemantic) {
|
|
13053
|
+
try {
|
|
13054
|
+
const entities = getEntitiesForMemory(candidate.memory.id, db);
|
|
13055
|
+
for (const entity of entities) {
|
|
13056
|
+
const linkedMemories = getMemoriesForEntity(entity.id, db);
|
|
13057
|
+
for (const mem of linkedMemories) {
|
|
13058
|
+
if (entityMemoryIds.has(mem.id))
|
|
13059
|
+
continue;
|
|
13060
|
+
entityMemoryIds.add(mem.id);
|
|
13061
|
+
candidates.push({
|
|
13062
|
+
memory: mem,
|
|
13063
|
+
semanticScore: 0,
|
|
13064
|
+
entityLinkStrength: 1,
|
|
13065
|
+
entityName: entity.name,
|
|
13066
|
+
entityRelation: entity.type
|
|
13067
|
+
});
|
|
13068
|
+
}
|
|
13069
|
+
}
|
|
13070
|
+
} catch {}
|
|
13071
|
+
}
|
|
13072
|
+
try {
|
|
13073
|
+
const matchingEntities = listEntities({ search: query, limit: 10, project_id: opts.project_id }, db);
|
|
13074
|
+
const exactEntity = getEntityByName(query, undefined, opts.project_id, db);
|
|
13075
|
+
if (exactEntity && !matchingEntities.find((e) => e.id === exactEntity.id)) {
|
|
13076
|
+
matchingEntities.unshift(exactEntity);
|
|
13077
|
+
}
|
|
13078
|
+
for (const entity of matchingEntities) {
|
|
13079
|
+
entityQueryCount++;
|
|
13080
|
+
const linkedMemories = getMemoriesForEntity(entity.id, db);
|
|
13081
|
+
for (const mem of linkedMemories) {
|
|
13082
|
+
if (entityMemoryIds.has(mem.id))
|
|
13083
|
+
continue;
|
|
13084
|
+
entityMemoryIds.add(mem.id);
|
|
13085
|
+
const nameMatch = entity.name.toLowerCase() === queryLower ? 1 : 0.7;
|
|
13086
|
+
candidates.push({
|
|
13087
|
+
memory: mem,
|
|
13088
|
+
semanticScore: 0,
|
|
13089
|
+
entityLinkStrength: nameMatch,
|
|
13090
|
+
entityName: entity.name,
|
|
13091
|
+
entityRelation: entity.type
|
|
13092
|
+
});
|
|
13093
|
+
}
|
|
13094
|
+
}
|
|
13095
|
+
} catch {}
|
|
13096
|
+
const unique = deduplicateCandidates(candidates);
|
|
13097
|
+
const results = [];
|
|
13098
|
+
for (const c of unique) {
|
|
13099
|
+
if (opts.project_id && c.memory.project_id && c.memory.project_id !== opts.project_id)
|
|
13100
|
+
continue;
|
|
13101
|
+
if (c.memory.status !== "active")
|
|
13102
|
+
continue;
|
|
13103
|
+
const effectiveImportance = computeDecayScore(c.memory) / 10;
|
|
13104
|
+
const score = c.semanticScore * 0.4 + c.entityLinkStrength * 0.3 + effectiveImportance * 0.3;
|
|
13105
|
+
let reasoning;
|
|
13106
|
+
if (c.entityName) {
|
|
13107
|
+
reasoning = `Found via entity ${c.entityName} (relation: ${c.entityRelation})`;
|
|
13108
|
+
} else {
|
|
13109
|
+
reasoning = `Semantic similarity match (score: ${c.semanticScore.toFixed(3)})`;
|
|
13110
|
+
}
|
|
13111
|
+
results.push({
|
|
13112
|
+
memory: c.memory,
|
|
13113
|
+
score,
|
|
13114
|
+
source_agent: "context",
|
|
13115
|
+
reasoning,
|
|
13116
|
+
verbatim_excerpt: c.memory.value
|
|
13117
|
+
});
|
|
13118
|
+
}
|
|
13119
|
+
results.sort((a, b) => b.score - a.score);
|
|
13120
|
+
const trimmed = results.slice(0, maxResults);
|
|
13121
|
+
return {
|
|
13122
|
+
memories: trimmed,
|
|
13123
|
+
reasoning: `Context agent found ${semanticCount} semantic matches, traversed ${entityQueryCount} entities, returned ${trimmed.length} contextual results`
|
|
13124
|
+
};
|
|
13125
|
+
}
|
|
13126
|
+
|
|
13127
|
+
// src/lib/asmr/temporal-agent.ts
|
|
13128
|
+
init_memories();
|
|
13129
|
+
function hasFts5Table3(db) {
|
|
13130
|
+
try {
|
|
13131
|
+
const row = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
|
|
13132
|
+
return !!row;
|
|
13133
|
+
} catch {
|
|
13134
|
+
return false;
|
|
13135
|
+
}
|
|
13136
|
+
}
|
|
13137
|
+
function escapeFts5Token2(token) {
|
|
13138
|
+
return `"${token.replace(/"/g, '""')}"`;
|
|
13139
|
+
}
|
|
13140
|
+
function queryRelevance(memory, queryLower) {
|
|
13141
|
+
const keyLower = memory.key.toLowerCase();
|
|
13142
|
+
const valueLower = memory.value.toLowerCase();
|
|
13143
|
+
if (keyLower === queryLower)
|
|
13144
|
+
return 1;
|
|
13145
|
+
if (keyLower.includes(queryLower))
|
|
13146
|
+
return 0.8;
|
|
13147
|
+
if (valueLower.includes(queryLower))
|
|
13148
|
+
return 0.5;
|
|
13149
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
13150
|
+
if (tokens.length > 1) {
|
|
13151
|
+
let hits = 0;
|
|
13152
|
+
for (const t of tokens) {
|
|
13153
|
+
if (keyLower.includes(t) || valueLower.includes(t))
|
|
13154
|
+
hits++;
|
|
13155
|
+
}
|
|
13156
|
+
return hits / tokens.length * 0.6;
|
|
13157
|
+
}
|
|
13158
|
+
return 0.1;
|
|
13159
|
+
}
|
|
13160
|
+
function formatDate(iso) {
|
|
13161
|
+
if (!iso)
|
|
13162
|
+
return "unknown";
|
|
13163
|
+
return iso.slice(0, 10);
|
|
13164
|
+
}
|
|
13165
|
+
async function runTemporalAgent(db, query, opts) {
|
|
13166
|
+
const queryLower = query.toLowerCase().trim();
|
|
13167
|
+
if (!queryLower)
|
|
13168
|
+
return { memories: [], reasoning: "Empty query" };
|
|
13169
|
+
const maxResults = opts.max_results ?? 20;
|
|
13170
|
+
const limit = maxResults * 3;
|
|
13171
|
+
const conditions = [
|
|
13172
|
+
"(m.expires_at IS NULL OR m.expires_at >= datetime('now'))"
|
|
13173
|
+
];
|
|
13174
|
+
const params = [];
|
|
13175
|
+
if (opts.project_id) {
|
|
13176
|
+
conditions.push("m.project_id = ?");
|
|
13177
|
+
params.push(opts.project_id);
|
|
13178
|
+
}
|
|
13179
|
+
if (opts.agent_id) {
|
|
13180
|
+
conditions.push("m.agent_id = ?");
|
|
13181
|
+
params.push(opts.agent_id);
|
|
13182
|
+
}
|
|
13183
|
+
let temporalRows = [];
|
|
13184
|
+
const temporalCondition = "(m.valid_from IS NOT NULL OR m.valid_until IS NOT NULL)";
|
|
13185
|
+
if (hasFts5Table3(db)) {
|
|
13186
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
13187
|
+
const ftsQuery = tokens.map(escapeFts5Token2).join(" ");
|
|
13188
|
+
try {
|
|
13189
|
+
const ftsCondition = `m.rowid IN (SELECT f.rowid FROM memories_fts f WHERE memories_fts MATCH ?)`;
|
|
13190
|
+
const sql = `SELECT m.* FROM memories m WHERE ${ftsCondition} AND ${temporalCondition} AND ${conditions.join(" AND ")} ORDER BY m.valid_from DESC NULLS LAST LIMIT ?`;
|
|
13191
|
+
temporalRows = db.query(sql).all(ftsQuery, ...params, limit);
|
|
13192
|
+
} catch {
|
|
13193
|
+
temporalRows = [];
|
|
13194
|
+
}
|
|
13195
|
+
}
|
|
13196
|
+
if (temporalRows.length === 0) {
|
|
13197
|
+
const likePattern = `%${queryLower}%`;
|
|
13198
|
+
const sql = `SELECT m.* FROM memories m WHERE (m.key LIKE ? OR m.value LIKE ?) AND ${temporalCondition} AND ${conditions.join(" AND ")} ORDER BY m.valid_from DESC NULLS LAST LIMIT ?`;
|
|
13199
|
+
temporalRows = db.query(sql).all(likePattern, likePattern, ...params, limit);
|
|
13200
|
+
}
|
|
13201
|
+
let supersededRows = [];
|
|
13202
|
+
try {
|
|
13203
|
+
const likePattern = `%${queryLower}%`;
|
|
13204
|
+
const sql = `SELECT m.* FROM memories m WHERE (m.key LIKE ? OR m.value LIKE ?) AND (m.status = 'archived' OR m.valid_until IS NOT NULL) ${conditions.length > 0 ? "AND " + conditions.join(" AND ") : ""} ORDER BY m.updated_at DESC LIMIT ?`;
|
|
13205
|
+
supersededRows = db.query(sql).all(likePattern, likePattern, ...params, limit);
|
|
13206
|
+
} catch {}
|
|
13207
|
+
const seen = new Map;
|
|
13208
|
+
const allRows = [...temporalRows, ...supersededRows];
|
|
13209
|
+
for (const row of allRows) {
|
|
13210
|
+
const memory = parseMemoryRow(row);
|
|
13211
|
+
if (!seen.has(memory.id)) {
|
|
13212
|
+
seen.set(memory.id, memory);
|
|
13213
|
+
}
|
|
13214
|
+
}
|
|
13215
|
+
const memories = Array.from(seen.values());
|
|
13216
|
+
const timeline = [];
|
|
13217
|
+
const results = [];
|
|
13218
|
+
for (const memory of memories) {
|
|
13219
|
+
const hasTemporal = memory.valid_from !== null || memory.valid_until !== null;
|
|
13220
|
+
const isSuperseded = memory.status === "archived";
|
|
13221
|
+
const relevance = queryRelevance(memory, queryLower);
|
|
13222
|
+
let versions = [];
|
|
13223
|
+
let newerVersionNote = "";
|
|
13224
|
+
try {
|
|
13225
|
+
versions = getMemoryVersions(memory.id, db);
|
|
13226
|
+
if (versions.length > 1) {
|
|
13227
|
+
const latest = versions[versions.length - 1];
|
|
13228
|
+
if (latest.version > memory.version) {
|
|
13229
|
+
newerVersionNote = `, superseded at version ${latest.version}`;
|
|
13230
|
+
}
|
|
13231
|
+
}
|
|
13232
|
+
} catch {}
|
|
13233
|
+
if (hasTemporal || isSuperseded) {
|
|
13234
|
+
const from = formatDate(memory.valid_from);
|
|
13235
|
+
const until = formatDate(memory.valid_until);
|
|
13236
|
+
const status = isSuperseded ? " [superseded]" : "";
|
|
13237
|
+
timeline.push(`${from} - ${until}: ${memory.key}${status}`);
|
|
13238
|
+
}
|
|
13239
|
+
const recencyMs = memory.valid_from ? Date.now() - new Date(memory.valid_from).getTime() : Date.now() - new Date(memory.created_at).getTime();
|
|
13240
|
+
const daysSince = recencyMs / (1000 * 60 * 60 * 24);
|
|
13241
|
+
const recencyScore = Math.max(0, 1 - daysSince / 365);
|
|
13242
|
+
const temporalBonus = hasTemporal ? 1 : 0.3;
|
|
13243
|
+
const effectiveImportance = computeDecayScore(memory) / 10;
|
|
13244
|
+
const score = recencyScore * 0.4 + relevance * 0.3 + temporalBonus * 0.3;
|
|
13245
|
+
const finalScore = score * effectiveImportance;
|
|
13246
|
+
let reasoning;
|
|
13247
|
+
if (isSuperseded) {
|
|
13248
|
+
reasoning = `Superseded memory from ${formatDate(memory.valid_from)}${newerVersionNote}`;
|
|
13249
|
+
} else if (memory.valid_until && new Date(memory.valid_until) < new Date) {
|
|
13250
|
+
reasoning = `Expired temporal fact (valid ${formatDate(memory.valid_from)} to ${formatDate(memory.valid_until)})${newerVersionNote}`;
|
|
13251
|
+
} else if (hasTemporal) {
|
|
13252
|
+
reasoning = `Current as of ${formatDate(memory.valid_from)}${newerVersionNote}`;
|
|
13253
|
+
} else {
|
|
13254
|
+
reasoning = `Historical record from ${formatDate(memory.created_at)}${newerVersionNote}`;
|
|
13255
|
+
}
|
|
13256
|
+
results.push({
|
|
13257
|
+
memory,
|
|
13258
|
+
score: finalScore,
|
|
13259
|
+
source_agent: "temporal",
|
|
13260
|
+
reasoning,
|
|
13261
|
+
verbatim_excerpt: memory.value
|
|
13262
|
+
});
|
|
13263
|
+
}
|
|
13264
|
+
timeline.sort();
|
|
13265
|
+
results.sort((a, b) => b.score - a.score);
|
|
13266
|
+
const trimmed = results.slice(0, maxResults);
|
|
13267
|
+
return {
|
|
13268
|
+
memories: trimmed,
|
|
13269
|
+
reasoning: `Temporal agent found ${temporalRows.length} temporal memories and ${supersededRows.length} superseded records, built ${timeline.length}-entry timeline`
|
|
13270
|
+
};
|
|
13271
|
+
}
|
|
13272
|
+
|
|
13273
|
+
// src/lib/asmr/orchestrator.ts
|
|
13274
|
+
var DEFAULT_MAX_RESULTS = 20;
|
|
13275
|
+
function mergeResults(agentResults) {
|
|
13276
|
+
const byId = new Map;
|
|
13277
|
+
for (const { agent, result } of agentResults) {
|
|
13278
|
+
for (const mem of result.memories) {
|
|
13279
|
+
const existing = byId.get(mem.memory.id);
|
|
13280
|
+
if (!existing) {
|
|
13281
|
+
byId.set(mem.memory.id, {
|
|
13282
|
+
best: mem,
|
|
13283
|
+
sources: new Set([agent]),
|
|
13284
|
+
maxScore: mem.score
|
|
13285
|
+
});
|
|
13286
|
+
} else {
|
|
13287
|
+
existing.sources.add(agent);
|
|
13288
|
+
if (mem.score > existing.maxScore) {
|
|
13289
|
+
existing.best = mem;
|
|
13290
|
+
existing.maxScore = mem.score;
|
|
13291
|
+
}
|
|
13292
|
+
}
|
|
13293
|
+
}
|
|
13294
|
+
}
|
|
13295
|
+
const merged = [];
|
|
13296
|
+
for (const [, entry] of byId) {
|
|
13297
|
+
const multiAgentBoost = entry.sources.size > 1 ? 1.5 : 1;
|
|
13298
|
+
merged.push({
|
|
13299
|
+
...entry.best,
|
|
13300
|
+
score: entry.maxScore * multiAgentBoost
|
|
13301
|
+
});
|
|
13302
|
+
}
|
|
13303
|
+
merged.sort((a, b) => b.score - a.score);
|
|
13304
|
+
return merged;
|
|
13305
|
+
}
|
|
13306
|
+
function extractFacts(factResult) {
|
|
13307
|
+
const facts = [];
|
|
13308
|
+
const seen = new Set;
|
|
13309
|
+
for (const mem of factResult.memories) {
|
|
13310
|
+
const statement = `${mem.memory.key}: ${mem.memory.value}`;
|
|
13311
|
+
if (!seen.has(mem.memory.id)) {
|
|
13312
|
+
seen.add(mem.memory.id);
|
|
13313
|
+
facts.push(statement);
|
|
13314
|
+
}
|
|
13315
|
+
}
|
|
13316
|
+
return facts;
|
|
13317
|
+
}
|
|
13318
|
+
function extractTimeline(temporalResult) {
|
|
13319
|
+
const entries = [];
|
|
13320
|
+
const seen = new Set;
|
|
13321
|
+
for (const mem of temporalResult.memories) {
|
|
13322
|
+
if (seen.has(mem.memory.id))
|
|
13323
|
+
continue;
|
|
13324
|
+
seen.add(mem.memory.id);
|
|
13325
|
+
const date = mem.memory.valid_from ?? mem.memory.created_at;
|
|
13326
|
+
const status = mem.memory.status === "archived" ? " [superseded]" : "";
|
|
13327
|
+
entries.push({
|
|
13328
|
+
date,
|
|
13329
|
+
label: `${date.slice(0, 10)}: ${mem.memory.key}${status}`
|
|
13330
|
+
});
|
|
13331
|
+
}
|
|
13332
|
+
entries.sort((a, b) => a.date.localeCompare(b.date));
|
|
13333
|
+
return entries.map((e) => e.label);
|
|
13334
|
+
}
|
|
13335
|
+
async function asmrRecall(db, query, opts) {
|
|
13336
|
+
const options = {
|
|
13337
|
+
max_results: DEFAULT_MAX_RESULTS,
|
|
13338
|
+
include_reasoning: true,
|
|
13339
|
+
...opts
|
|
13340
|
+
};
|
|
13341
|
+
const start = performance.now();
|
|
13342
|
+
const [factResult, contextResult, temporalResult] = await Promise.all([
|
|
13343
|
+
runFactAgent(db, query, options),
|
|
13344
|
+
runContextAgent(db, query, options),
|
|
13345
|
+
runTemporalAgent(db, query, options)
|
|
13346
|
+
]);
|
|
13347
|
+
const agentResults = [
|
|
13348
|
+
{ agent: "facts", result: factResult },
|
|
13349
|
+
{ agent: "context", result: contextResult },
|
|
13350
|
+
{ agent: "temporal", result: temporalResult }
|
|
13351
|
+
];
|
|
13352
|
+
const merged = mergeResults(agentResults);
|
|
13353
|
+
const trimmed = merged.slice(0, options.max_results ?? DEFAULT_MAX_RESULTS);
|
|
13354
|
+
const facts = extractFacts(factResult);
|
|
13355
|
+
const timeline = extractTimeline(temporalResult);
|
|
13356
|
+
const agentsUsed = [];
|
|
13357
|
+
if (factResult.memories.length > 0)
|
|
13358
|
+
agentsUsed.push("facts");
|
|
13359
|
+
if (contextResult.memories.length > 0)
|
|
13360
|
+
agentsUsed.push("context");
|
|
13361
|
+
if (temporalResult.memories.length > 0)
|
|
13362
|
+
agentsUsed.push("temporal");
|
|
13363
|
+
const reasoningParts = [
|
|
13364
|
+
factResult.reasoning,
|
|
13365
|
+
contextResult.reasoning,
|
|
13366
|
+
temporalResult.reasoning
|
|
13367
|
+
].filter(Boolean);
|
|
13368
|
+
const duration = Math.round(performance.now() - start);
|
|
13369
|
+
return {
|
|
13370
|
+
memories: trimmed,
|
|
13371
|
+
facts,
|
|
13372
|
+
timeline,
|
|
13373
|
+
reasoning: reasoningParts.join(". "),
|
|
13374
|
+
agents_used: agentsUsed,
|
|
13375
|
+
duration_ms: duration
|
|
13376
|
+
};
|
|
13377
|
+
}
|
|
13378
|
+
// src/lib/asmr/ensemble.ts
|
|
13379
|
+
var DEFAULT_MODEL = "gpt-4.1-mini";
|
|
13380
|
+
var ESCALATION_MODEL = "gpt-4.1";
|
|
13381
|
+
var CONSENSUS_THRESHOLD = 0.7;
|
|
13382
|
+
var VARIANT_PROMPTS = [
|
|
13383
|
+
"Focus on the most recent and up-to-date facts. If there are contradictions, prefer the newer information.",
|
|
13384
|
+
"Focus on factual accuracy. Only use information explicitly stated in the memories. Do not infer.",
|
|
13385
|
+
"Focus on relationships and connections between entities. Consider how different facts relate to each other.",
|
|
13386
|
+
"Focus on the chronological timeline. Consider when events happened and how the situation evolved over time.",
|
|
13387
|
+
"Synthesize all available information into a comprehensive answer. Consider all perspectives and resolve contradictions.",
|
|
13388
|
+
"Focus on preferences and personal information. Prioritize what the user has explicitly stated they prefer or want.",
|
|
13389
|
+
"Be skeptical. Look for contradictions, outdated information, or facts that might no longer be true.",
|
|
13390
|
+
"Focus on the most frequently mentioned and highly-rated facts. These are likely the most important."
|
|
13391
|
+
];
|
|
13392
|
+
function buildContext(result) {
|
|
13393
|
+
const sections = [];
|
|
13394
|
+
if (result.facts.length > 0) {
|
|
13395
|
+
sections.push(`Known Facts:
|
|
13396
|
+
` + result.facts.map((f) => `- ${f}`).join(`
|
|
13397
|
+
`));
|
|
13398
|
+
}
|
|
13399
|
+
if (result.timeline.length > 0) {
|
|
13400
|
+
sections.push(`Timeline:
|
|
13401
|
+
` + result.timeline.map((t) => `- ${t}`).join(`
|
|
13402
|
+
`));
|
|
13403
|
+
}
|
|
13404
|
+
const memories = result.memories.slice(0, 30);
|
|
13405
|
+
if (memories.length > 0) {
|
|
13406
|
+
sections.push(`Memory Excerpts:
|
|
13407
|
+
` + memories.map((m) => `[${m.source_agent}] ${m.memory.key}: ${m.verbatim_excerpt.slice(0, 500)}`).join(`
|
|
13408
|
+
|
|
13409
|
+
`));
|
|
13410
|
+
}
|
|
13411
|
+
return sections.join(`
|
|
13412
|
+
|
|
13413
|
+
---
|
|
13414
|
+
|
|
13415
|
+
`);
|
|
13416
|
+
}
|
|
13417
|
+
async function callLLM(systemPrompt, userPrompt, model) {
|
|
13418
|
+
const apiKey = process.env["OPENAI_API_KEY"] ?? process.env["LLM_API_KEY"];
|
|
13419
|
+
if (!apiKey)
|
|
13420
|
+
throw new Error("No API key for ensemble LLM calls");
|
|
13421
|
+
const baseUrl = process.env["LLM_BASE_URL"] ?? "https://api.openai.com/v1";
|
|
13422
|
+
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
13423
|
+
method: "POST",
|
|
13424
|
+
headers: {
|
|
13425
|
+
"Content-Type": "application/json",
|
|
13426
|
+
Authorization: `Bearer ${apiKey}`
|
|
13427
|
+
},
|
|
13428
|
+
body: JSON.stringify({
|
|
13429
|
+
model,
|
|
13430
|
+
messages: [
|
|
13431
|
+
{ role: "system", content: systemPrompt },
|
|
13432
|
+
{ role: "user", content: userPrompt }
|
|
13433
|
+
],
|
|
13434
|
+
temperature: 0.3,
|
|
13435
|
+
max_tokens: 1024
|
|
13436
|
+
}),
|
|
13437
|
+
signal: AbortSignal.timeout(30000)
|
|
13438
|
+
});
|
|
13439
|
+
if (!res.ok)
|
|
13440
|
+
throw new Error(`LLM API ${res.status}`);
|
|
13441
|
+
const data = await res.json();
|
|
13442
|
+
return data.choices[0]?.message?.content ?? "";
|
|
13443
|
+
}
|
|
13444
|
+
function findMajority(answers) {
|
|
13445
|
+
if (answers.length === 0)
|
|
13446
|
+
return null;
|
|
13447
|
+
const normalized = answers.map((a) => a.toLowerCase().trim().replace(/[.!?]+$/, ""));
|
|
13448
|
+
const counts = new Map;
|
|
13449
|
+
for (let i = 0;i < normalized.length; i++) {
|
|
13450
|
+
const key = normalized[i];
|
|
13451
|
+
const existing = counts.get(key);
|
|
13452
|
+
if (existing) {
|
|
13453
|
+
existing.count++;
|
|
13454
|
+
} else {
|
|
13455
|
+
counts.set(key, { original: answers[i], count: 1 });
|
|
13456
|
+
}
|
|
13457
|
+
}
|
|
13458
|
+
let best = null;
|
|
13459
|
+
for (const entry of counts.values()) {
|
|
13460
|
+
if (!best || entry.count > best.count) {
|
|
13461
|
+
best = entry;
|
|
13462
|
+
}
|
|
13463
|
+
}
|
|
13464
|
+
return best ? { answer: best.original, count: best.count, total: answers.length } : null;
|
|
13465
|
+
}
|
|
13466
|
+
async function ensembleAnswer(context, query, opts) {
|
|
13467
|
+
const numVariants = Math.min(opts?.variants ?? 5, VARIANT_PROMPTS.length);
|
|
13468
|
+
const model = opts?.model ?? DEFAULT_MODEL;
|
|
13469
|
+
const escalationModel = opts?.escalation_model ?? ESCALATION_MODEL;
|
|
13470
|
+
const threshold = opts?.consensus_threshold ?? CONSENSUS_THRESHOLD;
|
|
13471
|
+
const contextStr = buildContext(context);
|
|
13472
|
+
if (!contextStr.trim()) {
|
|
13473
|
+
return {
|
|
13474
|
+
answer: "No relevant information found in memory.",
|
|
13475
|
+
confidence: 0,
|
|
13476
|
+
reasoning: "No context available from ASMR search agents.",
|
|
13477
|
+
variants_used: 0,
|
|
13478
|
+
consensus_reached: false,
|
|
13479
|
+
escalated: false
|
|
13480
|
+
};
|
|
13481
|
+
}
|
|
13482
|
+
const variantPromises = VARIANT_PROMPTS.slice(0, numVariants).map((variantInstruction) => callLLM(`You are a memory retrieval assistant. Answer the user's question based ONLY on the provided context.
|
|
13483
|
+
|
|
13484
|
+
${variantInstruction}
|
|
13485
|
+
|
|
13486
|
+
Context:
|
|
13487
|
+
${contextStr}`, query, model).catch(() => null));
|
|
13488
|
+
const results = await Promise.all(variantPromises);
|
|
13489
|
+
const answers = results.filter((r) => r !== null && r.trim().length > 0);
|
|
13490
|
+
if (answers.length === 0) {
|
|
13491
|
+
return {
|
|
13492
|
+
answer: "Unable to generate an answer from the available context.",
|
|
13493
|
+
confidence: 0,
|
|
13494
|
+
reasoning: "All variant prompts failed.",
|
|
13495
|
+
variants_used: numVariants,
|
|
13496
|
+
consensus_reached: false,
|
|
13497
|
+
escalated: false
|
|
13498
|
+
};
|
|
13499
|
+
}
|
|
13500
|
+
const majority = findMajority(answers);
|
|
13501
|
+
if (majority && majority.count / majority.total >= threshold) {
|
|
13502
|
+
return {
|
|
13503
|
+
answer: majority.answer,
|
|
13504
|
+
confidence: majority.count / majority.total,
|
|
13505
|
+
reasoning: `${majority.count}/${majority.total} variants agreed on this answer.`,
|
|
13506
|
+
variants_used: numVariants,
|
|
13507
|
+
consensus_reached: true,
|
|
13508
|
+
escalated: false
|
|
13509
|
+
};
|
|
13510
|
+
}
|
|
13511
|
+
try {
|
|
13512
|
+
const escalationPrompt = `Multiple analysis variants produced different answers to the question. Review all of them and produce the single best answer.
|
|
13513
|
+
|
|
13514
|
+
Question: ${query}
|
|
13515
|
+
|
|
13516
|
+
Context:
|
|
13517
|
+
${contextStr}
|
|
13518
|
+
|
|
13519
|
+
Variant answers:
|
|
13520
|
+
${answers.map((a, i) => `Variant ${i + 1}: ${a}`).join(`
|
|
13521
|
+
|
|
13522
|
+
`)}
|
|
13523
|
+
|
|
13524
|
+
Produce the single most accurate answer based on the context. If there are genuine contradictions in the data, acknowledge them.`;
|
|
13525
|
+
const finalAnswer = await callLLM("You are an expert arbitrator. Given multiple candidate answers and the original context, determine the most accurate answer.", escalationPrompt, escalationModel);
|
|
13526
|
+
return {
|
|
13527
|
+
answer: finalAnswer,
|
|
13528
|
+
confidence: 0.6,
|
|
13529
|
+
reasoning: `No consensus (best: ${majority?.count ?? 0}/${answers.length}). Escalated to ${escalationModel} for conflict resolution.`,
|
|
13530
|
+
variants_used: numVariants,
|
|
13531
|
+
consensus_reached: false,
|
|
13532
|
+
escalated: true
|
|
13533
|
+
};
|
|
13534
|
+
} catch {
|
|
13535
|
+
return {
|
|
13536
|
+
answer: majority?.answer ?? answers[0],
|
|
13537
|
+
confidence: (majority?.count ?? 1) / answers.length,
|
|
13538
|
+
reasoning: `No consensus, escalation failed. Returning best available answer (${majority?.count ?? 1}/${answers.length} agreement).`,
|
|
13539
|
+
variants_used: numVariants,
|
|
13540
|
+
consensus_reached: false,
|
|
13541
|
+
escalated: false
|
|
13542
|
+
};
|
|
13543
|
+
}
|
|
13544
|
+
}
|
|
10866
13545
|
// src/mcp/index.ts
|
|
10867
13546
|
init_auto_memory();
|
|
10868
13547
|
init_registry();
|
|
10869
13548
|
import { createRequire } from "module";
|
|
10870
13549
|
var _require = createRequire(import.meta.url);
|
|
10871
13550
|
var _pkg = _require("../../package.json");
|
|
13551
|
+
var mcpServer = null;
|
|
10872
13552
|
var server = new McpServer({
|
|
10873
13553
|
name: "mementos",
|
|
10874
13554
|
version: _pkg.version
|
|
13555
|
+
}, {
|
|
13556
|
+
capabilities: {
|
|
13557
|
+
experimental: { "claude/channel": {} }
|
|
13558
|
+
},
|
|
13559
|
+
instructions: `Mementos is the persistent memory layer for AI agents. It stores, searches, and manages memories across sessions and projects.
|
|
13560
|
+
|
|
13561
|
+
When running with --dangerously-load-development-channels, mementos will proactively push relevant memories into your conversation via channel notifications. These appear as <channel source="mementos"> tags. They contain memories activated by your current task context \u2014 use them to inform your work. You don't need to call memory_inject when auto-inject is active.`
|
|
10875
13562
|
});
|
|
13563
|
+
mcpServer = server;
|
|
10876
13564
|
var _autoProjectInitialized = false;
|
|
10877
13565
|
function ensureAutoProject() {
|
|
10878
13566
|
if (_autoProjectInitialized)
|
|
@@ -10891,8 +13579,20 @@ function formatError(error) {
|
|
|
10891
13579
|
return `Duplicate: ${error.message}`;
|
|
10892
13580
|
if (error instanceof InvalidScopeError)
|
|
10893
13581
|
return `Invalid scope: ${error.message}`;
|
|
10894
|
-
if (error instanceof Error)
|
|
10895
|
-
|
|
13582
|
+
if (error instanceof Error) {
|
|
13583
|
+
const msg = error.message;
|
|
13584
|
+
if (msg.includes("UNIQUE constraint failed: projects.")) {
|
|
13585
|
+
return `Project already registered at this path. Use list_projects to find it.`;
|
|
13586
|
+
}
|
|
13587
|
+
if (msg.includes("UNIQUE constraint failed")) {
|
|
13588
|
+
const table = msg.match(/UNIQUE constraint failed: (\w+)\./)?.[1] ?? "unknown";
|
|
13589
|
+
return `Duplicate entry in ${table}. The record already exists \u2014 use the list or get tool to find it.`;
|
|
13590
|
+
}
|
|
13591
|
+
if (msg.includes("FOREIGN KEY constraint failed")) {
|
|
13592
|
+
return `Referenced record not found. Check that the project_id or agent_id exists.`;
|
|
13593
|
+
}
|
|
13594
|
+
return msg;
|
|
13595
|
+
}
|
|
10896
13596
|
return String(error);
|
|
10897
13597
|
}
|
|
10898
13598
|
function resolveId(partialId, table = "memories") {
|
|
@@ -10936,6 +13636,32 @@ function formatMemory(m) {
|
|
|
10936
13636
|
return parts.join(`
|
|
10937
13637
|
`);
|
|
10938
13638
|
}
|
|
13639
|
+
function formatAsmrResult(result, query) {
|
|
13640
|
+
const sections = [];
|
|
13641
|
+
sections.push(`[deep] ASMR recall for "${query}" (${result.duration_ms}ms, agents: ${result.agents_used.join(", ")})`);
|
|
13642
|
+
if (result.memories.length > 0) {
|
|
13643
|
+
const memLines = result.memories.map((m, i) => `${i + 1}. [${m.source_agent}] [score:${m.score.toFixed(3)}] [${m.memory.scope}/${m.memory.category}] ${m.memory.key} = ${m.memory.value.slice(0, 120)}${m.memory.value.length > 120 ? "..." : ""}`);
|
|
13644
|
+
sections.push(`Memories (${result.memories.length}):
|
|
13645
|
+
${memLines.join(`
|
|
13646
|
+
`)}`);
|
|
13647
|
+
}
|
|
13648
|
+
if (result.facts.length > 0) {
|
|
13649
|
+
sections.push(`Facts:
|
|
13650
|
+
${result.facts.map((f) => `- ${f}`).join(`
|
|
13651
|
+
`)}`);
|
|
13652
|
+
}
|
|
13653
|
+
if (result.timeline.length > 0) {
|
|
13654
|
+
sections.push(`Timeline:
|
|
13655
|
+
${result.timeline.map((t) => `- ${t}`).join(`
|
|
13656
|
+
`)}`);
|
|
13657
|
+
}
|
|
13658
|
+
if (result.reasoning) {
|
|
13659
|
+
sections.push(`Reasoning: ${result.reasoning}`);
|
|
13660
|
+
}
|
|
13661
|
+
return sections.join(`
|
|
13662
|
+
|
|
13663
|
+
`);
|
|
13664
|
+
}
|
|
10939
13665
|
server.tool("memory_save", "Save/upsert a memory. scope: global=all agents, shared=project, private=single agent, working=transient session scratchpad (auto-expires in 1h, excluded from ALMA synthesis). conflict controls what happens when key already exists.", {
|
|
10940
13666
|
key: exports_external.string(),
|
|
10941
13667
|
value: exports_external.string(),
|
|
@@ -10952,11 +13678,15 @@ server.tool("memory_save", "Save/upsert a memory. scope: global=all agents, shar
|
|
|
10952
13678
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
10953
13679
|
conflict: exports_external.enum(["merge", "overwrite", "error", "version-fork"]).optional().describe("Conflict strategy: merge=upsert(default), overwrite=same as merge, error=fail if key exists, version-fork=always create new"),
|
|
10954
13680
|
conflict_strategy: exports_external.enum(["last_writer_wins", "reject"]).optional().describe("Vector clock conflict strategy: last_writer_wins (default) proceeds even if diverged, reject returns error on divergence"),
|
|
10955
|
-
machine_id: exports_external.string().optional().describe("Machine ID (from register_machine). If omitted, auto-detected from current hostname.")
|
|
13681
|
+
machine_id: exports_external.string().optional().describe("Machine ID (from register_machine). If omitted, auto-detected from current hostname."),
|
|
13682
|
+
when_to_use: exports_external.string().optional().describe("Activation context \u2014 describes WHEN this memory should be retrieved. Used for intent-based retrieval. Example: 'when deploying to production' or 'when debugging database issues'. If set, semantic search matches against this instead of the value."),
|
|
13683
|
+
sequence_group: exports_external.string().optional().describe("Chain/sequence group ID \u2014 links memories into an ordered procedural sequence. Use memory_chain_get to retrieve the full chain."),
|
|
13684
|
+
sequence_order: exports_external.coerce.number().optional().describe("Position within the sequence group (1-based). Memories in a chain are returned ordered by this field."),
|
|
13685
|
+
dedup_mode: exports_external.enum(["key", "semantic", "llm"]).optional().default("key").describe("Dedup strategy: 'key' = current key-based (default), 'semantic' = skip if >0.92 embedding similarity to existing memory, 'llm' = ask LLM if content is already covered")
|
|
10956
13686
|
}, async (args) => {
|
|
10957
13687
|
try {
|
|
10958
13688
|
ensureAutoProject();
|
|
10959
|
-
const { conflict, ...restArgs } = args;
|
|
13689
|
+
const { conflict, dedup_mode, ...restArgs } = args;
|
|
10960
13690
|
const input = { ...restArgs };
|
|
10961
13691
|
if (restArgs.ttl_ms !== undefined) {
|
|
10962
13692
|
input.ttl_ms = parseDuration(restArgs.ttl_ms);
|
|
@@ -10987,6 +13717,89 @@ server.tool("memory_save", "Save/upsert a memory. scope: global=all agents, shar
|
|
|
10987
13717
|
}
|
|
10988
13718
|
} catch {}
|
|
10989
13719
|
}
|
|
13720
|
+
if (dedup_mode === "semantic") {
|
|
13721
|
+
try {
|
|
13722
|
+
const textToEmbed = input.when_to_use || input.value;
|
|
13723
|
+
const results = await semanticSearch(textToEmbed, {
|
|
13724
|
+
threshold: 0.3,
|
|
13725
|
+
limit: 5,
|
|
13726
|
+
scope: input.scope,
|
|
13727
|
+
agent_id: input.agent_id,
|
|
13728
|
+
project_id: input.project_id
|
|
13729
|
+
});
|
|
13730
|
+
const SEMANTIC_THRESHOLD = 0.92;
|
|
13731
|
+
for (const result of results) {
|
|
13732
|
+
if (result.score >= SEMANTIC_THRESHOLD) {
|
|
13733
|
+
return { content: [{ type: "text", text: `Skipped: content similar to existing memory ${result.memory.id.slice(0, 8)} (similarity: ${result.score.toFixed(2)})` }] };
|
|
13734
|
+
}
|
|
13735
|
+
}
|
|
13736
|
+
} catch {}
|
|
13737
|
+
}
|
|
13738
|
+
if (dedup_mode === "llm") {
|
|
13739
|
+
try {
|
|
13740
|
+
const textToEmbed = input.when_to_use || input.value;
|
|
13741
|
+
const candidates = await semanticSearch(textToEmbed, {
|
|
13742
|
+
threshold: 0.3,
|
|
13743
|
+
limit: 5,
|
|
13744
|
+
scope: input.scope,
|
|
13745
|
+
agent_id: input.agent_id,
|
|
13746
|
+
project_id: input.project_id
|
|
13747
|
+
});
|
|
13748
|
+
if (candidates.length > 0) {
|
|
13749
|
+
const anthropicKey = process.env["ANTHROPIC_API_KEY"];
|
|
13750
|
+
if (anthropicKey) {
|
|
13751
|
+
const existingList = candidates.map((c, i) => `[${i + 1}] key="${c.memory.key}" value="${c.memory.value}" (similarity: ${c.score.toFixed(2)})`).join(`
|
|
13752
|
+
`);
|
|
13753
|
+
const llmPrompt = `New memory to save:
|
|
13754
|
+
key="${input.key}"
|
|
13755
|
+
value="${input.value}"
|
|
13756
|
+
|
|
13757
|
+
Existing similar memories:
|
|
13758
|
+
${existingList}
|
|
13759
|
+
|
|
13760
|
+
Is the new memory already covered by these existing memories? Reply with JSON only:
|
|
13761
|
+
{"action": "skip" | "merge" | "add", "reason": "string", "merge_text": "string if action=merge \u2014 the merged content combining new and existing"}`;
|
|
13762
|
+
const llmRes = await fetch("https://api.anthropic.com/v1/messages", {
|
|
13763
|
+
method: "POST",
|
|
13764
|
+
headers: {
|
|
13765
|
+
"x-api-key": anthropicKey,
|
|
13766
|
+
"anthropic-version": "2023-06-01",
|
|
13767
|
+
"content-type": "application/json"
|
|
13768
|
+
},
|
|
13769
|
+
body: JSON.stringify({
|
|
13770
|
+
model: "claude-haiku-4-5-20251001",
|
|
13771
|
+
max_tokens: 300,
|
|
13772
|
+
system: "You are a deduplication assistant. Given a new memory and existing similar memories, decide: skip (already covered), merge (combine with existing), or add (genuinely new). Reply with JSON only.",
|
|
13773
|
+
messages: [{ role: "user", content: llmPrompt }]
|
|
13774
|
+
}),
|
|
13775
|
+
signal: AbortSignal.timeout(15000)
|
|
13776
|
+
});
|
|
13777
|
+
if (llmRes.ok) {
|
|
13778
|
+
const llmData = await llmRes.json();
|
|
13779
|
+
const llmText = llmData.content?.[0]?.text?.trim() ?? "";
|
|
13780
|
+
const jsonMatch = llmText.match(/\{[\s\S]*\}/);
|
|
13781
|
+
if (jsonMatch) {
|
|
13782
|
+
const decision = JSON.parse(jsonMatch[0]);
|
|
13783
|
+
if (decision.action === "skip") {
|
|
13784
|
+
return { content: [{ type: "text", text: `Skipped (LLM dedup): ${decision.reason}` }] };
|
|
13785
|
+
}
|
|
13786
|
+
if (decision.action === "merge" && decision.merge_text && candidates[0]) {
|
|
13787
|
+
const target = candidates[0].memory;
|
|
13788
|
+
try {
|
|
13789
|
+
updateMemory(target.id, { value: decision.merge_text, version: target.version });
|
|
13790
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
13791
|
+
merged_into: target.id.slice(0, 8),
|
|
13792
|
+
key: target.key,
|
|
13793
|
+
reason: decision.reason
|
|
13794
|
+
}) }] };
|
|
13795
|
+
} catch {}
|
|
13796
|
+
}
|
|
13797
|
+
}
|
|
13798
|
+
}
|
|
13799
|
+
}
|
|
13800
|
+
}
|
|
13801
|
+
} catch {}
|
|
13802
|
+
}
|
|
10990
13803
|
const memory = createMemory(input, dedupeMode);
|
|
10991
13804
|
if (input.agent_id) {
|
|
10992
13805
|
try {
|
|
@@ -11034,7 +13847,21 @@ server.tool("memory_recall", "Recall a memory by key. Returns the best matching
|
|
|
11034
13847
|
touchMemory(memory.id);
|
|
11035
13848
|
if (args.agent_id)
|
|
11036
13849
|
touchAgent(args.agent_id);
|
|
11037
|
-
|
|
13850
|
+
let text = formatMemory(memory);
|
|
13851
|
+
if (memory.sequence_group) {
|
|
13852
|
+
try {
|
|
13853
|
+
const db = getDatabase();
|
|
13854
|
+
const chainRows = db.prepare("SELECT * FROM memories WHERE sequence_group = ? AND status = 'active' ORDER BY sequence_order ASC").all(memory.sequence_group);
|
|
13855
|
+
if (chainRows.length > 1) {
|
|
13856
|
+
const steps = chainRows.map(parseMemoryRow);
|
|
13857
|
+
const chainLine = steps.map((s) => `[${s.sequence_order ?? "?"}] ${s.key}`).join(" \u2192 ");
|
|
13858
|
+
text += `
|
|
13859
|
+
|
|
13860
|
+
Chain context: ${chainLine}`;
|
|
13861
|
+
}
|
|
13862
|
+
} catch {}
|
|
13863
|
+
}
|
|
13864
|
+
return { content: [{ type: "text", text }] };
|
|
11038
13865
|
}
|
|
11039
13866
|
const results = searchMemories(args.key, {
|
|
11040
13867
|
scope: args.scope,
|
|
@@ -11172,7 +13999,8 @@ server.tool("memory_update", "Update a memory's metadata (value, importance, tag
|
|
|
11172
13999
|
status: exports_external.enum(["active", "archived", "expired"]).optional(),
|
|
11173
14000
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
11174
14001
|
expires_at: exports_external.string().nullable().optional(),
|
|
11175
|
-
version: exports_external.coerce.number().optional()
|
|
14002
|
+
version: exports_external.coerce.number().optional(),
|
|
14003
|
+
when_to_use: exports_external.string().optional().describe("Update the activation context for this memory")
|
|
11176
14004
|
}, async (args) => {
|
|
11177
14005
|
try {
|
|
11178
14006
|
const id = resolveId(args.id);
|
|
@@ -11437,6 +14265,34 @@ server.tool("memory_diff", "Show what changed between two versions of a memory.
|
|
|
11437
14265
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11438
14266
|
}
|
|
11439
14267
|
});
|
|
14268
|
+
server.tool("memory_chain_get", "Retrieve an ordered memory chain/sequence by group ID. Returns all steps in order.", {
|
|
14269
|
+
sequence_group: exports_external.string().describe("The chain/sequence group ID to retrieve"),
|
|
14270
|
+
project_id: exports_external.string().optional()
|
|
14271
|
+
}, async (args) => {
|
|
14272
|
+
try {
|
|
14273
|
+
ensureAutoProject();
|
|
14274
|
+
const db = getDatabase();
|
|
14275
|
+
let effectiveProjectId = args.project_id;
|
|
14276
|
+
const conditions = ["sequence_group = ?", "status = 'active'"];
|
|
14277
|
+
const params = [args.sequence_group];
|
|
14278
|
+
if (effectiveProjectId) {
|
|
14279
|
+
conditions.push("project_id = ?");
|
|
14280
|
+
params.push(effectiveProjectId);
|
|
14281
|
+
}
|
|
14282
|
+
const rows = db.prepare(`SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY sequence_order ASC`).all(...params);
|
|
14283
|
+
if (rows.length === 0) {
|
|
14284
|
+
return { content: [{ type: "text", text: `No chain found for sequence_group: "${args.sequence_group}"` }] };
|
|
14285
|
+
}
|
|
14286
|
+
const memories = rows.map(parseMemoryRow);
|
|
14287
|
+
const chainSteps = memories.map((m, i) => `[Step ${m.sequence_order ?? i + 1}] ${m.key}: ${m.value}`).join(`
|
|
14288
|
+
`);
|
|
14289
|
+
const header = `Chain "${args.sequence_group}" (${memories.length} steps):
|
|
14290
|
+
`;
|
|
14291
|
+
return { content: [{ type: "text", text: header + chainSteps }] };
|
|
14292
|
+
} catch (e) {
|
|
14293
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
14294
|
+
}
|
|
14295
|
+
});
|
|
11440
14296
|
server.tool("memory_search", "Search memories by keyword across key, value, summary, and tags", {
|
|
11441
14297
|
query: exports_external.string(),
|
|
11442
14298
|
scope: exports_external.enum(["global", "shared", "private", "working"]).optional(),
|
|
@@ -11490,10 +14346,10 @@ server.tool("memory_search_semantic", "Semantic (meaning-based) memory search us
|
|
|
11490
14346
|
ensureAutoProject();
|
|
11491
14347
|
if (args.index_missing) {
|
|
11492
14348
|
const db = getDatabase();
|
|
11493
|
-
const unindexed = db.prepare(`SELECT id, value, summary FROM memories
|
|
14349
|
+
const unindexed = db.prepare(`SELECT id, value, summary, when_to_use FROM memories
|
|
11494
14350
|
WHERE status = 'active' AND id NOT IN (SELECT memory_id FROM memory_embeddings)
|
|
11495
14351
|
LIMIT 100`).all();
|
|
11496
|
-
await Promise.all(unindexed.map((m) => indexMemoryEmbedding(m.id, [m.value, m.summary].filter(Boolean).join(" "))));
|
|
14352
|
+
await Promise.all(unindexed.map((m) => indexMemoryEmbedding(m.id, m.when_to_use || [m.value, m.summary].filter(Boolean).join(" "))));
|
|
11497
14353
|
}
|
|
11498
14354
|
let effectiveProjectId = args.project_id;
|
|
11499
14355
|
if (!args.project_id && args.agent_id) {
|
|
@@ -11595,6 +14451,91 @@ ${lines.join(`
|
|
|
11595
14451
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11596
14452
|
}
|
|
11597
14453
|
});
|
|
14454
|
+
server.tool("memory_recall_deep", "Deep memory recall using ASMR (Agentic Search and Memory Retrieval) \u2014 runs 3 parallel search agents (facts, context, temporal) for high-accuracy retrieval with optional ensemble answering", {
|
|
14455
|
+
query: exports_external.string().describe("Natural language query"),
|
|
14456
|
+
mode: exports_external.enum(["fast", "deep", "auto"]).default("deep").describe("fast=FTS+semantic, deep=ASMR 3-agent, auto=fast then escalate"),
|
|
14457
|
+
max_results: exports_external.coerce.number().default(20),
|
|
14458
|
+
ensemble: exports_external.coerce.boolean().default(false).describe("Use ensemble answering with majority voting"),
|
|
14459
|
+
project_id: exports_external.string().optional()
|
|
14460
|
+
}, async (args) => {
|
|
14461
|
+
try {
|
|
14462
|
+
ensureAutoProject();
|
|
14463
|
+
const db = getDatabase();
|
|
14464
|
+
const FAST_SCORE_THRESHOLD = 0.6;
|
|
14465
|
+
if (args.mode === "fast") {
|
|
14466
|
+
const results = await hybridSearch(args.query, {
|
|
14467
|
+
filter: { project_id: args.project_id, limit: args.max_results },
|
|
14468
|
+
limit: args.max_results
|
|
14469
|
+
});
|
|
14470
|
+
if (results.length === 0) {
|
|
14471
|
+
return { content: [{ type: "text", text: `No memories found for "${args.query}" via fast search.` }] };
|
|
14472
|
+
}
|
|
14473
|
+
const lines = results.map((r, i) => `${i + 1}. [score:${r.score.toFixed(3)}] [${r.memory.scope}/${r.memory.category}] ${r.memory.key} = ${r.memory.value.slice(0, 120)}${r.memory.value.length > 120 ? "..." : ""}`);
|
|
14474
|
+
return { content: [{ type: "text", text: `[fast] ${results.length} result(s) for "${args.query}":
|
|
14475
|
+
${lines.join(`
|
|
14476
|
+
`)}` }] };
|
|
14477
|
+
}
|
|
14478
|
+
if (args.mode === "deep") {
|
|
14479
|
+
const asmrResult2 = await asmrRecall(db, args.query, {
|
|
14480
|
+
max_results: args.max_results,
|
|
14481
|
+
project_id: args.project_id
|
|
14482
|
+
});
|
|
14483
|
+
let text2 = formatAsmrResult(asmrResult2, args.query);
|
|
14484
|
+
if (args.ensemble) {
|
|
14485
|
+
try {
|
|
14486
|
+
const answer = await ensembleAnswer(asmrResult2, args.query);
|
|
14487
|
+
text2 += `
|
|
14488
|
+
|
|
14489
|
+
--- Ensemble Answer (confidence: ${(answer.confidence * 100).toFixed(0)}%, consensus: ${answer.consensus_reached ? "yes" : "no"}, escalated: ${answer.escalated ? "yes" : "no"}) ---
|
|
14490
|
+
${answer.answer}
|
|
14491
|
+
|
|
14492
|
+
Reasoning: ${answer.reasoning}`;
|
|
14493
|
+
} catch (ensErr) {
|
|
14494
|
+
text2 += `
|
|
14495
|
+
|
|
14496
|
+
[Ensemble failed: ${ensErr instanceof Error ? ensErr.message : "unknown error"}]`;
|
|
14497
|
+
}
|
|
14498
|
+
}
|
|
14499
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
14500
|
+
}
|
|
14501
|
+
const fastResults = await hybridSearch(args.query, {
|
|
14502
|
+
filter: { project_id: args.project_id, limit: args.max_results },
|
|
14503
|
+
limit: args.max_results
|
|
14504
|
+
});
|
|
14505
|
+
const topScore = fastResults.length > 0 ? fastResults[0].score : 0;
|
|
14506
|
+
if (topScore >= FAST_SCORE_THRESHOLD && fastResults.length >= 3) {
|
|
14507
|
+
const lines = fastResults.map((r, i) => `${i + 1}. [score:${r.score.toFixed(3)}] [${r.memory.scope}/${r.memory.category}] ${r.memory.key} = ${r.memory.value.slice(0, 120)}${r.memory.value.length > 120 ? "..." : ""}`);
|
|
14508
|
+
return { content: [{ type: "text", text: `[auto/fast] ${fastResults.length} result(s) for "${args.query}" (top score ${topScore.toFixed(3)} >= threshold):
|
|
14509
|
+
${lines.join(`
|
|
14510
|
+
`)}` }] };
|
|
14511
|
+
}
|
|
14512
|
+
const asmrResult = await asmrRecall(db, args.query, {
|
|
14513
|
+
max_results: args.max_results,
|
|
14514
|
+
project_id: args.project_id
|
|
14515
|
+
});
|
|
14516
|
+
let text = `[auto/escalated] Fast search top score ${topScore.toFixed(3)} < ${FAST_SCORE_THRESHOLD} threshold \u2014 escalated to ASMR deep recall.
|
|
14517
|
+
|
|
14518
|
+
${formatAsmrResult(asmrResult, args.query)}`;
|
|
14519
|
+
if (args.ensemble) {
|
|
14520
|
+
try {
|
|
14521
|
+
const answer = await ensembleAnswer(asmrResult, args.query);
|
|
14522
|
+
text += `
|
|
14523
|
+
|
|
14524
|
+
--- Ensemble Answer (confidence: ${(answer.confidence * 100).toFixed(0)}%, consensus: ${answer.consensus_reached ? "yes" : "no"}, escalated: ${answer.escalated ? "yes" : "no"}) ---
|
|
14525
|
+
${answer.answer}
|
|
14526
|
+
|
|
14527
|
+
Reasoning: ${answer.reasoning}`;
|
|
14528
|
+
} catch (ensErr) {
|
|
14529
|
+
text += `
|
|
14530
|
+
|
|
14531
|
+
[Ensemble failed: ${ensErr instanceof Error ? ensErr.message : "unknown error"}]`;
|
|
14532
|
+
}
|
|
14533
|
+
}
|
|
14534
|
+
return { content: [{ type: "text", text }] };
|
|
14535
|
+
} catch (e) {
|
|
14536
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
14537
|
+
}
|
|
14538
|
+
});
|
|
11598
14539
|
server.tool("memory_check_contradiction", "Check if a new memory would contradict existing high-importance facts. Call before saving to detect conflicts. Returns contradiction details if found.", {
|
|
11599
14540
|
key: exports_external.string().describe("Memory key to check"),
|
|
11600
14541
|
value: exports_external.string().describe("New value to check for contradictions"),
|
|
@@ -11900,9 +14841,23 @@ server.tool("memory_inject", "Get memory context for system prompt injection. Se
|
|
|
11900
14841
|
format: exports_external.enum(["xml", "markdown", "compact", "json"]).optional(),
|
|
11901
14842
|
raw: exports_external.boolean().optional(),
|
|
11902
14843
|
strategy: exports_external.enum(["default", "smart"]).optional().describe("Injection strategy: 'default' uses importance+recency, 'smart' uses embedding similarity+importance+recency"),
|
|
11903
|
-
query: exports_external.string().optional().describe("Query for smart injection relevance scoring. Required when strategy='smart'.")
|
|
14844
|
+
query: exports_external.string().optional().describe("Query for smart injection relevance scoring. Required when strategy='smart'."),
|
|
14845
|
+
task_context: exports_external.string().optional().describe("What the agent is about to do. When provided, activates intent-based retrieval \u2014 matches against when_to_use fields for situationally relevant memories."),
|
|
14846
|
+
mode: exports_external.enum(["full", "hints"]).optional().default("full").describe("'full' = inject complete memory content (default), 'hints' = inject lightweight topic summary with counts, saving 60-70% tokens. Agent uses memory_recall to pull details as needed.")
|
|
11904
14847
|
}, async (args) => {
|
|
11905
14848
|
try {
|
|
14849
|
+
if (args.strategy === "smart" && args.task_context && args.mode !== "hints") {
|
|
14850
|
+
const { smartInject: smartInject2 } = await Promise.resolve().then(() => (init_injector(), exports_injector));
|
|
14851
|
+
const result = await smartInject2({
|
|
14852
|
+
task_context: args.task_context,
|
|
14853
|
+
project_id: args.project_id,
|
|
14854
|
+
agent_id: args.agent_id,
|
|
14855
|
+
session_id: args.session_id,
|
|
14856
|
+
max_tokens: args.max_tokens,
|
|
14857
|
+
min_importance: args.min_importance
|
|
14858
|
+
});
|
|
14859
|
+
return { content: [{ type: "text", text: result.output }] };
|
|
14860
|
+
}
|
|
11906
14861
|
const maxTokens = args.max_tokens || 500;
|
|
11907
14862
|
const minImportance = args.min_importance || 3;
|
|
11908
14863
|
const categories = args.categories || ["preference", "fact", "knowledge"];
|
|
@@ -11956,10 +14911,29 @@ server.tool("memory_inject", "Get memory context for system prompt injection. Se
|
|
|
11956
14911
|
seen.add(m.id);
|
|
11957
14912
|
return true;
|
|
11958
14913
|
});
|
|
14914
|
+
const activationBoostedIds = new Set;
|
|
14915
|
+
if (args.task_context) {
|
|
14916
|
+
try {
|
|
14917
|
+
const activationResults = await semanticSearch(args.task_context, {
|
|
14918
|
+
threshold: 0.3,
|
|
14919
|
+
limit: 20,
|
|
14920
|
+
scope: undefined,
|
|
14921
|
+
agent_id: args.agent_id,
|
|
14922
|
+
project_id: args.project_id
|
|
14923
|
+
});
|
|
14924
|
+
for (const r of activationResults) {
|
|
14925
|
+
activationBoostedIds.add(r.memory.id);
|
|
14926
|
+
if (!seen.has(r.memory.id)) {
|
|
14927
|
+
seen.add(r.memory.id);
|
|
14928
|
+
unique.push(r.memory);
|
|
14929
|
+
}
|
|
14930
|
+
}
|
|
14931
|
+
} catch {}
|
|
14932
|
+
}
|
|
11959
14933
|
if (args.strategy === "smart" && args.query) {
|
|
11960
14934
|
const { generateEmbedding: genEmb, cosineSimilarity: cosSim, deserializeEmbedding: deserEmb } = await Promise.resolve().then(() => exports_embeddings);
|
|
11961
|
-
const { getDatabase:
|
|
11962
|
-
const d =
|
|
14935
|
+
const { getDatabase: getDb2 } = await Promise.resolve().then(() => (init_database(), exports_database));
|
|
14936
|
+
const d = getDb2();
|
|
11963
14937
|
const { embedding: queryEmbedding } = await genEmb(args.query);
|
|
11964
14938
|
const embeddingMap = new Map;
|
|
11965
14939
|
if (unique.length > 0) {
|
|
@@ -11978,7 +14952,7 @@ server.tool("memory_inject", "Get memory context for system prompt injection. Se
|
|
|
11978
14952
|
for (const m of unique) {
|
|
11979
14953
|
const memEmb = embeddingMap.get(m.id);
|
|
11980
14954
|
const similarity = memEmb ? cosSim(queryEmbedding, memEmb) : 0;
|
|
11981
|
-
const importanceScore = m.importance / 10;
|
|
14955
|
+
const importanceScore = (m.importance + (activationBoostedIds.has(m.id) ? 3 : 0)) / 10;
|
|
11982
14956
|
const recencyScore = (new Date(m.updated_at).getTime() - oldestMs) / timeRange;
|
|
11983
14957
|
const pinBonus = m.pinned ? 0.5 : 0;
|
|
11984
14958
|
scores.set(m.id, similarity * 0.4 + importanceScore * 0.3 + recencyScore * 0.3 + pinBonus);
|
|
@@ -11986,18 +14960,83 @@ server.tool("memory_inject", "Get memory context for system prompt injection. Se
|
|
|
11986
14960
|
unique.sort((a, b) => (scores.get(b.id) || 0) - (scores.get(a.id) || 0));
|
|
11987
14961
|
} else {
|
|
11988
14962
|
unique.sort((a, b) => {
|
|
11989
|
-
|
|
11990
|
-
|
|
14963
|
+
const aImp = a.importance + (activationBoostedIds.has(a.id) ? 3 : 0);
|
|
14964
|
+
const bImp = b.importance + (activationBoostedIds.has(b.id) ? 3 : 0);
|
|
14965
|
+
if (bImp !== aImp)
|
|
14966
|
+
return bImp - aImp;
|
|
11991
14967
|
return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
|
|
11992
14968
|
});
|
|
11993
14969
|
}
|
|
11994
14970
|
} else {
|
|
11995
14971
|
unique.sort((a, b) => {
|
|
11996
|
-
|
|
11997
|
-
|
|
14972
|
+
const aImp = a.importance + (activationBoostedIds.has(a.id) ? 3 : 0);
|
|
14973
|
+
const bImp = b.importance + (activationBoostedIds.has(b.id) ? 3 : 0);
|
|
14974
|
+
if (bImp !== aImp)
|
|
14975
|
+
return bImp - aImp;
|
|
11998
14976
|
return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
|
|
11999
14977
|
});
|
|
12000
14978
|
}
|
|
14979
|
+
if (args.mode === "hints") {
|
|
14980
|
+
let extractTopics = function(memories, maxTopics = 8) {
|
|
14981
|
+
const wordCounts = new Map;
|
|
14982
|
+
for (const m of memories) {
|
|
14983
|
+
const keyWords = m.key.split(/[-_\s]+/).filter((w) => w.length > 2 && !stopWords.has(w.toLowerCase()));
|
|
14984
|
+
for (const w of keyWords) {
|
|
14985
|
+
const lower = w.toLowerCase();
|
|
14986
|
+
wordCounts.set(lower, (wordCounts.get(lower) || 0) + 1);
|
|
14987
|
+
}
|
|
14988
|
+
if (m.tags) {
|
|
14989
|
+
const tags = Array.isArray(m.tags) ? m.tags : typeof m.tags === "string" ? m.tags.split(",") : [];
|
|
14990
|
+
for (const t of tags) {
|
|
14991
|
+
const tag = t.trim().toLowerCase();
|
|
14992
|
+
if (tag.length > 2 && !stopWords.has(tag)) {
|
|
14993
|
+
wordCounts.set(tag, (wordCounts.get(tag) || 0) + 1);
|
|
14994
|
+
}
|
|
14995
|
+
}
|
|
14996
|
+
}
|
|
14997
|
+
}
|
|
14998
|
+
return Array.from(wordCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, maxTopics).map(([word]) => word);
|
|
14999
|
+
};
|
|
15000
|
+
if (unique.length === 0) {
|
|
15001
|
+
return { content: [{ type: "text", text: "No relevant memories found." }] };
|
|
15002
|
+
}
|
|
15003
|
+
const groups = new Map;
|
|
15004
|
+
for (const m of unique) {
|
|
15005
|
+
const cat = m.category || "knowledge";
|
|
15006
|
+
if (!groups.has(cat))
|
|
15007
|
+
groups.set(cat, []);
|
|
15008
|
+
groups.get(cat).push(m);
|
|
15009
|
+
}
|
|
15010
|
+
const stopWords = new Set(["the", "a", "an", "is", "in", "on", "at", "to", "for", "of", "and", "or", "with", "my", "this", "that"]);
|
|
15011
|
+
const categoryLabels = {
|
|
15012
|
+
fact: "Facts",
|
|
15013
|
+
preference: "Preferences",
|
|
15014
|
+
knowledge: "Knowledge",
|
|
15015
|
+
history: "History",
|
|
15016
|
+
procedural: "Procedures",
|
|
15017
|
+
resource: "Resources"
|
|
15018
|
+
};
|
|
15019
|
+
const hintLines = ["You have relevant memories available:"];
|
|
15020
|
+
const categoryOrder = ["fact", "knowledge", "preference", "procedural", "history", "resource"];
|
|
15021
|
+
for (const cat of categoryOrder) {
|
|
15022
|
+
const mems = groups.get(cat);
|
|
15023
|
+
if (!mems || mems.length === 0)
|
|
15024
|
+
continue;
|
|
15025
|
+
const label = categoryLabels[cat] || cat;
|
|
15026
|
+
const topics = extractTopics(mems);
|
|
15027
|
+
hintLines.push(`- ${label} (${mems.length}): ${topics.join(", ")}`);
|
|
15028
|
+
}
|
|
15029
|
+
for (const [cat, mems] of groups) {
|
|
15030
|
+
if (categoryOrder.includes(cat))
|
|
15031
|
+
continue;
|
|
15032
|
+
const topics = extractTopics(mems);
|
|
15033
|
+
hintLines.push(`- ${cat} (${mems.length}): ${topics.join(", ")}`);
|
|
15034
|
+
}
|
|
15035
|
+
hintLines.push("");
|
|
15036
|
+
hintLines.push('Use memory_recall(key="...") to access any of these. Use memory_search(query="...") for broader searches.');
|
|
15037
|
+
return { content: [{ type: "text", text: hintLines.join(`
|
|
15038
|
+
`) }] };
|
|
15039
|
+
}
|
|
12001
15040
|
const fmt = args.format ?? (args.raw ? "compact" : "xml");
|
|
12002
15041
|
const preCtx = {
|
|
12003
15042
|
memories: unique,
|
|
@@ -12624,9 +15663,21 @@ server.tool("memory_context", "Get memories relevant to current context. Uses ti
|
|
|
12624
15663
|
scope: exports_external.enum(["global", "shared", "private", "working"]).optional(),
|
|
12625
15664
|
limit: exports_external.coerce.number().optional(),
|
|
12626
15665
|
decay_halflife_days: exports_external.coerce.number().optional().describe("Importance half-life in days (default: 90). Lower = more weight on recent memories."),
|
|
12627
|
-
no_decay: exports_external.coerce.boolean().optional().describe("Set true to disable decay and sort purely by importance.")
|
|
15666
|
+
no_decay: exports_external.coerce.boolean().optional().describe("Set true to disable decay and sort purely by importance."),
|
|
15667
|
+
task_context: exports_external.string().optional().describe("What the agent is about to do. When provided, activates intent-based retrieval \u2014 matches against when_to_use fields for situationally relevant memories."),
|
|
15668
|
+
strategy: exports_external.enum(["default", "smart"]).optional().default("default").describe("Injection strategy: 'default' = decay-scored, 'smart' = activation-matched + layered + tool-aware (requires task_context)")
|
|
12628
15669
|
}, async (args) => {
|
|
12629
15670
|
try {
|
|
15671
|
+
if (args.strategy === "smart" && args.task_context) {
|
|
15672
|
+
const { smartInject: smartInject2 } = await Promise.resolve().then(() => (init_injector(), exports_injector));
|
|
15673
|
+
const result = await smartInject2({
|
|
15674
|
+
task_context: args.task_context,
|
|
15675
|
+
project_id: args.project_id,
|
|
15676
|
+
agent_id: args.agent_id,
|
|
15677
|
+
max_tokens: args.limit ? args.limit * 20 : undefined
|
|
15678
|
+
});
|
|
15679
|
+
return { content: [{ type: "text", text: result.output }] };
|
|
15680
|
+
}
|
|
12630
15681
|
const filter = {
|
|
12631
15682
|
scope: args.scope,
|
|
12632
15683
|
agent_id: args.agent_id,
|
|
@@ -12635,18 +15686,39 @@ server.tool("memory_context", "Get memories relevant to current context. Uses ti
|
|
|
12635
15686
|
limit: (args.limit || 30) * 2
|
|
12636
15687
|
};
|
|
12637
15688
|
const memories = listMemories(filter);
|
|
15689
|
+
const activationBoostedIds = new Set;
|
|
15690
|
+
if (args.task_context) {
|
|
15691
|
+
try {
|
|
15692
|
+
const activationResults = await semanticSearch(args.task_context, {
|
|
15693
|
+
threshold: 0.3,
|
|
15694
|
+
limit: 20,
|
|
15695
|
+
scope: args.scope,
|
|
15696
|
+
agent_id: args.agent_id,
|
|
15697
|
+
project_id: args.project_id
|
|
15698
|
+
});
|
|
15699
|
+
const seenIds = new Set(memories.map((m) => m.id));
|
|
15700
|
+
for (const r of activationResults) {
|
|
15701
|
+
activationBoostedIds.add(r.memory.id);
|
|
15702
|
+
if (!seenIds.has(r.memory.id)) {
|
|
15703
|
+
seenIds.add(r.memory.id);
|
|
15704
|
+
memories.push(r.memory);
|
|
15705
|
+
}
|
|
15706
|
+
}
|
|
15707
|
+
} catch {}
|
|
15708
|
+
}
|
|
12638
15709
|
if (memories.length === 0) {
|
|
12639
15710
|
return { content: [{ type: "text", text: "No memories in current context." }] };
|
|
12640
15711
|
}
|
|
12641
15712
|
const halflifeDays = args.decay_halflife_days ?? 90;
|
|
12642
|
-
const
|
|
15713
|
+
const now3 = Date.now();
|
|
12643
15714
|
const scored = memories.map((m) => {
|
|
12644
|
-
|
|
15715
|
+
const activationBoost = activationBoostedIds.has(m.id) ? 3 : 0;
|
|
15716
|
+
let effectiveScore = m.importance + activationBoost;
|
|
12645
15717
|
if (!args.no_decay && !m.pinned) {
|
|
12646
|
-
const ageMs =
|
|
12647
|
-
const ageDays = ageMs /
|
|
15718
|
+
const ageMs = now3 - new Date(m.updated_at).getTime();
|
|
15719
|
+
const ageDays = ageMs / 86400000;
|
|
12648
15720
|
const decayFactor = Math.pow(0.5, ageDays / halflifeDays);
|
|
12649
|
-
effectiveScore = m.importance * decayFactor;
|
|
15721
|
+
effectiveScore = (m.importance + activationBoost) * decayFactor;
|
|
12650
15722
|
}
|
|
12651
15723
|
if (m.flag)
|
|
12652
15724
|
effectiveScore = Math.max(effectiveScore, 11);
|
|
@@ -12692,6 +15764,25 @@ ${ctx.total_memories} memories, ~${ctx.token_estimate} tokens` }] };
|
|
|
12692
15764
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
12693
15765
|
}
|
|
12694
15766
|
});
|
|
15767
|
+
server.tool("memory_profile", "Synthesize a coherent profile from preference and fact memories using LLM. Cached for 24h, auto-refreshed when preferences change. Returns markdown profile.", {
|
|
15768
|
+
project_id: exports_external.string().optional(),
|
|
15769
|
+
agent_id: exports_external.string().optional(),
|
|
15770
|
+
scope: exports_external.enum(["agent", "project", "global"]).optional().default("project"),
|
|
15771
|
+
force_refresh: exports_external.boolean().optional().default(false).describe("Force re-synthesis even if cached profile exists")
|
|
15772
|
+
}, async (args) => {
|
|
15773
|
+
try {
|
|
15774
|
+
ensureAutoProject();
|
|
15775
|
+
const result = await synthesizeProfile(args);
|
|
15776
|
+
if (!result) {
|
|
15777
|
+
return { content: [{ type: "text", text: "No preference or fact memories found to synthesize a profile from." }] };
|
|
15778
|
+
}
|
|
15779
|
+
return { content: [{ type: "text", text: `${result.from_cache ? "[cached] " : "[synthesized] "}(${result.memory_count} memories)
|
|
15780
|
+
|
|
15781
|
+
${result.profile}` }] };
|
|
15782
|
+
} catch (e) {
|
|
15783
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
15784
|
+
}
|
|
15785
|
+
});
|
|
12695
15786
|
function resolveEntityParam(nameOrId, type) {
|
|
12696
15787
|
const byName = getEntityByName(nameOrId, type);
|
|
12697
15788
|
if (byName)
|
|
@@ -13043,6 +16134,95 @@ server.tool("build_file_dep_graph", "Scan a codebase directory and build a file
|
|
|
13043
16134
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13044
16135
|
}
|
|
13045
16136
|
});
|
|
16137
|
+
server.tool("memory_tool_insights", "Get usage stats, lessons learned, and recommendations for MCP tools. Helps agents avoid past mistakes and reuse successful patterns.", {
|
|
16138
|
+
tool_name: exports_external.string().optional().describe("Specific tool to get insights for. If omitted, returns insights for all tools."),
|
|
16139
|
+
task_context: exports_external.string().optional().describe("What the agent is about to do \u2014 used to find relevant tool lessons via semantic match"),
|
|
16140
|
+
project_id: exports_external.string().optional(),
|
|
16141
|
+
agent_id: exports_external.string().optional(),
|
|
16142
|
+
limit: exports_external.coerce.number().optional().default(10).describe("Max lessons to return per tool")
|
|
16143
|
+
}, async (args) => {
|
|
16144
|
+
try {
|
|
16145
|
+
const db = getDatabase();
|
|
16146
|
+
const projId = args.project_id ? resolvePartialId(db, "projects", args.project_id) ?? args.project_id : undefined;
|
|
16147
|
+
const limit = args.limit ?? 10;
|
|
16148
|
+
let toolNames;
|
|
16149
|
+
if (args.tool_name) {
|
|
16150
|
+
toolNames = [args.tool_name];
|
|
16151
|
+
} else {
|
|
16152
|
+
const filters = { limit: 200 };
|
|
16153
|
+
if (projId)
|
|
16154
|
+
filters.project_id = projId;
|
|
16155
|
+
if (args.agent_id)
|
|
16156
|
+
filters.agent_id = args.agent_id;
|
|
16157
|
+
const events = getToolEvents(filters);
|
|
16158
|
+
toolNames = [...new Set(events.map((e) => e.tool_name))];
|
|
16159
|
+
}
|
|
16160
|
+
if (toolNames.length === 0) {
|
|
16161
|
+
return { content: [{ type: "text", text: "No tool events recorded yet." }] };
|
|
16162
|
+
}
|
|
16163
|
+
const sections = [];
|
|
16164
|
+
for (const tn of toolNames) {
|
|
16165
|
+
const stats = getToolStats(tn, projId);
|
|
16166
|
+
const lessons = getToolLessons(tn, projId, limit);
|
|
16167
|
+
const successPct = stats.total_calls > 0 ? Math.round(stats.success_rate * 100) : 0;
|
|
16168
|
+
const avgTok = stats.avg_tokens != null ? Math.round(stats.avg_tokens) : "?";
|
|
16169
|
+
const avgLat = stats.avg_latency_ms != null ? (stats.avg_latency_ms / 1000).toFixed(1) : "?";
|
|
16170
|
+
let section = `## Tool: ${tn}
|
|
16171
|
+
Stats: ${stats.total_calls} calls | ${successPct}% success | avg ${avgTok} tokens | avg ${avgLat}s`;
|
|
16172
|
+
if (stats.common_errors.length > 0) {
|
|
16173
|
+
const errParts = stats.common_errors.map((e) => `${e.error_type} (${e.count})`);
|
|
16174
|
+
section += `
|
|
16175
|
+
Common errors: ${errParts.join(", ")}`;
|
|
16176
|
+
}
|
|
16177
|
+
if (lessons.length > 0) {
|
|
16178
|
+
const dos = [];
|
|
16179
|
+
const donts = [];
|
|
16180
|
+
for (const l of lessons) {
|
|
16181
|
+
const ctx = (l.when_to_use || "").toLowerCase();
|
|
16182
|
+
if (ctx.includes("fail") || ctx.includes("error") || ctx.includes("avoid") || ctx.includes("don't") || ctx.includes("never")) {
|
|
16183
|
+
donts.push(l.lesson);
|
|
16184
|
+
} else {
|
|
16185
|
+
dos.push(l.lesson);
|
|
16186
|
+
}
|
|
16187
|
+
}
|
|
16188
|
+
if (dos.length > 0 || donts.length > 0) {
|
|
16189
|
+
section += `
|
|
16190
|
+
|
|
16191
|
+
### Recommendations`;
|
|
16192
|
+
for (const d of dos.slice(0, 5))
|
|
16193
|
+
section += `
|
|
16194
|
+
\u2705 DO: ${d}`;
|
|
16195
|
+
for (const d of donts.slice(0, 5))
|
|
16196
|
+
section += `
|
|
16197
|
+
\u274C DON'T: ${d}`;
|
|
16198
|
+
}
|
|
16199
|
+
section += `
|
|
16200
|
+
|
|
16201
|
+
### Lessons (newest first)`;
|
|
16202
|
+
for (const l of lessons) {
|
|
16203
|
+
const when = l.when_to_use ? ` (when: ${l.when_to_use})` : "";
|
|
16204
|
+
section += `
|
|
16205
|
+
- ${l.lesson}${when}`;
|
|
16206
|
+
}
|
|
16207
|
+
}
|
|
16208
|
+
sections.push(section);
|
|
16209
|
+
}
|
|
16210
|
+
let header = "";
|
|
16211
|
+
if (args.task_context) {
|
|
16212
|
+
header = `> Context: "${args.task_context}"
|
|
16213
|
+
> Showing insights filtered for relevance.
|
|
16214
|
+
|
|
16215
|
+
`;
|
|
16216
|
+
}
|
|
16217
|
+
return { content: [{ type: "text", text: header + sections.join(`
|
|
16218
|
+
|
|
16219
|
+
---
|
|
16220
|
+
|
|
16221
|
+
`) }] };
|
|
16222
|
+
} catch (e) {
|
|
16223
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
16224
|
+
}
|
|
16225
|
+
});
|
|
13046
16226
|
var FULL_SCHEMAS = {
|
|
13047
16227
|
memory_save: {
|
|
13048
16228
|
description: "Save/upsert a memory. Creates new or merges with existing key.",
|
|
@@ -13060,7 +16240,10 @@ var FULL_SCHEMAS = {
|
|
|
13060
16240
|
session_id: { type: "string", description: "Session UUID" },
|
|
13061
16241
|
ttl_ms: { type: "string|number", description: "Time-to-live e.g. '7d', '2h', or ms integer" },
|
|
13062
16242
|
source: { type: "string", description: "Origin of the memory", enum: ["user", "agent", "system", "auto", "imported"] },
|
|
13063
|
-
metadata: { type: "object", description: "Arbitrary JSON metadata" }
|
|
16243
|
+
metadata: { type: "object", description: "Arbitrary JSON metadata" },
|
|
16244
|
+
when_to_use: { type: "string", description: "Activation context \u2014 describes WHEN this memory should be retrieved. Used for intent-based retrieval. Example: 'when deploying to production'" },
|
|
16245
|
+
sequence_group: { type: "string", description: "Chain/sequence group ID \u2014 links memories into an ordered procedural sequence" },
|
|
16246
|
+
sequence_order: { type: "number", description: "Position within the sequence group (1-based)" }
|
|
13064
16247
|
},
|
|
13065
16248
|
example: '{"key":"preferred-language","value":"TypeScript","scope":"global","importance":8,"tags":["language","preference"]}'
|
|
13066
16249
|
},
|
|
@@ -13092,6 +16275,15 @@ var FULL_SCHEMAS = {
|
|
|
13092
16275
|
},
|
|
13093
16276
|
example: '{"key":"preferred-language","scope":"global"}'
|
|
13094
16277
|
},
|
|
16278
|
+
memory_chain_get: {
|
|
16279
|
+
description: "Retrieve an ordered memory chain/sequence by group ID. Returns all steps in procedural order.",
|
|
16280
|
+
category: "memory",
|
|
16281
|
+
params: {
|
|
16282
|
+
sequence_group: { type: "string", description: "The chain/sequence group ID to retrieve", required: true },
|
|
16283
|
+
project_id: { type: "string", description: "Project UUID filter" }
|
|
16284
|
+
},
|
|
16285
|
+
example: '{"sequence_group":"deploy-to-production"}'
|
|
16286
|
+
},
|
|
13095
16287
|
memory_list: {
|
|
13096
16288
|
description: "List memories with optional filters. Returns compact lines by default.",
|
|
13097
16289
|
category: "memory",
|
|
@@ -13127,7 +16319,8 @@ var FULL_SCHEMAS = {
|
|
|
13127
16319
|
pinned: { type: "boolean", description: "Pin/unpin the memory" },
|
|
13128
16320
|
status: { type: "string", description: "New status", enum: ["active", "archived", "expired"] },
|
|
13129
16321
|
metadata: { type: "object", description: "New metadata (replaces existing)" },
|
|
13130
|
-
expires_at: { type: "string", description: "New expiry ISO timestamp (null to clear)" }
|
|
16322
|
+
expires_at: { type: "string", description: "New expiry ISO timestamp (null to clear)" },
|
|
16323
|
+
when_to_use: { type: "string", description: "Update the activation context for this memory" }
|
|
13131
16324
|
},
|
|
13132
16325
|
example: '{"id":"abc123","version":1,"importance":9,"tags":["correction","important"]}'
|
|
13133
16326
|
},
|
|
@@ -13217,6 +16410,18 @@ var FULL_SCHEMAS = {
|
|
|
13217
16410
|
},
|
|
13218
16411
|
example: '{"query":"database migration","limit":5}'
|
|
13219
16412
|
},
|
|
16413
|
+
memory_recall_deep: {
|
|
16414
|
+
description: "Deep memory recall using ASMR 3-agent search (facts, context, temporal). Modes: fast (hybrid), deep (ASMR), auto (fast then escalate). Optional ensemble answering.",
|
|
16415
|
+
category: "memory",
|
|
16416
|
+
params: {
|
|
16417
|
+
query: { type: "string", description: "Natural language query", required: true },
|
|
16418
|
+
mode: { type: "string", description: "fast=FTS+semantic, deep=ASMR 3-agent, auto=fast then escalate", enum: ["fast", "deep", "auto"] },
|
|
16419
|
+
max_results: { type: "number", description: "Max results (default 20)" },
|
|
16420
|
+
ensemble: { type: "boolean", description: "Use ensemble answering with majority voting (default false)" },
|
|
16421
|
+
project_id: { type: "string", description: "Project UUID filter" }
|
|
16422
|
+
},
|
|
16423
|
+
example: '{"query":"what is the deployment process","mode":"deep","ensemble":true}'
|
|
16424
|
+
},
|
|
13220
16425
|
memory_activity: {
|
|
13221
16426
|
description: "Get daily memory creation counts over N days (max 365). Like 'git log --stat' for memories.",
|
|
13222
16427
|
category: "memory",
|
|
@@ -13255,7 +16460,7 @@ var FULL_SCHEMAS = {
|
|
|
13255
16460
|
example: '{"memories":[{"key":"foo","value":"bar","scope":"global","importance":7}]}'
|
|
13256
16461
|
},
|
|
13257
16462
|
memory_inject: {
|
|
13258
|
-
description: "Get formatted memory context for system prompt injection. Respects token budget. Use strategy='smart' with
|
|
16463
|
+
description: "Get formatted memory context for system prompt injection. Respects token budget. Use strategy='smart' with task_context for full activation-matched + layered + tool-aware pipeline. Use mode='hints' for a lightweight topic summary (60-70% fewer tokens) \u2014 agent can then use memory_recall for details on demand.",
|
|
13259
16464
|
category: "memory",
|
|
13260
16465
|
params: {
|
|
13261
16466
|
agent_id: { type: "string", description: "Agent UUID to include private memories" },
|
|
@@ -13266,10 +16471,12 @@ var FULL_SCHEMAS = {
|
|
|
13266
16471
|
min_importance: { type: "number", description: "Minimum importance (default 3)" },
|
|
13267
16472
|
format: { type: "string", description: "Output format: xml (default, <agent-memories>), compact (key: value, ~60% smaller), markdown, json", enum: ["xml", "compact", "markdown", "json"] },
|
|
13268
16473
|
raw: { type: "boolean", description: "Deprecated: use format=compact instead. true=plain lines only" },
|
|
13269
|
-
strategy: { type: "string", description: "Injection strategy: 'default' (importance+recency)
|
|
13270
|
-
query: { type: "string", description: "Query for smart injection relevance scoring. Required when strategy='smart'." }
|
|
16474
|
+
strategy: { type: "string", description: "Injection strategy: 'default' = decay-scored (importance+recency), 'smart' = full pipeline (activation-matched + layered + tool-aware). Smart requires task_context.", enum: ["default", "smart"] },
|
|
16475
|
+
query: { type: "string", description: "Query for smart injection relevance scoring. Required when strategy='smart'." },
|
|
16476
|
+
task_context: { type: "string", description: "What the agent is about to do. Required for strategy='smart'. Activates intent-based retrieval \u2014 matches against when_to_use fields for situationally relevant memories." },
|
|
16477
|
+
mode: { type: "string", description: "Injection mode: 'full' (default) = inject complete memory content, 'hints' = lightweight topic summary with counts per category, saving 60-70% tokens. In hints mode, use memory_recall(key=...) or memory_search(query=...) to pull details on demand.", enum: ["full", "hints"] }
|
|
13271
16478
|
},
|
|
13272
|
-
example: '{"project_id":"proj-uuid","max_tokens":300,"
|
|
16479
|
+
example: '{"project_id":"proj-uuid","max_tokens":300,"strategy":"smart","task_context":"writing database migration for user table"}'
|
|
13273
16480
|
},
|
|
13274
16481
|
session_extract: {
|
|
13275
16482
|
description: "Auto-create memories from a session summary (title, topics, notes, project). Designed for sessions\u2192mementos integration.",
|
|
@@ -13288,15 +16495,28 @@ var FULL_SCHEMAS = {
|
|
|
13288
16495
|
example: '{"session_id":"abc123","title":"Fix auth middleware","project":"alumia","key_topics":["jwt","compliance"],"agent_id":"galba-id"}'
|
|
13289
16496
|
},
|
|
13290
16497
|
memory_context: {
|
|
13291
|
-
description: "Get active memories for the current context (agent/project/scope).",
|
|
16498
|
+
description: "Get active memories for the current context (agent/project/scope). Supports intent-based retrieval via task_context. Use strategy='smart' for full activation-matched + layered + tool-aware pipeline.",
|
|
13292
16499
|
category: "memory",
|
|
13293
16500
|
params: {
|
|
13294
16501
|
agent_id: { type: "string", description: "Agent UUID filter" },
|
|
13295
16502
|
project_id: { type: "string", description: "Project UUID filter" },
|
|
13296
16503
|
scope: { type: "string", description: "Scope filter", enum: ["global", "shared", "private", "working"] },
|
|
13297
|
-
limit: { type: "number", description: "Max results (default 30)" }
|
|
16504
|
+
limit: { type: "number", description: "Max results (default 30)" },
|
|
16505
|
+
task_context: { type: "string", description: "What the agent is about to do. Required for strategy='smart'. Activates intent-based retrieval \u2014 matches against when_to_use fields for situationally relevant memories." },
|
|
16506
|
+
strategy: { type: "string", description: "Injection strategy: 'default' = decay-scored, 'smart' = activation-matched + layered + tool-aware (requires task_context)", enum: ["default", "smart"] }
|
|
16507
|
+
},
|
|
16508
|
+
example: '{"project_id":"proj-uuid","scope":"shared","limit":20,"strategy":"smart","task_context":"deploying to production"}'
|
|
16509
|
+
},
|
|
16510
|
+
memory_profile: {
|
|
16511
|
+
description: "Synthesize a coherent profile from preference and fact memories using LLM. Cached for 24h, auto-refreshed when preferences change.",
|
|
16512
|
+
category: "memory",
|
|
16513
|
+
params: {
|
|
16514
|
+
project_id: { type: "string", description: "Project UUID to scope profile to" },
|
|
16515
|
+
agent_id: { type: "string", description: "Agent UUID to scope profile to" },
|
|
16516
|
+
scope: { type: "string", description: "Profile scope", enum: ["agent", "project", "global"] },
|
|
16517
|
+
force_refresh: { type: "boolean", description: "Force re-synthesis even if cached profile exists (default false)" }
|
|
13298
16518
|
},
|
|
13299
|
-
example: '{"project_id":"proj-uuid","scope":"
|
|
16519
|
+
example: '{"project_id":"proj-uuid","scope":"project"}'
|
|
13300
16520
|
},
|
|
13301
16521
|
register_agent: {
|
|
13302
16522
|
description: "Register an agent. Idempotent \u2014 same name returns existing agent.",
|
|
@@ -13637,6 +16857,68 @@ var FULL_SCHEMAS = {
|
|
|
13637
16857
|
},
|
|
13638
16858
|
example: '{"id":"sub-abc12"}'
|
|
13639
16859
|
},
|
|
16860
|
+
memory_tool_insights: {
|
|
16861
|
+
description: "Get usage stats, lessons learned, and recommendations for MCP tools. Helps agents avoid past mistakes and reuse successful patterns.",
|
|
16862
|
+
category: "utility",
|
|
16863
|
+
params: {
|
|
16864
|
+
tool_name: { type: "string", description: "Specific tool to get insights for. If omitted, returns insights for all tools." },
|
|
16865
|
+
task_context: { type: "string", description: "What the agent is about to do \u2014 used to find relevant tool lessons via semantic match" },
|
|
16866
|
+
project_id: { type: "string", description: "Project ID filter" },
|
|
16867
|
+
agent_id: { type: "string", description: "Agent ID filter" },
|
|
16868
|
+
limit: { type: "number", description: "Max lessons to return per tool (default: 10)" }
|
|
16869
|
+
},
|
|
16870
|
+
example: '{"tool_name":"bash","limit":5}'
|
|
16871
|
+
},
|
|
16872
|
+
memory_save_tool_event: {
|
|
16873
|
+
description: "Record a tool call event (success/failure, latency, tokens). Optionally saves a lesson as a shared memory.",
|
|
16874
|
+
category: "memory",
|
|
16875
|
+
params: {
|
|
16876
|
+
tool_name: { type: "string", description: "Name of the tool that was called", required: true },
|
|
16877
|
+
action: { type: "string", description: "What was attempted" },
|
|
16878
|
+
success: { type: "boolean", description: "Whether the tool call succeeded", required: true },
|
|
16879
|
+
error_type: { type: "string", description: "Error category if failed", enum: ["timeout", "permission", "not_found", "syntax", "rate_limit", "other"] },
|
|
16880
|
+
error_message: { type: "string", description: "Raw error text if failed" },
|
|
16881
|
+
tokens_used: { type: "number", description: "Tokens consumed by the tool call" },
|
|
16882
|
+
latency_ms: { type: "number", description: "Time taken in milliseconds" },
|
|
16883
|
+
context: { type: "string", description: "What task triggered this tool call" },
|
|
16884
|
+
lesson: { type: "string", description: "Qualitative insight learned from this call" },
|
|
16885
|
+
when_to_use: { type: "string", description: "Activation context for the lesson" },
|
|
16886
|
+
agent_id: { type: "string", description: "Agent ID" },
|
|
16887
|
+
project_id: { type: "string", description: "Project ID" },
|
|
16888
|
+
session_id: { type: "string", description: "Session ID" }
|
|
16889
|
+
},
|
|
16890
|
+
example: '{"tool_name":"bash","action":"npm install","success":false,"error_type":"timeout","lesson":"npm install hangs on large monorepos \u2014 use --prefer-offline","when_to_use":"when installing deps in a monorepo"}'
|
|
16891
|
+
},
|
|
16892
|
+
memory_autoinject_config: {
|
|
16893
|
+
description: "Get or set auto-inject orchestrator config (channel-based proactive memory push). Controls throttle, debounce, rate limits, similarity thresholds.",
|
|
16894
|
+
category: "utility",
|
|
16895
|
+
params: {
|
|
16896
|
+
action: { type: "string", description: "Get or set auto-inject config", enum: ["get", "set"], required: true },
|
|
16897
|
+
throttle_ms: { type: "number", description: "Min ms between pushes (default 30000)" },
|
|
16898
|
+
debounce_ms: { type: "number", description: "Wait ms after last message before processing (default 2000)" },
|
|
16899
|
+
max_pushes_per_5min: { type: "number", description: "Rate limit per 5-minute window (default 5)" },
|
|
16900
|
+
min_similarity: { type: "number", description: "Minimum activation match threshold 0-1 (default 0.4)" },
|
|
16901
|
+
enabled: { type: "boolean", description: "Enable/disable auto-inject" },
|
|
16902
|
+
session_briefing: { type: "boolean", description: "Push session-start briefing (default true)" }
|
|
16903
|
+
},
|
|
16904
|
+
example: '{"action":"set","throttle_ms":15000,"min_similarity":0.5}'
|
|
16905
|
+
},
|
|
16906
|
+
memory_autoinject_status: {
|
|
16907
|
+
description: "Get auto-inject orchestrator status: running state, session watcher, push history, rate limit counters, and full config.",
|
|
16908
|
+
category: "utility",
|
|
16909
|
+
params: {},
|
|
16910
|
+
example: "{}"
|
|
16911
|
+
},
|
|
16912
|
+
memory_autoinject_test: {
|
|
16913
|
+
description: "Test what memories would be activated by a given context WITHOUT pushing. Shows what the auto-inject pipeline would match \u2014 useful for tuning min_similarity.",
|
|
16914
|
+
category: "utility",
|
|
16915
|
+
params: {
|
|
16916
|
+
context_text: { type: "string", description: "Simulated context to test activation matching", required: true },
|
|
16917
|
+
project_id: { type: "string", description: "Scope to a specific project" },
|
|
16918
|
+
min_similarity: { type: "number", description: "Minimum similarity threshold (default 0.4)" }
|
|
16919
|
+
},
|
|
16920
|
+
example: '{"context_text":"debugging a SQLite FTS5 index issue","min_similarity":0.3}'
|
|
16921
|
+
},
|
|
13640
16922
|
search_tools: {
|
|
13641
16923
|
description: "Search available tools by name or keyword. Returns matching tool names and categories.",
|
|
13642
16924
|
category: "meta",
|
|
@@ -14333,6 +17615,149 @@ server.tool("memory_unsubscribe", "Remove a memory subscription by ID.", {
|
|
|
14333
17615
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
14334
17616
|
}
|
|
14335
17617
|
});
|
|
17618
|
+
server.tool("memory_save_tool_event", "Record a tool call event (success/failure, latency, tokens). Optionally saves a lesson as a shared memory.", {
|
|
17619
|
+
tool_name: exports_external.string().describe("Name of the tool that was called (e.g. 'bash', 'read', 'grep')"),
|
|
17620
|
+
action: exports_external.string().optional().describe("What was attempted (e.g. 'npm install', 'git push')"),
|
|
17621
|
+
success: exports_external.boolean().describe("Whether the tool call succeeded"),
|
|
17622
|
+
error_type: exports_external.enum(["timeout", "permission", "not_found", "syntax", "rate_limit", "other"]).optional().describe("Error category if failed"),
|
|
17623
|
+
error_message: exports_external.string().optional().describe("Raw error text if failed"),
|
|
17624
|
+
tokens_used: exports_external.number().optional().describe("Tokens consumed by the tool call"),
|
|
17625
|
+
latency_ms: exports_external.number().optional().describe("Time taken in milliseconds"),
|
|
17626
|
+
context: exports_external.string().optional().describe("What task triggered this tool call"),
|
|
17627
|
+
lesson: exports_external.string().optional().describe("Qualitative insight learned from this call"),
|
|
17628
|
+
when_to_use: exports_external.string().optional().describe("Activation context for the lesson"),
|
|
17629
|
+
agent_id: exports_external.string().optional(),
|
|
17630
|
+
project_id: exports_external.string().optional(),
|
|
17631
|
+
session_id: exports_external.string().optional()
|
|
17632
|
+
}, async (args) => {
|
|
17633
|
+
try {
|
|
17634
|
+
const event = saveToolEvent(args);
|
|
17635
|
+
if (args.lesson) {
|
|
17636
|
+
try {
|
|
17637
|
+
createMemory({
|
|
17638
|
+
key: `tool-lesson-${args.tool_name}-${Date.now()}`,
|
|
17639
|
+
value: args.lesson,
|
|
17640
|
+
category: "knowledge",
|
|
17641
|
+
scope: "shared",
|
|
17642
|
+
importance: 7,
|
|
17643
|
+
tags: ["tool-memory", args.tool_name],
|
|
17644
|
+
when_to_use: args.when_to_use,
|
|
17645
|
+
agent_id: args.agent_id,
|
|
17646
|
+
project_id: args.project_id,
|
|
17647
|
+
session_id: args.session_id,
|
|
17648
|
+
source: "auto"
|
|
17649
|
+
});
|
|
17650
|
+
} catch {}
|
|
17651
|
+
}
|
|
17652
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
17653
|
+
id: event.id,
|
|
17654
|
+
tool_name: event.tool_name,
|
|
17655
|
+
success: event.success,
|
|
17656
|
+
error_type: event.error_type,
|
|
17657
|
+
lesson_saved: !!args.lesson,
|
|
17658
|
+
created_at: event.created_at
|
|
17659
|
+
}) }] };
|
|
17660
|
+
} catch (e) {
|
|
17661
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17662
|
+
}
|
|
17663
|
+
});
|
|
17664
|
+
server.tool("memory_autoinject_config", "Get or set auto-inject orchestrator config (channel-based memory push). Controls throttle, debounce, rate limits, and similarity thresholds for proactive memory injection.", {
|
|
17665
|
+
action: exports_external.enum(["get", "set"]).describe("Get or set auto-inject config"),
|
|
17666
|
+
throttle_ms: exports_external.number().optional().describe("Min ms between pushes (default 30000)"),
|
|
17667
|
+
debounce_ms: exports_external.number().optional().describe("Wait ms after last message before processing (default 2000)"),
|
|
17668
|
+
max_pushes_per_5min: exports_external.number().optional().describe("Rate limit per 5-minute window (default 5)"),
|
|
17669
|
+
min_similarity: exports_external.number().optional().describe("Minimum activation match threshold 0-1 (default 0.4)"),
|
|
17670
|
+
enabled: exports_external.boolean().optional().describe("Enable/disable auto-inject"),
|
|
17671
|
+
session_briefing: exports_external.boolean().optional().describe("Push session-start briefing (default true)")
|
|
17672
|
+
}, async (args) => {
|
|
17673
|
+
try {
|
|
17674
|
+
if (args.action === "get") {
|
|
17675
|
+
return { content: [{ type: "text", text: JSON.stringify(getAutoInjectConfig(), null, 2) }] };
|
|
17676
|
+
}
|
|
17677
|
+
const updates = {};
|
|
17678
|
+
if (args.throttle_ms !== undefined)
|
|
17679
|
+
updates.throttle_ms = args.throttle_ms;
|
|
17680
|
+
if (args.debounce_ms !== undefined)
|
|
17681
|
+
updates.debounce_ms = args.debounce_ms;
|
|
17682
|
+
if (args.max_pushes_per_5min !== undefined)
|
|
17683
|
+
updates.max_pushes_per_5min = args.max_pushes_per_5min;
|
|
17684
|
+
if (args.min_similarity !== undefined)
|
|
17685
|
+
updates.min_similarity = args.min_similarity;
|
|
17686
|
+
if (args.enabled !== undefined)
|
|
17687
|
+
updates.enabled = args.enabled;
|
|
17688
|
+
if (args.session_briefing !== undefined)
|
|
17689
|
+
updates.session_briefing = args.session_briefing;
|
|
17690
|
+
const updated = updateAutoInjectConfig(updates);
|
|
17691
|
+
return { content: [{ type: "text", text: JSON.stringify({ updated: true, config: updated }, null, 2) }] };
|
|
17692
|
+
} catch (e) {
|
|
17693
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17694
|
+
}
|
|
17695
|
+
});
|
|
17696
|
+
server.tool("memory_autoinject_status", "Get auto-inject orchestrator status: running state, session watcher, push history, rate limit counters, and full config.", {}, async () => {
|
|
17697
|
+
try {
|
|
17698
|
+
const status = getAutoInjectStatus();
|
|
17699
|
+
const lines = [
|
|
17700
|
+
`Running: ${status.running}`,
|
|
17701
|
+
`Session ID: ${status.session_id || "none"}`,
|
|
17702
|
+
`Watcher active: ${status.watcher.active}`,
|
|
17703
|
+
`Watching file: ${status.watcher.watching_file || "none"}`,
|
|
17704
|
+
`Last offset: ${status.watcher.last_offset}`,
|
|
17705
|
+
``,
|
|
17706
|
+
`Pushes:`,
|
|
17707
|
+
` Total: ${status.pushes.total}`,
|
|
17708
|
+
` Last 5 min: ${status.pushes.last_5min}`,
|
|
17709
|
+
` Recently pushed memories: ${status.pushes.recently_pushed_memories}`,
|
|
17710
|
+
` Next available in: ${status.pushes.next_available_in_ms}ms`,
|
|
17711
|
+
``,
|
|
17712
|
+
`Config:`,
|
|
17713
|
+
` Enabled: ${status.config.enabled}`,
|
|
17714
|
+
` Throttle: ${status.config.throttle_ms}ms`,
|
|
17715
|
+
` Debounce: ${status.config.debounce_ms}ms`,
|
|
17716
|
+
` Max pushes/5min: ${status.config.max_pushes_per_5min}`,
|
|
17717
|
+
` Min similarity: ${status.config.min_similarity}`,
|
|
17718
|
+
` Session briefing: ${status.config.session_briefing}`
|
|
17719
|
+
];
|
|
17720
|
+
if (status.history.length > 0) {
|
|
17721
|
+
lines.push(``, `Recent push history:`);
|
|
17722
|
+
for (const h of status.history) {
|
|
17723
|
+
lines.push(` [${h.timestamp}] ${h.memory_count} memories \u2014 "${h.context}"`);
|
|
17724
|
+
}
|
|
17725
|
+
}
|
|
17726
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
17727
|
+
`) }] };
|
|
17728
|
+
} catch (e) {
|
|
17729
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17730
|
+
}
|
|
17731
|
+
});
|
|
17732
|
+
server.tool("memory_autoinject_test", "Test what memories would be activated by a given context WITHOUT pushing. Shows what the auto-inject pipeline would match \u2014 useful for tuning min_similarity.", {
|
|
17733
|
+
context_text: exports_external.string().describe("Simulated context to test activation matching"),
|
|
17734
|
+
project_id: exports_external.string().optional().describe("Scope to a specific project"),
|
|
17735
|
+
min_similarity: exports_external.number().optional().default(0.4).describe("Minimum similarity threshold (default 0.4)")
|
|
17736
|
+
}, async (args) => {
|
|
17737
|
+
try {
|
|
17738
|
+
const memories = await findActivatedMemories(args.context_text, {
|
|
17739
|
+
project_id: args.project_id,
|
|
17740
|
+
min_similarity: args.min_similarity
|
|
17741
|
+
});
|
|
17742
|
+
if (memories.length === 0) {
|
|
17743
|
+
return { content: [{ type: "text", text: "No memories matched the given context. Try lowering min_similarity or broadening the context text." }] };
|
|
17744
|
+
}
|
|
17745
|
+
const lines = [`${memories.length} memories would be activated:
|
|
17746
|
+
`];
|
|
17747
|
+
for (const m of memories) {
|
|
17748
|
+
lines.push(`- [${m.id.slice(0, 8)}] ${m.key}: ${m.value.slice(0, 200)}${m.value.length > 200 ? "\u2026" : ""}`);
|
|
17749
|
+
if (m.tags.length > 0)
|
|
17750
|
+
lines.push(` Tags: ${m.tags.join(", ")}`);
|
|
17751
|
+
lines.push(` Scope: ${m.scope} | Importance: ${m.importance}/10`);
|
|
17752
|
+
}
|
|
17753
|
+
lines.push(`
|
|
17754
|
+
Note: DRY RUN \u2014 nothing was pushed.`);
|
|
17755
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
17756
|
+
`) }] };
|
|
17757
|
+
} catch (e) {
|
|
17758
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
17759
|
+
}
|
|
17760
|
+
});
|
|
14336
17761
|
async function ensureRestServerRunning() {
|
|
14337
17762
|
try {
|
|
14338
17763
|
const res = await fetch("http://127.0.0.1:19428/api/memories?limit=0", {
|
|
@@ -14363,8 +17788,26 @@ async function main() {
|
|
|
14363
17788
|
loadWebhooksFromDb();
|
|
14364
17789
|
const transport = new StdioServerTransport;
|
|
14365
17790
|
await server.connect(transport);
|
|
17791
|
+
const autoProject = detectProject();
|
|
17792
|
+
startAutoInject({
|
|
17793
|
+
server,
|
|
17794
|
+
project_id: autoProject?.id,
|
|
17795
|
+
project_name: autoProject?.name,
|
|
17796
|
+
cwd: process.cwd()
|
|
17797
|
+
}).catch(() => {});
|
|
17798
|
+
process.on("SIGINT", () => {
|
|
17799
|
+
stopAutoInject();
|
|
17800
|
+
process.exit(0);
|
|
17801
|
+
});
|
|
17802
|
+
process.on("SIGTERM", () => {
|
|
17803
|
+
stopAutoInject();
|
|
17804
|
+
process.exit(0);
|
|
17805
|
+
});
|
|
14366
17806
|
}
|
|
14367
17807
|
main().catch((error) => {
|
|
14368
17808
|
console.error("MCP server error:", error);
|
|
14369
17809
|
process.exit(1);
|
|
14370
17810
|
});
|
|
17811
|
+
export {
|
|
17812
|
+
mcpServer
|
|
17813
|
+
};
|