@smyslenny/agent-memory 2.1.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -27
- package/README.en.md +153 -0
- package/README.md +95 -122
- package/dist/bin/agent-memory.js +28 -485
- package/dist/bin/agent-memory.js.map +1 -1
- package/dist/index.d.ts +53 -143
- package/dist/index.js +289 -574
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +585 -631
- package/dist/mcp/server.js.map +1 -1
- package/docs/design/0004-agent-memory-integration.md +316 -0
- package/docs/design/0005-reranker-api-integration.md +276 -0
- package/docs/design/0006-multi-provider-embedding.md +196 -0
- package/docs/design/0014-memory-core-dedup.md +722 -0
- package/docs/design/TEMPLATE.md +67 -0
- package/docs/roadmap/integration-plan-v1.md +139 -0
- package/docs/roadmap/memory-architecture.md +168 -0
- package/docs/roadmap/warm-boot.md +135 -0
- package/package.json +2 -3
- package/README.zh-CN.md +0 -170
package/dist/index.js
CHANGED
|
@@ -565,108 +565,6 @@ function deletePath(db, id) {
|
|
|
565
565
|
return result.changes > 0;
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
-
// src/core/link.ts
|
|
569
|
-
function createLink(db, sourceId, targetId, relation, weight = 1, agent_id) {
|
|
570
|
-
const sourceAgent = db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(sourceId)?.agent_id;
|
|
571
|
-
const targetAgent = db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(targetId)?.agent_id;
|
|
572
|
-
if (!sourceAgent) throw new Error(`Source memory not found: ${sourceId}`);
|
|
573
|
-
if (!targetAgent) throw new Error(`Target memory not found: ${targetId}`);
|
|
574
|
-
if (sourceAgent !== targetAgent) throw new Error("Cross-agent links are not allowed");
|
|
575
|
-
if (agent_id && agent_id !== sourceAgent) throw new Error("Agent mismatch for link");
|
|
576
|
-
const agentId = agent_id ?? sourceAgent;
|
|
577
|
-
db.prepare(
|
|
578
|
-
`INSERT OR REPLACE INTO links (agent_id, source_id, target_id, relation, weight, created_at)
|
|
579
|
-
VALUES (?, ?, ?, ?, ?, ?)`
|
|
580
|
-
).run(agentId, sourceId, targetId, relation, weight, now());
|
|
581
|
-
return { agent_id: agentId, source_id: sourceId, target_id: targetId, relation, weight, created_at: now() };
|
|
582
|
-
}
|
|
583
|
-
function getLinks(db, memoryId, agent_id) {
|
|
584
|
-
const agentId = agent_id ?? db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(memoryId)?.agent_id ?? "default";
|
|
585
|
-
return db.prepare("SELECT * FROM links WHERE agent_id = ? AND (source_id = ? OR target_id = ?)").all(agentId, memoryId, memoryId);
|
|
586
|
-
}
|
|
587
|
-
function getOutgoingLinks(db, sourceId, agent_id) {
|
|
588
|
-
const agentId = agent_id ?? db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(sourceId)?.agent_id ?? "default";
|
|
589
|
-
return db.prepare("SELECT * FROM links WHERE agent_id = ? AND source_id = ?").all(agentId, sourceId);
|
|
590
|
-
}
|
|
591
|
-
function traverse(db, startId, maxHops = 2, agent_id) {
|
|
592
|
-
const agentId = agent_id ?? db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(startId)?.agent_id ?? "default";
|
|
593
|
-
const visited = /* @__PURE__ */ new Set();
|
|
594
|
-
const results = [];
|
|
595
|
-
const queue = [
|
|
596
|
-
{ id: startId, hop: 0, relation: "self" }
|
|
597
|
-
];
|
|
598
|
-
while (queue.length > 0) {
|
|
599
|
-
const current = queue.shift();
|
|
600
|
-
if (visited.has(current.id)) continue;
|
|
601
|
-
visited.add(current.id);
|
|
602
|
-
if (current.hop > 0) {
|
|
603
|
-
results.push(current);
|
|
604
|
-
}
|
|
605
|
-
if (current.hop < maxHops) {
|
|
606
|
-
const links = db.prepare("SELECT target_id, relation FROM links WHERE agent_id = ? AND source_id = ?").all(agentId, current.id);
|
|
607
|
-
for (const link of links) {
|
|
608
|
-
if (!visited.has(link.target_id)) {
|
|
609
|
-
queue.push({
|
|
610
|
-
id: link.target_id,
|
|
611
|
-
hop: current.hop + 1,
|
|
612
|
-
relation: link.relation
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
const reverseLinks = db.prepare("SELECT source_id, relation FROM links WHERE agent_id = ? AND target_id = ?").all(agentId, current.id);
|
|
617
|
-
for (const link of reverseLinks) {
|
|
618
|
-
if (!visited.has(link.source_id)) {
|
|
619
|
-
queue.push({
|
|
620
|
-
id: link.source_id,
|
|
621
|
-
hop: current.hop + 1,
|
|
622
|
-
relation: link.relation
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
return results;
|
|
629
|
-
}
|
|
630
|
-
function deleteLink(db, sourceId, targetId, agent_id) {
|
|
631
|
-
const agentId = agent_id ?? db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(sourceId)?.agent_id ?? "default";
|
|
632
|
-
const result = db.prepare("DELETE FROM links WHERE agent_id = ? AND source_id = ? AND target_id = ?").run(agentId, sourceId, targetId);
|
|
633
|
-
return result.changes > 0;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// src/core/snapshot.ts
|
|
637
|
-
function createSnapshot(db, memoryId, action, changedBy) {
|
|
638
|
-
const memory = db.prepare("SELECT content FROM memories WHERE id = ?").get(memoryId);
|
|
639
|
-
if (!memory) throw new Error(`Memory not found: ${memoryId}`);
|
|
640
|
-
const id = newId();
|
|
641
|
-
db.prepare(
|
|
642
|
-
`INSERT INTO snapshots (id, memory_id, content, changed_by, action, created_at)
|
|
643
|
-
VALUES (?, ?, ?, ?, ?, ?)`
|
|
644
|
-
).run(id, memoryId, memory.content, changedBy ?? null, action, now());
|
|
645
|
-
return { id, memory_id: memoryId, content: memory.content, changed_by: changedBy ?? null, action, created_at: now() };
|
|
646
|
-
}
|
|
647
|
-
function getSnapshots(db, memoryId) {
|
|
648
|
-
return db.prepare("SELECT * FROM snapshots WHERE memory_id = ? ORDER BY created_at DESC").all(memoryId);
|
|
649
|
-
}
|
|
650
|
-
function getSnapshot(db, id) {
|
|
651
|
-
return db.prepare("SELECT * FROM snapshots WHERE id = ?").get(id) ?? null;
|
|
652
|
-
}
|
|
653
|
-
function rollback(db, snapshotId) {
|
|
654
|
-
const snapshot = getSnapshot(db, snapshotId);
|
|
655
|
-
if (!snapshot) return false;
|
|
656
|
-
createSnapshot(db, snapshot.memory_id, "update", "rollback");
|
|
657
|
-
db.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?").run(
|
|
658
|
-
snapshot.content,
|
|
659
|
-
now(),
|
|
660
|
-
snapshot.memory_id
|
|
661
|
-
);
|
|
662
|
-
db.prepare("DELETE FROM memories_fts WHERE id = ?").run(snapshot.memory_id);
|
|
663
|
-
db.prepare("INSERT INTO memories_fts (id, content) VALUES (?, ?)").run(
|
|
664
|
-
snapshot.memory_id,
|
|
665
|
-
tokenizeForIndex(snapshot.content)
|
|
666
|
-
);
|
|
667
|
-
return true;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
568
|
// src/core/guard.ts
|
|
671
569
|
function guard(db, input) {
|
|
672
570
|
const hash = contentHash(input.content);
|
|
@@ -880,389 +778,314 @@ function buildFtsQuery(text) {
|
|
|
880
778
|
return tokens.map((w) => `"${w}"`).join(" OR ");
|
|
881
779
|
}
|
|
882
780
|
|
|
883
|
-
// src/
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
/(多少|几个|哪个|哪些|哪里)/,
|
|
894
|
-
// Chinese - lookup patterns
|
|
895
|
-
/(查一下|找一下|看看|搜一下)/,
|
|
896
|
-
/(.+)是什么$/
|
|
897
|
-
],
|
|
898
|
-
temporal: [
|
|
899
|
-
// English
|
|
900
|
-
/^(when|what time|how long)\b/i,
|
|
901
|
-
/\b(yesterday|today|tomorrow|last week|recently|ago|before|after)\b/i,
|
|
902
|
-
/\b(first|latest|newest|oldest|previous|next)\b/i,
|
|
903
|
-
// Chinese - time expressions
|
|
904
|
-
/什么时候/,
|
|
905
|
-
/(昨天|今天|明天|上周|下周|最近|以前|之前|之后|刚才|刚刚)/,
|
|
906
|
-
/(几月|几号|几点|多久|多长时间)/,
|
|
907
|
-
/(上次|下次|第一次|最后一次|那天|那时)/,
|
|
908
|
-
// Date patterns
|
|
909
|
-
/\d{4}[-/.]\d{1,2}/,
|
|
910
|
-
/\d{1,2}月\d{1,2}[日号]/,
|
|
911
|
-
// Chinese - temporal context
|
|
912
|
-
/(历史|记录|日志|以来|至今|期间)/
|
|
913
|
-
],
|
|
914
|
-
causal: [
|
|
915
|
-
// English
|
|
916
|
-
/^(why|how come|what caused)\b/i,
|
|
917
|
-
/\b(because|due to|reason|cause|result)\b/i,
|
|
918
|
-
// Chinese - causal questions
|
|
919
|
-
/为(什么|啥|何)/,
|
|
920
|
-
/(原因|导致|造成|引起|因为|所以|结果)/,
|
|
921
|
-
/(怎么回事|怎么了|咋回事|咋了)/,
|
|
922
|
-
/(为啥|凭啥|凭什么)/,
|
|
923
|
-
// Chinese - problem/diagnosis
|
|
924
|
-
/(出(了|了什么)?问题|报错|失败|出错|bug)/
|
|
925
|
-
],
|
|
926
|
-
exploratory: [
|
|
927
|
-
// English
|
|
928
|
-
/^(how|tell me about|explain|describe|show me)\b/i,
|
|
929
|
-
/^(what do you think|what about|any)\b/i,
|
|
930
|
-
/\b(overview|summary|list|compare)\b/i,
|
|
931
|
-
// Chinese - exploratory
|
|
932
|
-
/(怎么样|怎样|如何)/,
|
|
933
|
-
/(介绍|说说|讲讲|聊聊|谈谈)/,
|
|
934
|
-
/(有哪些|有什么|有没有)/,
|
|
935
|
-
/(关于|对于|至于|关联)/,
|
|
936
|
-
/(总结|概括|梳理|回顾|盘点)/,
|
|
937
|
-
// Chinese - opinion/analysis
|
|
938
|
-
/(看法|想法|意见|建议|评价|感觉|觉得)/,
|
|
939
|
-
/(对比|比较|区别|差异|优缺点)/
|
|
940
|
-
]
|
|
941
|
-
};
|
|
942
|
-
var CN_STRUCTURE_BOOSTS = {
|
|
943
|
-
factual: [/^.{1,6}(是什么|叫什么|在哪)/, /^(谁|哪)/],
|
|
944
|
-
temporal: [/^(什么时候|上次|最近)/, /(时间|日期)$/],
|
|
945
|
-
causal: [/^(为什么|为啥)/, /(为什么|怎么回事)$/],
|
|
946
|
-
exploratory: [/^(怎么|如何|说说)/, /(哪些|什么样)$/]
|
|
947
|
-
};
|
|
948
|
-
function classifyIntent(query) {
|
|
949
|
-
const scores = {
|
|
950
|
-
factual: 0,
|
|
951
|
-
exploratory: 0,
|
|
952
|
-
temporal: 0,
|
|
953
|
-
causal: 0
|
|
781
|
+
// src/sleep/sync.ts
|
|
782
|
+
function syncOne(db, input) {
|
|
783
|
+
const memInput = {
|
|
784
|
+
content: input.content,
|
|
785
|
+
type: input.type ?? "event",
|
|
786
|
+
priority: input.priority,
|
|
787
|
+
emotion_val: input.emotion_val,
|
|
788
|
+
source: input.source,
|
|
789
|
+
agent_id: input.agent_id,
|
|
790
|
+
uri: input.uri
|
|
954
791
|
};
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
792
|
+
const guardResult = guard(db, memInput);
|
|
793
|
+
switch (guardResult.action) {
|
|
794
|
+
case "skip":
|
|
795
|
+
return { action: "skipped", reason: guardResult.reason, memoryId: guardResult.existingId };
|
|
796
|
+
case "add": {
|
|
797
|
+
const mem = createMemory(db, memInput);
|
|
798
|
+
if (!mem) return { action: "skipped", reason: "createMemory returned null" };
|
|
799
|
+
if (input.uri) {
|
|
800
|
+
try {
|
|
801
|
+
createPath(db, mem.id, input.uri);
|
|
802
|
+
} catch {
|
|
803
|
+
}
|
|
959
804
|
}
|
|
805
|
+
return { action: "added", memoryId: mem.id, reason: guardResult.reason };
|
|
960
806
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
scores[intent] += 0.5;
|
|
966
|
-
}
|
|
807
|
+
case "update": {
|
|
808
|
+
if (!guardResult.existingId) return { action: "skipped", reason: "No existing ID for update" };
|
|
809
|
+
updateMemory(db, guardResult.existingId, { content: input.content });
|
|
810
|
+
return { action: "updated", memoryId: guardResult.existingId, reason: guardResult.reason };
|
|
967
811
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
let maxIntent = "factual";
|
|
975
|
-
let maxScore = 0;
|
|
976
|
-
for (const [intent, score] of Object.entries(scores)) {
|
|
977
|
-
if (score > maxScore) {
|
|
978
|
-
maxScore = score;
|
|
979
|
-
maxIntent = intent;
|
|
812
|
+
case "merge": {
|
|
813
|
+
if (!guardResult.existingId || !guardResult.mergedContent) {
|
|
814
|
+
return { action: "skipped", reason: "Missing merge data" };
|
|
815
|
+
}
|
|
816
|
+
updateMemory(db, guardResult.existingId, { content: guardResult.mergedContent });
|
|
817
|
+
return { action: "merged", memoryId: guardResult.existingId, reason: guardResult.reason };
|
|
980
818
|
}
|
|
981
819
|
}
|
|
982
|
-
const totalScore = Object.values(scores).reduce((a, b) => a + b, 0);
|
|
983
|
-
const confidence = totalScore > 0 ? Math.min(0.95, maxScore / totalScore) : 0.5;
|
|
984
|
-
return { intent: maxIntent, confidence };
|
|
985
820
|
}
|
|
986
|
-
function
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
return { boostRecent: true, boostPriority: false, limit: 10 };
|
|
992
|
-
case "causal":
|
|
993
|
-
return { boostRecent: false, boostPriority: false, limit: 10 };
|
|
994
|
-
case "exploratory":
|
|
995
|
-
return { boostRecent: false, boostPriority: false, limit: 15 };
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
// src/search/rerank.ts
|
|
1000
|
-
function rerank(results, opts) {
|
|
1001
|
-
const now2 = Date.now();
|
|
1002
|
-
const scored = results.map((r) => {
|
|
1003
|
-
let finalScore = r.score;
|
|
1004
|
-
if (opts.boostPriority) {
|
|
1005
|
-
const priorityMultiplier = [4, 3, 2, 1][r.memory.priority] ?? 1;
|
|
1006
|
-
finalScore *= priorityMultiplier;
|
|
1007
|
-
}
|
|
1008
|
-
if (opts.boostRecent && r.memory.updated_at) {
|
|
1009
|
-
const age = now2 - new Date(r.memory.updated_at).getTime();
|
|
1010
|
-
const daysSinceUpdate = age / (1e3 * 60 * 60 * 24);
|
|
1011
|
-
const recencyBoost = Math.max(0.1, 1 / (1 + daysSinceUpdate * 0.1));
|
|
1012
|
-
finalScore *= recencyBoost;
|
|
821
|
+
function syncBatch(db, inputs) {
|
|
822
|
+
const results = [];
|
|
823
|
+
const transaction = db.transaction(() => {
|
|
824
|
+
for (const input of inputs) {
|
|
825
|
+
results.push(syncOne(db, input));
|
|
1013
826
|
}
|
|
1014
|
-
finalScore *= Math.max(0.1, r.memory.vitality);
|
|
1015
|
-
return { ...r, score: finalScore };
|
|
1016
827
|
});
|
|
1017
|
-
|
|
1018
|
-
return
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// src/search/embeddings.ts
|
|
1022
|
-
function encodeEmbedding(vector) {
|
|
1023
|
-
const arr = vector instanceof Float32Array ? vector : Float32Array.from(vector);
|
|
1024
|
-
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1025
|
-
}
|
|
1026
|
-
function decodeEmbedding(buf) {
|
|
1027
|
-
const copy = Buffer.from(buf);
|
|
1028
|
-
return new Float32Array(copy.buffer, copy.byteOffset, Math.floor(copy.byteLength / 4));
|
|
1029
|
-
}
|
|
1030
|
-
function upsertEmbedding(db, input) {
|
|
1031
|
-
const ts = now();
|
|
1032
|
-
const vec = input.vector instanceof Float32Array ? input.vector : Float32Array.from(input.vector);
|
|
1033
|
-
const blob = encodeEmbedding(vec);
|
|
1034
|
-
db.prepare(
|
|
1035
|
-
`INSERT INTO embeddings (agent_id, memory_id, model, dim, vector, created_at, updated_at)
|
|
1036
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1037
|
-
ON CONFLICT(agent_id, memory_id, model) DO UPDATE SET
|
|
1038
|
-
dim = excluded.dim,
|
|
1039
|
-
vector = excluded.vector,
|
|
1040
|
-
updated_at = excluded.updated_at`
|
|
1041
|
-
).run(input.agent_id, input.memory_id, input.model, vec.length, blob, ts, ts);
|
|
1042
|
-
}
|
|
1043
|
-
function getEmbedding(db, agent_id, memory_id, model) {
|
|
1044
|
-
const row = db.prepare(
|
|
1045
|
-
"SELECT agent_id, memory_id, model, dim, vector, created_at, updated_at FROM embeddings WHERE agent_id = ? AND memory_id = ? AND model = ?"
|
|
1046
|
-
).get(agent_id, memory_id, model);
|
|
1047
|
-
if (!row) return null;
|
|
1048
|
-
return { ...row, vector: decodeEmbedding(row.vector) };
|
|
1049
|
-
}
|
|
1050
|
-
function listEmbeddings(db, agent_id, model) {
|
|
1051
|
-
const rows = db.prepare(
|
|
1052
|
-
"SELECT memory_id, vector FROM embeddings WHERE agent_id = ? AND model = ?"
|
|
1053
|
-
).all(agent_id, model);
|
|
1054
|
-
return rows.map((r) => ({ memory_id: r.memory_id, vector: decodeEmbedding(r.vector) }));
|
|
828
|
+
transaction();
|
|
829
|
+
return results;
|
|
1055
830
|
}
|
|
1056
831
|
|
|
1057
|
-
// src/
|
|
1058
|
-
function
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const y = b[i];
|
|
1066
|
-
dot += x * y;
|
|
1067
|
-
na += x * x;
|
|
1068
|
-
nb += y * y;
|
|
832
|
+
// src/ingest/ingest.ts
|
|
833
|
+
function slugify(input) {
|
|
834
|
+
return input.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, " ").trim().replace(/\s+/g, "-").slice(0, 64) || "item";
|
|
835
|
+
}
|
|
836
|
+
function classifyIngestType(text) {
|
|
837
|
+
const lower = text.toLowerCase();
|
|
838
|
+
if (/##\s*身份|\bidentity\b|\b我是\b|我是/.test(text)) {
|
|
839
|
+
return "identity";
|
|
1069
840
|
}
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
function rrfScore(rank, k) {
|
|
1074
|
-
return 1 / (k + rank);
|
|
1075
|
-
}
|
|
1076
|
-
function fuseRrf(lists, k) {
|
|
1077
|
-
const out = /* @__PURE__ */ new Map();
|
|
1078
|
-
for (const list of lists) {
|
|
1079
|
-
for (let i = 0; i < list.items.length; i++) {
|
|
1080
|
-
const it = list.items[i];
|
|
1081
|
-
const rank = i + 1;
|
|
1082
|
-
const add = rrfScore(rank, k);
|
|
1083
|
-
const prev = out.get(it.id);
|
|
1084
|
-
if (!prev) out.set(it.id, { score: add, sources: [list.name] });
|
|
1085
|
-
else {
|
|
1086
|
-
prev.score += add;
|
|
1087
|
-
if (!prev.sources.includes(list.name)) prev.sources.push(list.name);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
return out;
|
|
1092
|
-
}
|
|
1093
|
-
function fetchMemories(db, ids, agentId) {
|
|
1094
|
-
if (ids.length === 0) return [];
|
|
1095
|
-
const placeholders = ids.map(() => "?").join(", ");
|
|
1096
|
-
const sql = agentId ? `SELECT * FROM memories WHERE id IN (${placeholders}) AND agent_id = ?` : `SELECT * FROM memories WHERE id IN (${placeholders})`;
|
|
1097
|
-
const rows = db.prepare(sql).all(...agentId ? [...ids, agentId] : ids);
|
|
1098
|
-
return rows;
|
|
1099
|
-
}
|
|
1100
|
-
async function searchHybrid(db, query, opts) {
|
|
1101
|
-
const agentId = opts?.agent_id ?? "default";
|
|
1102
|
-
const limit = opts?.limit ?? 10;
|
|
1103
|
-
const bm25Mult = opts?.bm25CandidateMultiplier ?? 3;
|
|
1104
|
-
const semanticCandidates = opts?.semanticCandidates ?? 50;
|
|
1105
|
-
const rrfK = opts?.rrfK ?? 60;
|
|
1106
|
-
const bm25 = searchBM25(db, query, {
|
|
1107
|
-
agent_id: agentId,
|
|
1108
|
-
limit: limit * bm25Mult
|
|
1109
|
-
});
|
|
1110
|
-
const provider = opts?.embeddingProvider ?? null;
|
|
1111
|
-
const model = opts?.embeddingModel ?? provider?.model;
|
|
1112
|
-
if (!provider || !model) {
|
|
1113
|
-
return bm25.slice(0, limit);
|
|
841
|
+
if (/##\s*情感|❤️|💕|爱你|感动|难过|开心|害怕|想念|表白/.test(text)) {
|
|
842
|
+
return "emotion";
|
|
1114
843
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const scored = [];
|
|
1118
|
-
for (const e of embeddings) {
|
|
1119
|
-
scored.push({ id: e.memory_id, score: cosine(qVec, e.vector) });
|
|
844
|
+
if (/##\s*决策|##\s*技术|选型|教训|\bknowledge\b|⚠️|复盘|经验/.test(text)) {
|
|
845
|
+
return "knowledge";
|
|
1120
846
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
const fused = fuseRrf(
|
|
1124
|
-
[
|
|
1125
|
-
{ name: "bm25", items: bm25.map((r) => ({ id: r.memory.id, score: r.score })) },
|
|
1126
|
-
{ name: "semantic", items: semanticTop }
|
|
1127
|
-
],
|
|
1128
|
-
rrfK
|
|
1129
|
-
);
|
|
1130
|
-
const ids = [...fused.keys()];
|
|
1131
|
-
const memories = fetchMemories(db, ids, agentId);
|
|
1132
|
-
const byId = new Map(memories.map((m) => [m.id, m]));
|
|
1133
|
-
const out = [];
|
|
1134
|
-
for (const [id, meta] of fused) {
|
|
1135
|
-
const mem = byId.get(id);
|
|
1136
|
-
if (!mem) continue;
|
|
1137
|
-
out.push({
|
|
1138
|
-
memory: mem,
|
|
1139
|
-
score: meta.score,
|
|
1140
|
-
matchReason: meta.sources.sort().join("+")
|
|
1141
|
-
});
|
|
847
|
+
if (/\d{4}-\d{2}-\d{2}|发生了|完成了|今天|昨日|刚刚|部署|上线/.test(text)) {
|
|
848
|
+
return "event";
|
|
1142
849
|
}
|
|
1143
|
-
|
|
1144
|
-
return
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
const
|
|
1150
|
-
if (
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
850
|
+
if (lower.length <= 12) return "event";
|
|
851
|
+
return "knowledge";
|
|
852
|
+
}
|
|
853
|
+
function splitIngestBlocks(text) {
|
|
854
|
+
const headingRegex = /^##\s+(.+)$/gm;
|
|
855
|
+
const matches = [...text.matchAll(headingRegex)];
|
|
856
|
+
const blocks = [];
|
|
857
|
+
if (matches.length > 0) {
|
|
858
|
+
for (let i = 0; i < matches.length; i++) {
|
|
859
|
+
const match = matches[i];
|
|
860
|
+
const start = match.index ?? 0;
|
|
861
|
+
const end = i + 1 < matches.length ? matches[i + 1].index ?? text.length : text.length;
|
|
862
|
+
const raw = text.slice(start, end).trim();
|
|
863
|
+
const lines = raw.split("\n");
|
|
864
|
+
const title = lines[0].replace(/^##\s+/, "").trim();
|
|
865
|
+
const content = lines.slice(1).join("\n").trim();
|
|
866
|
+
if (content) blocks.push({ title, content });
|
|
867
|
+
}
|
|
868
|
+
return blocks;
|
|
1157
869
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
const baseUrl = process.env.DASHSCOPE_BASE_URL ?? "https://dashscope.aliyuncs.com";
|
|
1162
|
-
if (!apiKey) return null;
|
|
1163
|
-
return createDashScopeProvider({ apiKey, model, baseUrl });
|
|
870
|
+
const bullets = text.split("\n").map((line) => line.trim()).filter((line) => /^[-*]\s+/.test(line)).map((line) => line.replace(/^[-*]\s+/, "").trim()).filter(Boolean);
|
|
871
|
+
if (bullets.length > 0) {
|
|
872
|
+
return bullets.map((content, i) => ({ title: `bullet-${i + 1}`, content }));
|
|
1164
873
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
874
|
+
const plain = text.trim();
|
|
875
|
+
if (!plain) return [];
|
|
876
|
+
return [{ title: "ingest", content: plain }];
|
|
877
|
+
}
|
|
878
|
+
function extractIngestItems(text, source) {
|
|
879
|
+
const blocks = splitIngestBlocks(text);
|
|
880
|
+
return blocks.map((block, index) => {
|
|
881
|
+
const merged = `${block.title}
|
|
882
|
+
${block.content}`;
|
|
883
|
+
const type = classifyIngestType(merged);
|
|
884
|
+
const domain = type === "identity" ? "core" : type;
|
|
885
|
+
const sourcePart = slugify(source ?? "ingest");
|
|
886
|
+
const uri = `${domain}://ingest/${sourcePart}/${index + 1}-${slugify(block.title)}`;
|
|
887
|
+
return {
|
|
888
|
+
index,
|
|
889
|
+
title: block.title,
|
|
890
|
+
content: block.content,
|
|
891
|
+
type,
|
|
892
|
+
uri
|
|
893
|
+
};
|
|
1176
894
|
});
|
|
1177
895
|
}
|
|
1178
|
-
function
|
|
1179
|
-
const
|
|
896
|
+
function ingestText(db, options) {
|
|
897
|
+
const extracted = extractIngestItems(options.text, options.source);
|
|
898
|
+
const dryRun = options.dryRun ?? false;
|
|
899
|
+
const agentId = options.agentId ?? "default";
|
|
900
|
+
if (dryRun) {
|
|
901
|
+
return {
|
|
902
|
+
extracted: extracted.length,
|
|
903
|
+
written: 0,
|
|
904
|
+
skipped: extracted.length,
|
|
905
|
+
dry_run: true,
|
|
906
|
+
details: extracted.map((item) => ({
|
|
907
|
+
index: item.index,
|
|
908
|
+
type: item.type,
|
|
909
|
+
uri: item.uri,
|
|
910
|
+
preview: item.content.slice(0, 80)
|
|
911
|
+
}))
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
let written = 0;
|
|
915
|
+
let skipped = 0;
|
|
916
|
+
const details = [];
|
|
917
|
+
for (const item of extracted) {
|
|
918
|
+
const result = syncOne(db, {
|
|
919
|
+
content: item.content,
|
|
920
|
+
type: item.type,
|
|
921
|
+
uri: item.uri,
|
|
922
|
+
source: `auto:${options.source ?? "ingest"}`,
|
|
923
|
+
agent_id: agentId
|
|
924
|
+
});
|
|
925
|
+
if (result.action === "added" || result.action === "updated" || result.action === "merged") {
|
|
926
|
+
written++;
|
|
927
|
+
} else {
|
|
928
|
+
skipped++;
|
|
929
|
+
}
|
|
930
|
+
details.push({
|
|
931
|
+
index: item.index,
|
|
932
|
+
type: item.type,
|
|
933
|
+
uri: item.uri,
|
|
934
|
+
action: result.action,
|
|
935
|
+
reason: result.reason,
|
|
936
|
+
memoryId: result.memoryId
|
|
937
|
+
});
|
|
938
|
+
}
|
|
1180
939
|
return {
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
940
|
+
extracted: extracted.length,
|
|
941
|
+
written,
|
|
942
|
+
skipped,
|
|
943
|
+
dry_run: false,
|
|
944
|
+
details
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/ingest/watcher.ts
|
|
949
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync, watch } from "fs";
|
|
950
|
+
import { join as join2, relative, resolve } from "path";
|
|
951
|
+
function runAutoIngestWatcher(options) {
|
|
952
|
+
const workspaceDir = resolve(options.workspaceDir);
|
|
953
|
+
const memoryDir = join2(workspaceDir, "memory");
|
|
954
|
+
const memoryMdPath = join2(workspaceDir, "MEMORY.md");
|
|
955
|
+
const debounceMs = options.debounceMs ?? 1200;
|
|
956
|
+
const initialScan = options.initialScan ?? true;
|
|
957
|
+
const logger = options.logger ?? console;
|
|
958
|
+
const timers = /* @__PURE__ */ new Map();
|
|
959
|
+
const watchers = [];
|
|
960
|
+
const stats = {
|
|
961
|
+
triggers: 0,
|
|
962
|
+
filesProcessed: 0,
|
|
963
|
+
extracted: 0,
|
|
964
|
+
written: 0,
|
|
965
|
+
skipped: 0,
|
|
966
|
+
errors: 0
|
|
967
|
+
};
|
|
968
|
+
let stopped = false;
|
|
969
|
+
let queue = Promise.resolve();
|
|
970
|
+
const toSource = (absPath) => {
|
|
971
|
+
const rel = relative(workspaceDir, absPath).replace(/\\/g, "/");
|
|
972
|
+
return rel || absPath;
|
|
973
|
+
};
|
|
974
|
+
const isTrackedMarkdownFile = (absPath) => {
|
|
975
|
+
if (!absPath.endsWith(".md")) return false;
|
|
976
|
+
if (resolve(absPath) === memoryMdPath) return true;
|
|
977
|
+
const rel = relative(memoryDir, absPath).replace(/\\/g, "/");
|
|
978
|
+
if (rel.startsWith("..") || rel === "") return false;
|
|
979
|
+
return !rel.includes("/");
|
|
980
|
+
};
|
|
981
|
+
const ingestFile = (absPath, reason) => {
|
|
982
|
+
if (stopped) return;
|
|
983
|
+
if (!existsSync2(absPath)) {
|
|
984
|
+
logger.log(`[auto-ingest] skip missing file: ${toSource(absPath)} (reason=${reason})`);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
let isFile = false;
|
|
988
|
+
try {
|
|
989
|
+
isFile = statSync(absPath).isFile();
|
|
990
|
+
} catch (err) {
|
|
991
|
+
stats.errors += 1;
|
|
992
|
+
logger.warn(`[auto-ingest] stat failed for ${toSource(absPath)}: ${String(err)}`);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
if (!isFile) return;
|
|
996
|
+
try {
|
|
997
|
+
const text = readFileSync2(absPath, "utf-8");
|
|
998
|
+
const source = toSource(absPath);
|
|
999
|
+
const result = ingestText(options.db, {
|
|
1000
|
+
text,
|
|
1001
|
+
source,
|
|
1002
|
+
agentId: options.agentId
|
|
1191
1003
|
});
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1004
|
+
stats.filesProcessed += 1;
|
|
1005
|
+
stats.extracted += result.extracted;
|
|
1006
|
+
stats.written += result.written;
|
|
1007
|
+
stats.skipped += result.skipped;
|
|
1008
|
+
logger.log(
|
|
1009
|
+
`[auto-ingest] file=${source} reason=${reason} extracted=${result.extracted} written=${result.written} skipped=${result.skipped}`
|
|
1010
|
+
);
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
stats.errors += 1;
|
|
1013
|
+
logger.error(`[auto-ingest] ingest failed for ${toSource(absPath)}: ${String(err)}`);
|
|
1199
1014
|
}
|
|
1200
1015
|
};
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1214
|
-
body: JSON.stringify({
|
|
1215
|
-
model: opts.model,
|
|
1216
|
-
input: { texts: [text] }
|
|
1217
|
-
})
|
|
1016
|
+
const scheduleIngest = (absPath, reason) => {
|
|
1017
|
+
if (stopped) return;
|
|
1018
|
+
if (!isTrackedMarkdownFile(absPath)) return;
|
|
1019
|
+
stats.triggers += 1;
|
|
1020
|
+
const previous = timers.get(absPath);
|
|
1021
|
+
if (previous) clearTimeout(previous);
|
|
1022
|
+
const timer = setTimeout(() => {
|
|
1023
|
+
timers.delete(absPath);
|
|
1024
|
+
queue = queue.then(() => {
|
|
1025
|
+
ingestFile(absPath, reason);
|
|
1026
|
+
}).catch((err) => {
|
|
1027
|
+
stats.errors += 1;
|
|
1028
|
+
logger.error(`[auto-ingest] queue error: ${String(err)}`);
|
|
1218
1029
|
});
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
return
|
|
1030
|
+
}, debounceMs);
|
|
1031
|
+
timers.set(absPath, timer);
|
|
1032
|
+
};
|
|
1033
|
+
const safeWatch = (dir, onEvent) => {
|
|
1034
|
+
if (!existsSync2(dir)) {
|
|
1035
|
+
logger.warn(`[auto-ingest] watch path does not exist, skipping: ${dir}`);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
try {
|
|
1039
|
+
const watcher = watch(dir, { persistent: true }, (eventType, filename) => {
|
|
1040
|
+
if (!filename) return;
|
|
1041
|
+
onEvent(eventType, filename.toString());
|
|
1042
|
+
});
|
|
1043
|
+
watchers.push(watcher);
|
|
1044
|
+
logger.log(`[auto-ingest] watching ${dir}`);
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
stats.errors += 1;
|
|
1047
|
+
logger.error(`[auto-ingest] failed to watch ${dir}: ${String(err)}`);
|
|
1226
1048
|
}
|
|
1227
1049
|
};
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
const row = db.prepare("SELECT id, agent_id, content FROM memories WHERE id = ?").get(memoryId);
|
|
1233
|
-
if (!row) return false;
|
|
1234
|
-
if (opts?.agent_id && row.agent_id !== opts.agent_id) return false;
|
|
1235
|
-
const model = opts?.model ?? provider.model;
|
|
1236
|
-
const maxChars = opts?.maxChars ?? 2e3;
|
|
1237
|
-
const text = row.content.length > maxChars ? row.content.slice(0, maxChars) : row.content;
|
|
1238
|
-
const vector = await provider.embed(text);
|
|
1239
|
-
upsertEmbedding(db, {
|
|
1240
|
-
agent_id: row.agent_id,
|
|
1241
|
-
memory_id: row.id,
|
|
1242
|
-
model,
|
|
1243
|
-
vector
|
|
1050
|
+
safeWatch(workspaceDir, (eventType, filename) => {
|
|
1051
|
+
if (filename === "MEMORY.md") {
|
|
1052
|
+
scheduleIngest(join2(workspaceDir, filename), `workspace:${eventType}`);
|
|
1053
|
+
}
|
|
1244
1054
|
});
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
).all(model, agentId, limit);
|
|
1260
|
-
let embedded = 0;
|
|
1261
|
-
for (const r of rows) {
|
|
1262
|
-
const ok = await embedMemory(db, r.id, provider, { agent_id: agentId, model, maxChars: opts?.maxChars });
|
|
1263
|
-
if (ok) embedded++;
|
|
1055
|
+
safeWatch(memoryDir, (eventType, filename) => {
|
|
1056
|
+
if (filename.endsWith(".md")) {
|
|
1057
|
+
scheduleIngest(join2(memoryDir, filename), `memory:${eventType}`);
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
if (initialScan) {
|
|
1061
|
+
scheduleIngest(memoryMdPath, "initial");
|
|
1062
|
+
if (existsSync2(memoryDir)) {
|
|
1063
|
+
for (const file of readdirSync(memoryDir)) {
|
|
1064
|
+
if (file.endsWith(".md")) {
|
|
1065
|
+
scheduleIngest(join2(memoryDir, file), "initial");
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1264
1069
|
}
|
|
1265
|
-
return {
|
|
1070
|
+
return {
|
|
1071
|
+
close: () => {
|
|
1072
|
+
if (stopped) return;
|
|
1073
|
+
stopped = true;
|
|
1074
|
+
for (const timer of timers.values()) {
|
|
1075
|
+
clearTimeout(timer);
|
|
1076
|
+
}
|
|
1077
|
+
timers.clear();
|
|
1078
|
+
for (const watcher of watchers) {
|
|
1079
|
+
try {
|
|
1080
|
+
watcher.close();
|
|
1081
|
+
} catch {
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
logger.log(
|
|
1085
|
+
`[auto-ingest] stopped triggers=${stats.triggers} files=${stats.filesProcessed} extracted=${stats.extracted} written=${stats.written} skipped=${stats.skipped} errors=${stats.errors}`
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1266
1089
|
}
|
|
1267
1090
|
|
|
1268
1091
|
// src/sleep/decay.ts
|
|
@@ -1325,74 +1148,15 @@ function getDecayedMemories(db, threshold = 0.05, opts) {
|
|
|
1325
1148
|
).all(...agentId ? [threshold, agentId] : [threshold]);
|
|
1326
1149
|
}
|
|
1327
1150
|
|
|
1328
|
-
// src/sleep/sync.ts
|
|
1329
|
-
function syncOne(db, input) {
|
|
1330
|
-
const memInput = {
|
|
1331
|
-
content: input.content,
|
|
1332
|
-
type: input.type ?? "event",
|
|
1333
|
-
priority: input.priority,
|
|
1334
|
-
emotion_val: input.emotion_val,
|
|
1335
|
-
source: input.source,
|
|
1336
|
-
agent_id: input.agent_id,
|
|
1337
|
-
uri: input.uri
|
|
1338
|
-
};
|
|
1339
|
-
const guardResult = guard(db, memInput);
|
|
1340
|
-
switch (guardResult.action) {
|
|
1341
|
-
case "skip":
|
|
1342
|
-
return { action: "skipped", reason: guardResult.reason, memoryId: guardResult.existingId };
|
|
1343
|
-
case "add": {
|
|
1344
|
-
const mem = createMemory(db, memInput);
|
|
1345
|
-
if (!mem) return { action: "skipped", reason: "createMemory returned null" };
|
|
1346
|
-
if (input.uri) {
|
|
1347
|
-
try {
|
|
1348
|
-
createPath(db, mem.id, input.uri);
|
|
1349
|
-
} catch {
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
return { action: "added", memoryId: mem.id, reason: guardResult.reason };
|
|
1353
|
-
}
|
|
1354
|
-
case "update": {
|
|
1355
|
-
if (!guardResult.existingId) return { action: "skipped", reason: "No existing ID for update" };
|
|
1356
|
-
createSnapshot(db, guardResult.existingId, "update", "sync");
|
|
1357
|
-
updateMemory(db, guardResult.existingId, { content: input.content });
|
|
1358
|
-
return { action: "updated", memoryId: guardResult.existingId, reason: guardResult.reason };
|
|
1359
|
-
}
|
|
1360
|
-
case "merge": {
|
|
1361
|
-
if (!guardResult.existingId || !guardResult.mergedContent) {
|
|
1362
|
-
return { action: "skipped", reason: "Missing merge data" };
|
|
1363
|
-
}
|
|
1364
|
-
createSnapshot(db, guardResult.existingId, "merge", "sync");
|
|
1365
|
-
updateMemory(db, guardResult.existingId, { content: guardResult.mergedContent });
|
|
1366
|
-
return { action: "merged", memoryId: guardResult.existingId, reason: guardResult.reason };
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
function syncBatch(db, inputs) {
|
|
1371
|
-
const results = [];
|
|
1372
|
-
const transaction = db.transaction(() => {
|
|
1373
|
-
for (const input of inputs) {
|
|
1374
|
-
results.push(syncOne(db, input));
|
|
1375
|
-
}
|
|
1376
|
-
});
|
|
1377
|
-
transaction();
|
|
1378
|
-
return results;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
1151
|
// src/sleep/tidy.ts
|
|
1382
1152
|
function runTidy(db, opts) {
|
|
1383
1153
|
const threshold = opts?.vitalityThreshold ?? 0.05;
|
|
1384
|
-
const maxSnapshots = opts?.maxSnapshotsPerMemory ?? 10;
|
|
1385
1154
|
const agentId = opts?.agent_id;
|
|
1386
1155
|
let archived = 0;
|
|
1387
1156
|
let orphansCleaned = 0;
|
|
1388
|
-
let snapshotsPruned = 0;
|
|
1389
1157
|
const transaction = db.transaction(() => {
|
|
1390
1158
|
const decayed = getDecayedMemories(db, threshold, agentId ? { agent_id: agentId } : void 0);
|
|
1391
1159
|
for (const mem of decayed) {
|
|
1392
|
-
try {
|
|
1393
|
-
createSnapshot(db, mem.id, "delete", "tidy");
|
|
1394
|
-
} catch {
|
|
1395
|
-
}
|
|
1396
1160
|
deleteMemory(db, mem.id);
|
|
1397
1161
|
archived++;
|
|
1398
1162
|
}
|
|
@@ -1404,35 +1168,15 @@ function runTidy(db, opts) {
|
|
|
1404
1168
|
"DELETE FROM paths WHERE memory_id NOT IN (SELECT id FROM memories)"
|
|
1405
1169
|
).run();
|
|
1406
1170
|
orphansCleaned = orphans.changes;
|
|
1407
|
-
const memoriesWithSnapshots = agentId ? db.prepare(
|
|
1408
|
-
`SELECT s.memory_id, COUNT(*) as cnt
|
|
1409
|
-
FROM snapshots s
|
|
1410
|
-
JOIN memories m ON m.id = s.memory_id
|
|
1411
|
-
WHERE m.agent_id = ?
|
|
1412
|
-
GROUP BY s.memory_id HAVING cnt > ?`
|
|
1413
|
-
).all(agentId, maxSnapshots) : db.prepare(
|
|
1414
|
-
`SELECT memory_id, COUNT(*) as cnt FROM snapshots
|
|
1415
|
-
GROUP BY memory_id HAVING cnt > ?`
|
|
1416
|
-
).all(maxSnapshots);
|
|
1417
|
-
for (const { memory_id } of memoriesWithSnapshots) {
|
|
1418
|
-
const pruned = db.prepare(
|
|
1419
|
-
`DELETE FROM snapshots WHERE id NOT IN (
|
|
1420
|
-
SELECT id FROM snapshots WHERE memory_id = ?
|
|
1421
|
-
ORDER BY created_at DESC LIMIT ?
|
|
1422
|
-
) AND memory_id = ?`
|
|
1423
|
-
).run(memory_id, maxSnapshots, memory_id);
|
|
1424
|
-
snapshotsPruned += pruned.changes;
|
|
1425
|
-
}
|
|
1426
1171
|
});
|
|
1427
1172
|
transaction();
|
|
1428
|
-
return { archived, orphansCleaned
|
|
1173
|
+
return { archived, orphansCleaned };
|
|
1429
1174
|
}
|
|
1430
1175
|
|
|
1431
1176
|
// src/sleep/govern.ts
|
|
1432
1177
|
function runGovern(db, opts) {
|
|
1433
1178
|
const agentId = opts?.agent_id;
|
|
1434
1179
|
let orphanPaths = 0;
|
|
1435
|
-
let orphanLinks = 0;
|
|
1436
1180
|
let emptyMemories = 0;
|
|
1437
1181
|
const transaction = db.transaction(() => {
|
|
1438
1182
|
const pathResult = agentId ? db.prepare(
|
|
@@ -1441,23 +1185,11 @@ function runGovern(db, opts) {
|
|
|
1441
1185
|
AND memory_id NOT IN (SELECT id FROM memories WHERE agent_id = ?)`
|
|
1442
1186
|
).run(agentId, agentId) : db.prepare("DELETE FROM paths WHERE memory_id NOT IN (SELECT id FROM memories)").run();
|
|
1443
1187
|
orphanPaths = pathResult.changes;
|
|
1444
|
-
const linkResult = agentId ? db.prepare(
|
|
1445
|
-
`DELETE FROM links WHERE
|
|
1446
|
-
agent_id = ? AND (
|
|
1447
|
-
source_id NOT IN (SELECT id FROM memories WHERE agent_id = ?) OR
|
|
1448
|
-
target_id NOT IN (SELECT id FROM memories WHERE agent_id = ?)
|
|
1449
|
-
)`
|
|
1450
|
-
).run(agentId, agentId, agentId) : db.prepare(
|
|
1451
|
-
`DELETE FROM links WHERE
|
|
1452
|
-
source_id NOT IN (SELECT id FROM memories) OR
|
|
1453
|
-
target_id NOT IN (SELECT id FROM memories)`
|
|
1454
|
-
).run();
|
|
1455
|
-
orphanLinks = linkResult.changes;
|
|
1456
1188
|
const emptyResult = agentId ? db.prepare("DELETE FROM memories WHERE agent_id = ? AND TRIM(content) = ''").run(agentId) : db.prepare("DELETE FROM memories WHERE TRIM(content) = ''").run();
|
|
1457
1189
|
emptyMemories = emptyResult.changes;
|
|
1458
1190
|
});
|
|
1459
1191
|
transaction();
|
|
1460
|
-
return { orphanPaths,
|
|
1192
|
+
return { orphanPaths, emptyMemories };
|
|
1461
1193
|
}
|
|
1462
1194
|
|
|
1463
1195
|
// src/sleep/boot.ts
|
|
@@ -1514,56 +1246,39 @@ function boot(db, opts) {
|
|
|
1514
1246
|
export {
|
|
1515
1247
|
boot,
|
|
1516
1248
|
calculateVitality,
|
|
1517
|
-
|
|
1249
|
+
classifyIngestType,
|
|
1518
1250
|
contentHash,
|
|
1519
1251
|
countMemories,
|
|
1520
|
-
createDashScopeProvider,
|
|
1521
|
-
createLink,
|
|
1522
1252
|
createMemory,
|
|
1523
|
-
createOpenAIProvider,
|
|
1524
1253
|
createPath,
|
|
1525
|
-
createSnapshot,
|
|
1526
|
-
decodeEmbedding,
|
|
1527
|
-
deleteLink,
|
|
1528
1254
|
deleteMemory,
|
|
1529
1255
|
deletePath,
|
|
1530
|
-
embedMemory,
|
|
1531
|
-
embedMissingForAgent,
|
|
1532
|
-
encodeEmbedding,
|
|
1533
1256
|
exportMemories,
|
|
1257
|
+
extractIngestItems,
|
|
1534
1258
|
getDecayedMemories,
|
|
1535
|
-
getEmbedding,
|
|
1536
|
-
getEmbeddingProviderFromEnv,
|
|
1537
|
-
getLinks,
|
|
1538
1259
|
getMemory,
|
|
1539
|
-
getOutgoingLinks,
|
|
1540
1260
|
getPath,
|
|
1541
1261
|
getPathByUri,
|
|
1542
1262
|
getPathsByDomain,
|
|
1543
1263
|
getPathsByMemory,
|
|
1544
1264
|
getPathsByPrefix,
|
|
1545
|
-
getSnapshot,
|
|
1546
|
-
getSnapshots,
|
|
1547
|
-
getStrategy,
|
|
1548
1265
|
guard,
|
|
1266
|
+
ingestText,
|
|
1549
1267
|
isCountRow,
|
|
1550
|
-
listEmbeddings,
|
|
1551
1268
|
listMemories,
|
|
1552
1269
|
openDatabase,
|
|
1553
1270
|
parseUri,
|
|
1554
1271
|
recordAccess,
|
|
1555
|
-
|
|
1556
|
-
rollback,
|
|
1272
|
+
runAutoIngestWatcher,
|
|
1557
1273
|
runDecay,
|
|
1558
1274
|
runGovern,
|
|
1559
1275
|
runTidy,
|
|
1560
1276
|
searchBM25,
|
|
1561
|
-
|
|
1277
|
+
slugify,
|
|
1278
|
+
splitIngestBlocks,
|
|
1562
1279
|
syncBatch,
|
|
1563
1280
|
syncOne,
|
|
1564
1281
|
tokenize,
|
|
1565
|
-
|
|
1566
|
-
updateMemory,
|
|
1567
|
-
upsertEmbedding
|
|
1282
|
+
updateMemory
|
|
1568
1283
|
};
|
|
1569
1284
|
//# sourceMappingURL=index.js.map
|