@smyslenny/agent-memory 2.2.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 +45 -41
- package/README.en.md +153 -0
- package/README.md +86 -153
- package/dist/bin/agent-memory.js +28 -534
- package/dist/bin/agent-memory.js.map +1 -1
- package/dist/index.d.ts +50 -167
- package/dist/index.js +289 -692
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +584 -748
- package/dist/mcp/server.js.map +1 -1
- package/docs/design/0014-memory-core-dedup.md +722 -0
- package/docs/design/TEMPLATE.md +67 -0
- package/package.json +2 -3
- package/README.zh-CN.md +0 -170
package/dist/mcp/server.js
CHANGED
|
@@ -14366,261 +14366,6 @@ function getPathsByPrefix(db, prefix, agent_id = "default") {
|
|
|
14366
14366
|
return db.prepare("SELECT * FROM paths WHERE agent_id = ? AND uri LIKE ? ORDER BY uri").all(agent_id, `${prefix}%`);
|
|
14367
14367
|
}
|
|
14368
14368
|
|
|
14369
|
-
// src/core/link.ts
|
|
14370
|
-
init_db();
|
|
14371
|
-
function createLink(db, sourceId, targetId, relation, weight = 1, agent_id) {
|
|
14372
|
-
const sourceAgent = db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(sourceId)?.agent_id;
|
|
14373
|
-
const targetAgent = db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(targetId)?.agent_id;
|
|
14374
|
-
if (!sourceAgent) throw new Error(`Source memory not found: ${sourceId}`);
|
|
14375
|
-
if (!targetAgent) throw new Error(`Target memory not found: ${targetId}`);
|
|
14376
|
-
if (sourceAgent !== targetAgent) throw new Error("Cross-agent links are not allowed");
|
|
14377
|
-
if (agent_id && agent_id !== sourceAgent) throw new Error("Agent mismatch for link");
|
|
14378
|
-
const agentId = agent_id ?? sourceAgent;
|
|
14379
|
-
db.prepare(
|
|
14380
|
-
`INSERT OR REPLACE INTO links (agent_id, source_id, target_id, relation, weight, created_at)
|
|
14381
|
-
VALUES (?, ?, ?, ?, ?, ?)`
|
|
14382
|
-
).run(agentId, sourceId, targetId, relation, weight, now());
|
|
14383
|
-
return { agent_id: agentId, source_id: sourceId, target_id: targetId, relation, weight, created_at: now() };
|
|
14384
|
-
}
|
|
14385
|
-
function getLinks(db, memoryId, agent_id) {
|
|
14386
|
-
const agentId = agent_id ?? db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(memoryId)?.agent_id ?? "default";
|
|
14387
|
-
return db.prepare("SELECT * FROM links WHERE agent_id = ? AND (source_id = ? OR target_id = ?)").all(agentId, memoryId, memoryId);
|
|
14388
|
-
}
|
|
14389
|
-
function traverse(db, startId, maxHops = 2, agent_id) {
|
|
14390
|
-
const agentId = agent_id ?? db.prepare("SELECT agent_id FROM memories WHERE id = ?").get(startId)?.agent_id ?? "default";
|
|
14391
|
-
const visited = /* @__PURE__ */ new Set();
|
|
14392
|
-
const results = [];
|
|
14393
|
-
const queue = [
|
|
14394
|
-
{ id: startId, hop: 0, relation: "self" }
|
|
14395
|
-
];
|
|
14396
|
-
while (queue.length > 0) {
|
|
14397
|
-
const current = queue.shift();
|
|
14398
|
-
if (visited.has(current.id)) continue;
|
|
14399
|
-
visited.add(current.id);
|
|
14400
|
-
if (current.hop > 0) {
|
|
14401
|
-
results.push(current);
|
|
14402
|
-
}
|
|
14403
|
-
if (current.hop < maxHops) {
|
|
14404
|
-
const links = db.prepare("SELECT target_id, relation FROM links WHERE agent_id = ? AND source_id = ?").all(agentId, current.id);
|
|
14405
|
-
for (const link of links) {
|
|
14406
|
-
if (!visited.has(link.target_id)) {
|
|
14407
|
-
queue.push({
|
|
14408
|
-
id: link.target_id,
|
|
14409
|
-
hop: current.hop + 1,
|
|
14410
|
-
relation: link.relation
|
|
14411
|
-
});
|
|
14412
|
-
}
|
|
14413
|
-
}
|
|
14414
|
-
const reverseLinks = db.prepare("SELECT source_id, relation FROM links WHERE agent_id = ? AND target_id = ?").all(agentId, current.id);
|
|
14415
|
-
for (const link of reverseLinks) {
|
|
14416
|
-
if (!visited.has(link.source_id)) {
|
|
14417
|
-
queue.push({
|
|
14418
|
-
id: link.source_id,
|
|
14419
|
-
hop: current.hop + 1,
|
|
14420
|
-
relation: link.relation
|
|
14421
|
-
});
|
|
14422
|
-
}
|
|
14423
|
-
}
|
|
14424
|
-
}
|
|
14425
|
-
}
|
|
14426
|
-
return results;
|
|
14427
|
-
}
|
|
14428
|
-
|
|
14429
|
-
// src/core/snapshot.ts
|
|
14430
|
-
init_db();
|
|
14431
|
-
init_tokenizer();
|
|
14432
|
-
function createSnapshot(db, memoryId, action, changedBy) {
|
|
14433
|
-
const memory = db.prepare("SELECT content FROM memories WHERE id = ?").get(memoryId);
|
|
14434
|
-
if (!memory) throw new Error(`Memory not found: ${memoryId}`);
|
|
14435
|
-
const id = newId();
|
|
14436
|
-
db.prepare(
|
|
14437
|
-
`INSERT INTO snapshots (id, memory_id, content, changed_by, action, created_at)
|
|
14438
|
-
VALUES (?, ?, ?, ?, ?, ?)`
|
|
14439
|
-
).run(id, memoryId, memory.content, changedBy ?? null, action, now());
|
|
14440
|
-
return { id, memory_id: memoryId, content: memory.content, changed_by: changedBy ?? null, action, created_at: now() };
|
|
14441
|
-
}
|
|
14442
|
-
function getSnapshots(db, memoryId) {
|
|
14443
|
-
return db.prepare("SELECT * FROM snapshots WHERE memory_id = ? ORDER BY created_at DESC").all(memoryId);
|
|
14444
|
-
}
|
|
14445
|
-
function getSnapshot(db, id) {
|
|
14446
|
-
return db.prepare("SELECT * FROM snapshots WHERE id = ?").get(id) ?? null;
|
|
14447
|
-
}
|
|
14448
|
-
function rollback(db, snapshotId) {
|
|
14449
|
-
const snapshot = getSnapshot(db, snapshotId);
|
|
14450
|
-
if (!snapshot) return false;
|
|
14451
|
-
createSnapshot(db, snapshot.memory_id, "update", "rollback");
|
|
14452
|
-
db.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?").run(
|
|
14453
|
-
snapshot.content,
|
|
14454
|
-
now(),
|
|
14455
|
-
snapshot.memory_id
|
|
14456
|
-
);
|
|
14457
|
-
db.prepare("DELETE FROM memories_fts WHERE id = ?").run(snapshot.memory_id);
|
|
14458
|
-
db.prepare("INSERT INTO memories_fts (id, content) VALUES (?, ?)").run(
|
|
14459
|
-
snapshot.memory_id,
|
|
14460
|
-
tokenizeForIndex(snapshot.content)
|
|
14461
|
-
);
|
|
14462
|
-
return true;
|
|
14463
|
-
}
|
|
14464
|
-
|
|
14465
|
-
// src/search/intent.ts
|
|
14466
|
-
init_tokenizer();
|
|
14467
|
-
var INTENT_PATTERNS = {
|
|
14468
|
-
factual: [
|
|
14469
|
-
// English
|
|
14470
|
-
/^(what|who|where|which|how much|how many)\b/i,
|
|
14471
|
-
/\b(name|address|number|password|config|setting)\b/i,
|
|
14472
|
-
// Chinese - questions about facts
|
|
14473
|
-
/是(什么|谁|哪|啥)/,
|
|
14474
|
-
/叫(什么|啥)/,
|
|
14475
|
-
/(名字|地址|号码|密码|配置|设置|账号|邮箱|链接|版本)/,
|
|
14476
|
-
/(多少|几个|哪个|哪些|哪里)/,
|
|
14477
|
-
// Chinese - lookup patterns
|
|
14478
|
-
/(查一下|找一下|看看|搜一下)/,
|
|
14479
|
-
/(.+)是什么$/
|
|
14480
|
-
],
|
|
14481
|
-
temporal: [
|
|
14482
|
-
// English
|
|
14483
|
-
/^(when|what time|how long)\b/i,
|
|
14484
|
-
/\b(yesterday|today|tomorrow|last week|recently|ago|before|after)\b/i,
|
|
14485
|
-
/\b(first|latest|newest|oldest|previous|next)\b/i,
|
|
14486
|
-
// Chinese - time expressions
|
|
14487
|
-
/什么时候/,
|
|
14488
|
-
/(昨天|今天|明天|上周|下周|最近|以前|之前|之后|刚才|刚刚)/,
|
|
14489
|
-
/(几月|几号|几点|多久|多长时间)/,
|
|
14490
|
-
/(上次|下次|第一次|最后一次|那天|那时)/,
|
|
14491
|
-
// Date patterns
|
|
14492
|
-
/\d{4}[-/.]\d{1,2}/,
|
|
14493
|
-
/\d{1,2}月\d{1,2}[日号]/,
|
|
14494
|
-
// Chinese - temporal context
|
|
14495
|
-
/(历史|记录|日志|以来|至今|期间)/
|
|
14496
|
-
],
|
|
14497
|
-
causal: [
|
|
14498
|
-
// English
|
|
14499
|
-
/^(why|how come|what caused)\b/i,
|
|
14500
|
-
/\b(because|due to|reason|cause|result)\b/i,
|
|
14501
|
-
// Chinese - causal questions
|
|
14502
|
-
/为(什么|啥|何)/,
|
|
14503
|
-
/(原因|导致|造成|引起|因为|所以|结果)/,
|
|
14504
|
-
/(怎么回事|怎么了|咋回事|咋了)/,
|
|
14505
|
-
/(为啥|凭啥|凭什么)/,
|
|
14506
|
-
// Chinese - problem/diagnosis
|
|
14507
|
-
/(出(了|了什么)?问题|报错|失败|出错|bug)/
|
|
14508
|
-
],
|
|
14509
|
-
exploratory: [
|
|
14510
|
-
// English
|
|
14511
|
-
/^(how|tell me about|explain|describe|show me)\b/i,
|
|
14512
|
-
/^(what do you think|what about|any)\b/i,
|
|
14513
|
-
/\b(overview|summary|list|compare)\b/i,
|
|
14514
|
-
// Chinese - exploratory
|
|
14515
|
-
/(怎么样|怎样|如何)/,
|
|
14516
|
-
/(介绍|说说|讲讲|聊聊|谈谈)/,
|
|
14517
|
-
/(有哪些|有什么|有没有)/,
|
|
14518
|
-
/(关于|对于|至于|关联)/,
|
|
14519
|
-
/(总结|概括|梳理|回顾|盘点)/,
|
|
14520
|
-
// Chinese - opinion/analysis
|
|
14521
|
-
/(看法|想法|意见|建议|评价|感觉|觉得)/,
|
|
14522
|
-
/(对比|比较|区别|差异|优缺点)/
|
|
14523
|
-
]
|
|
14524
|
-
};
|
|
14525
|
-
var CN_STRUCTURE_BOOSTS = {
|
|
14526
|
-
factual: [/^.{1,6}(是什么|叫什么|在哪)/, /^(谁|哪)/],
|
|
14527
|
-
temporal: [/^(什么时候|上次|最近)/, /(时间|日期)$/],
|
|
14528
|
-
causal: [/^(为什么|为啥)/, /(为什么|怎么回事)$/],
|
|
14529
|
-
exploratory: [/^(怎么|如何|说说)/, /(哪些|什么样)$/]
|
|
14530
|
-
};
|
|
14531
|
-
function classifyIntent(query) {
|
|
14532
|
-
const scores = {
|
|
14533
|
-
factual: 0,
|
|
14534
|
-
exploratory: 0,
|
|
14535
|
-
temporal: 0,
|
|
14536
|
-
causal: 0
|
|
14537
|
-
};
|
|
14538
|
-
for (const [intent, patterns] of Object.entries(INTENT_PATTERNS)) {
|
|
14539
|
-
for (const pattern of patterns) {
|
|
14540
|
-
if (pattern.test(query)) {
|
|
14541
|
-
scores[intent] += 1;
|
|
14542
|
-
}
|
|
14543
|
-
}
|
|
14544
|
-
}
|
|
14545
|
-
for (const [intent, patterns] of Object.entries(CN_STRUCTURE_BOOSTS)) {
|
|
14546
|
-
for (const pattern of patterns) {
|
|
14547
|
-
if (pattern.test(query)) {
|
|
14548
|
-
scores[intent] += 0.5;
|
|
14549
|
-
}
|
|
14550
|
-
}
|
|
14551
|
-
}
|
|
14552
|
-
const tokens = tokenize(query);
|
|
14553
|
-
const totalPatternScore = Object.values(scores).reduce((a, b) => a + b, 0);
|
|
14554
|
-
if (totalPatternScore === 0 && tokens.length <= 3) {
|
|
14555
|
-
scores.factual += 1;
|
|
14556
|
-
}
|
|
14557
|
-
let maxIntent = "factual";
|
|
14558
|
-
let maxScore = 0;
|
|
14559
|
-
for (const [intent, score] of Object.entries(scores)) {
|
|
14560
|
-
if (score > maxScore) {
|
|
14561
|
-
maxScore = score;
|
|
14562
|
-
maxIntent = intent;
|
|
14563
|
-
}
|
|
14564
|
-
}
|
|
14565
|
-
const totalScore = Object.values(scores).reduce((a, b) => a + b, 0);
|
|
14566
|
-
const confidence = totalScore > 0 ? Math.min(0.95, maxScore / totalScore) : 0.5;
|
|
14567
|
-
return { intent: maxIntent, confidence };
|
|
14568
|
-
}
|
|
14569
|
-
function getStrategy(intent) {
|
|
14570
|
-
switch (intent) {
|
|
14571
|
-
case "factual":
|
|
14572
|
-
return { boostRecent: false, boostPriority: true, limit: 5 };
|
|
14573
|
-
case "temporal":
|
|
14574
|
-
return { boostRecent: true, boostPriority: false, limit: 10 };
|
|
14575
|
-
case "causal":
|
|
14576
|
-
return { boostRecent: false, boostPriority: false, limit: 10 };
|
|
14577
|
-
case "exploratory":
|
|
14578
|
-
return { boostRecent: false, boostPriority: false, limit: 15 };
|
|
14579
|
-
}
|
|
14580
|
-
}
|
|
14581
|
-
|
|
14582
|
-
// src/search/rerank.ts
|
|
14583
|
-
async function rerankWithProvider(results, query, provider) {
|
|
14584
|
-
if (results.length === 0) return results;
|
|
14585
|
-
const documents = results.map((r) => r.memory.content);
|
|
14586
|
-
try {
|
|
14587
|
-
const apiResults = await provider.rerank(query, documents);
|
|
14588
|
-
const scoreMap = new Map(apiResults.map((r) => [r.index, r.relevance_score]));
|
|
14589
|
-
return results.map((r, i) => {
|
|
14590
|
-
const score = scoreMap.get(i);
|
|
14591
|
-
if (score === void 0) return r;
|
|
14592
|
-
return {
|
|
14593
|
-
...r,
|
|
14594
|
-
score,
|
|
14595
|
-
matchReason: `${r.matchReason}+rerank`
|
|
14596
|
-
};
|
|
14597
|
-
});
|
|
14598
|
-
} catch (err) {
|
|
14599
|
-
console.warn("[agent-memory] External rerank failed, falling back:", err);
|
|
14600
|
-
return results;
|
|
14601
|
-
}
|
|
14602
|
-
}
|
|
14603
|
-
function rerank(results, opts) {
|
|
14604
|
-
const now2 = Date.now();
|
|
14605
|
-
const scored = results.map((r) => {
|
|
14606
|
-
let finalScore = r.score;
|
|
14607
|
-
if (opts.boostPriority) {
|
|
14608
|
-
const priorityMultiplier = [4, 3, 2, 1][r.memory.priority] ?? 1;
|
|
14609
|
-
finalScore *= priorityMultiplier;
|
|
14610
|
-
}
|
|
14611
|
-
if (opts.boostRecent && r.memory.updated_at) {
|
|
14612
|
-
const age = now2 - new Date(r.memory.updated_at).getTime();
|
|
14613
|
-
const daysSinceUpdate = age / (1e3 * 60 * 60 * 24);
|
|
14614
|
-
const recencyBoost = Math.max(0.1, 1 / (1 + daysSinceUpdate * 0.1));
|
|
14615
|
-
finalScore *= recencyBoost;
|
|
14616
|
-
}
|
|
14617
|
-
finalScore *= Math.max(0.1, r.memory.vitality);
|
|
14618
|
-
return { ...r, score: finalScore };
|
|
14619
|
-
});
|
|
14620
|
-
scored.sort((a, b) => b.score - a.score);
|
|
14621
|
-
return scored.slice(0, opts.limit);
|
|
14622
|
-
}
|
|
14623
|
-
|
|
14624
14369
|
// src/search/bm25.ts
|
|
14625
14370
|
init_tokenizer();
|
|
14626
14371
|
function searchBM25(db, query, opts) {
|
|
@@ -14673,321 +14418,6 @@ function buildFtsQuery(text) {
|
|
|
14673
14418
|
return tokens.map((w) => `"${w}"`).join(" OR ");
|
|
14674
14419
|
}
|
|
14675
14420
|
|
|
14676
|
-
// src/search/embeddings.ts
|
|
14677
|
-
init_db();
|
|
14678
|
-
function encodeEmbedding(vector) {
|
|
14679
|
-
const arr = vector instanceof Float32Array ? vector : Float32Array.from(vector);
|
|
14680
|
-
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
14681
|
-
}
|
|
14682
|
-
function decodeEmbedding(buf) {
|
|
14683
|
-
const copy = Buffer.from(buf);
|
|
14684
|
-
return new Float32Array(copy.buffer, copy.byteOffset, Math.floor(copy.byteLength / 4));
|
|
14685
|
-
}
|
|
14686
|
-
function upsertEmbedding(db, input) {
|
|
14687
|
-
const ts = now();
|
|
14688
|
-
const vec = input.vector instanceof Float32Array ? input.vector : Float32Array.from(input.vector);
|
|
14689
|
-
const blob = encodeEmbedding(vec);
|
|
14690
|
-
db.prepare(
|
|
14691
|
-
`INSERT INTO embeddings (agent_id, memory_id, model, dim, vector, created_at, updated_at)
|
|
14692
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
14693
|
-
ON CONFLICT(agent_id, memory_id, model) DO UPDATE SET
|
|
14694
|
-
dim = excluded.dim,
|
|
14695
|
-
vector = excluded.vector,
|
|
14696
|
-
updated_at = excluded.updated_at`
|
|
14697
|
-
).run(input.agent_id, input.memory_id, input.model, vec.length, blob, ts, ts);
|
|
14698
|
-
}
|
|
14699
|
-
function listEmbeddings(db, agent_id, model) {
|
|
14700
|
-
const rows = db.prepare(
|
|
14701
|
-
"SELECT memory_id, vector FROM embeddings WHERE agent_id = ? AND model = ?"
|
|
14702
|
-
).all(agent_id, model);
|
|
14703
|
-
return rows.map((r) => ({ memory_id: r.memory_id, vector: decodeEmbedding(r.vector) }));
|
|
14704
|
-
}
|
|
14705
|
-
|
|
14706
|
-
// src/search/hybrid.ts
|
|
14707
|
-
function cosine(a, b) {
|
|
14708
|
-
const n = Math.min(a.length, b.length);
|
|
14709
|
-
let dot = 0;
|
|
14710
|
-
let na = 0;
|
|
14711
|
-
let nb = 0;
|
|
14712
|
-
for (let i = 0; i < n; i++) {
|
|
14713
|
-
const x = a[i];
|
|
14714
|
-
const y = b[i];
|
|
14715
|
-
dot += x * y;
|
|
14716
|
-
na += x * x;
|
|
14717
|
-
nb += y * y;
|
|
14718
|
-
}
|
|
14719
|
-
if (na === 0 || nb === 0) return 0;
|
|
14720
|
-
return dot / (Math.sqrt(na) * Math.sqrt(nb));
|
|
14721
|
-
}
|
|
14722
|
-
function rrfScore(rank, k) {
|
|
14723
|
-
return 1 / (k + rank);
|
|
14724
|
-
}
|
|
14725
|
-
function fuseRrf(lists, k) {
|
|
14726
|
-
const out = /* @__PURE__ */ new Map();
|
|
14727
|
-
for (const list of lists) {
|
|
14728
|
-
for (let i = 0; i < list.items.length; i++) {
|
|
14729
|
-
const it = list.items[i];
|
|
14730
|
-
const rank = i + 1;
|
|
14731
|
-
const add = rrfScore(rank, k);
|
|
14732
|
-
const prev = out.get(it.id);
|
|
14733
|
-
if (!prev) out.set(it.id, { score: add, sources: [list.name] });
|
|
14734
|
-
else {
|
|
14735
|
-
prev.score += add;
|
|
14736
|
-
if (!prev.sources.includes(list.name)) prev.sources.push(list.name);
|
|
14737
|
-
}
|
|
14738
|
-
}
|
|
14739
|
-
}
|
|
14740
|
-
return out;
|
|
14741
|
-
}
|
|
14742
|
-
function fetchMemories(db, ids, agentId) {
|
|
14743
|
-
if (ids.length === 0) return [];
|
|
14744
|
-
const placeholders = ids.map(() => "?").join(", ");
|
|
14745
|
-
const sql = agentId ? `SELECT * FROM memories WHERE id IN (${placeholders}) AND agent_id = ?` : `SELECT * FROM memories WHERE id IN (${placeholders})`;
|
|
14746
|
-
const rows = db.prepare(sql).all(...agentId ? [...ids, agentId] : ids);
|
|
14747
|
-
return rows;
|
|
14748
|
-
}
|
|
14749
|
-
async function searchHybrid(db, query, opts) {
|
|
14750
|
-
const agentId = opts?.agent_id ?? "default";
|
|
14751
|
-
const limit = opts?.limit ?? 10;
|
|
14752
|
-
const bm25Mult = opts?.bm25CandidateMultiplier ?? 3;
|
|
14753
|
-
const semanticCandidates = opts?.semanticCandidates ?? 50;
|
|
14754
|
-
const rrfK = opts?.rrfK ?? 60;
|
|
14755
|
-
const bm25 = searchBM25(db, query, {
|
|
14756
|
-
agent_id: agentId,
|
|
14757
|
-
limit: limit * bm25Mult
|
|
14758
|
-
});
|
|
14759
|
-
const provider = opts?.embeddingProvider ?? null;
|
|
14760
|
-
const model = opts?.embeddingModel ?? provider?.model;
|
|
14761
|
-
if (!provider || !model) {
|
|
14762
|
-
return bm25.slice(0, limit);
|
|
14763
|
-
}
|
|
14764
|
-
const embedFn = provider.embedQuery ?? provider.embed;
|
|
14765
|
-
const qVec = Float32Array.from(await embedFn.call(provider, query));
|
|
14766
|
-
const embeddings = listEmbeddings(db, agentId, model);
|
|
14767
|
-
const scored = [];
|
|
14768
|
-
for (const e of embeddings) {
|
|
14769
|
-
scored.push({ id: e.memory_id, score: cosine(qVec, e.vector) });
|
|
14770
|
-
}
|
|
14771
|
-
scored.sort((a, b) => b.score - a.score);
|
|
14772
|
-
const semanticTop = scored.slice(0, semanticCandidates);
|
|
14773
|
-
const fused = fuseRrf(
|
|
14774
|
-
[
|
|
14775
|
-
{ name: "bm25", items: bm25.map((r) => ({ id: r.memory.id, score: r.score })) },
|
|
14776
|
-
{ name: "semantic", items: semanticTop }
|
|
14777
|
-
],
|
|
14778
|
-
rrfK
|
|
14779
|
-
);
|
|
14780
|
-
const ids = [...fused.keys()];
|
|
14781
|
-
const memories = fetchMemories(db, ids, agentId);
|
|
14782
|
-
const byId = new Map(memories.map((m) => [m.id, m]));
|
|
14783
|
-
const out = [];
|
|
14784
|
-
for (const [id, meta3] of fused) {
|
|
14785
|
-
const mem = byId.get(id);
|
|
14786
|
-
if (!mem) continue;
|
|
14787
|
-
out.push({
|
|
14788
|
-
memory: mem,
|
|
14789
|
-
score: meta3.score,
|
|
14790
|
-
matchReason: meta3.sources.sort().join("+")
|
|
14791
|
-
});
|
|
14792
|
-
}
|
|
14793
|
-
out.sort((a, b) => b.score - a.score);
|
|
14794
|
-
return out.slice(0, limit);
|
|
14795
|
-
}
|
|
14796
|
-
|
|
14797
|
-
// src/search/providers.ts
|
|
14798
|
-
var QWEN_DEFAULT_INSTRUCTION = "Given a query, retrieve the most semantically relevant document";
|
|
14799
|
-
function getDefaultInstruction(model) {
|
|
14800
|
-
const m = model.toLowerCase();
|
|
14801
|
-
if (m.includes("qwen")) return QWEN_DEFAULT_INSTRUCTION;
|
|
14802
|
-
if (m.includes("gemini")) return null;
|
|
14803
|
-
return null;
|
|
14804
|
-
}
|
|
14805
|
-
function resolveInstruction(model) {
|
|
14806
|
-
const override = process.env.AGENT_MEMORY_EMBEDDINGS_INSTRUCTION;
|
|
14807
|
-
if (override !== void 0) {
|
|
14808
|
-
const normalized = override.trim();
|
|
14809
|
-
if (!normalized) return null;
|
|
14810
|
-
const lowered = normalized.toLowerCase();
|
|
14811
|
-
if (lowered === "none" || lowered === "off" || lowered === "false" || lowered === "null") return null;
|
|
14812
|
-
return normalized;
|
|
14813
|
-
}
|
|
14814
|
-
return getDefaultInstruction(model);
|
|
14815
|
-
}
|
|
14816
|
-
function buildQueryInput(query, instructionPrefix) {
|
|
14817
|
-
if (!instructionPrefix) return query;
|
|
14818
|
-
return `Instruct: ${instructionPrefix}
|
|
14819
|
-
Query: ${query}`;
|
|
14820
|
-
}
|
|
14821
|
-
function getEmbeddingProviderFromEnv() {
|
|
14822
|
-
const provider = (process.env.AGENT_MEMORY_EMBEDDINGS_PROVIDER ?? "none").toLowerCase();
|
|
14823
|
-
if (provider === "none" || provider === "off" || provider === "false") return null;
|
|
14824
|
-
if (provider === "openai") {
|
|
14825
|
-
const apiKey = process.env.OPENAI_API_KEY;
|
|
14826
|
-
const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "text-embedding-3-small";
|
|
14827
|
-
const baseUrl = process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
|
|
14828
|
-
if (!apiKey) return null;
|
|
14829
|
-
const instruction = resolveInstruction(model);
|
|
14830
|
-
return createOpenAIProvider({ apiKey, model, baseUrl, instruction });
|
|
14831
|
-
}
|
|
14832
|
-
if (provider === "gemini" || provider === "google") {
|
|
14833
|
-
const apiKey = process.env.GEMINI_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
14834
|
-
const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "gemini-embedding-001";
|
|
14835
|
-
const baseUrl = process.env.GEMINI_BASE_URL ?? process.env.OPENAI_BASE_URL ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
14836
|
-
if (!apiKey) return null;
|
|
14837
|
-
const instruction = resolveInstruction(model);
|
|
14838
|
-
return createOpenAIProvider({ id: "gemini", apiKey, model, baseUrl, instruction });
|
|
14839
|
-
}
|
|
14840
|
-
if (provider === "qwen" || provider === "dashscope" || provider === "tongyi") {
|
|
14841
|
-
const apiKey = process.env.DASHSCOPE_API_KEY;
|
|
14842
|
-
const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "text-embedding-v3";
|
|
14843
|
-
const baseUrl = process.env.DASHSCOPE_BASE_URL ?? "https://dashscope.aliyuncs.com";
|
|
14844
|
-
if (!apiKey) return null;
|
|
14845
|
-
const instruction = resolveInstruction(model);
|
|
14846
|
-
return createDashScopeProvider({ apiKey, model, baseUrl, instruction });
|
|
14847
|
-
}
|
|
14848
|
-
return null;
|
|
14849
|
-
}
|
|
14850
|
-
function authHeader(apiKey) {
|
|
14851
|
-
return apiKey.startsWith("Bearer ") ? apiKey : `Bearer ${apiKey}`;
|
|
14852
|
-
}
|
|
14853
|
-
function normalizeEmbedding(e) {
|
|
14854
|
-
if (!Array.isArray(e)) throw new Error("Invalid embedding: not an array");
|
|
14855
|
-
if (e.length === 0) throw new Error("Invalid embedding: empty");
|
|
14856
|
-
return e.map((x) => {
|
|
14857
|
-
if (typeof x !== "number" || !Number.isFinite(x)) throw new Error("Invalid embedding: non-numeric value");
|
|
14858
|
-
return x;
|
|
14859
|
-
});
|
|
14860
|
-
}
|
|
14861
|
-
function createOpenAIProvider(opts) {
|
|
14862
|
-
const baseUrl = opts.baseUrl ?? "https://api.openai.com/v1";
|
|
14863
|
-
const instructionPrefix = opts.instruction ?? null;
|
|
14864
|
-
async function requestEmbedding(input) {
|
|
14865
|
-
const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/embeddings`, {
|
|
14866
|
-
method: "POST",
|
|
14867
|
-
headers: {
|
|
14868
|
-
"content-type": "application/json",
|
|
14869
|
-
authorization: authHeader(opts.apiKey)
|
|
14870
|
-
},
|
|
14871
|
-
body: JSON.stringify({ model: opts.model, input })
|
|
14872
|
-
});
|
|
14873
|
-
if (!resp.ok) {
|
|
14874
|
-
const body = await resp.text().catch(() => "");
|
|
14875
|
-
throw new Error(`OpenAI embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
|
|
14876
|
-
}
|
|
14877
|
-
const data = await resp.json();
|
|
14878
|
-
return normalizeEmbedding(data.data?.[0]?.embedding);
|
|
14879
|
-
}
|
|
14880
|
-
return {
|
|
14881
|
-
id: opts.id ?? "openai",
|
|
14882
|
-
model: opts.model,
|
|
14883
|
-
instructionPrefix,
|
|
14884
|
-
async embed(text) {
|
|
14885
|
-
return requestEmbedding(text);
|
|
14886
|
-
},
|
|
14887
|
-
async embedQuery(query) {
|
|
14888
|
-
return requestEmbedding(buildQueryInput(query, instructionPrefix));
|
|
14889
|
-
}
|
|
14890
|
-
};
|
|
14891
|
-
}
|
|
14892
|
-
function createDashScopeProvider(opts) {
|
|
14893
|
-
const baseUrl = opts.baseUrl ?? "https://dashscope.aliyuncs.com";
|
|
14894
|
-
const instructionPrefix = opts.instruction ?? null;
|
|
14895
|
-
async function requestEmbedding(text) {
|
|
14896
|
-
const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/services/embeddings/text-embedding/text-embedding`, {
|
|
14897
|
-
method: "POST",
|
|
14898
|
-
headers: {
|
|
14899
|
-
"content-type": "application/json",
|
|
14900
|
-
authorization: authHeader(opts.apiKey)
|
|
14901
|
-
},
|
|
14902
|
-
body: JSON.stringify({
|
|
14903
|
-
model: opts.model,
|
|
14904
|
-
input: { texts: [text] }
|
|
14905
|
-
})
|
|
14906
|
-
});
|
|
14907
|
-
if (!resp.ok) {
|
|
14908
|
-
const body = await resp.text().catch(() => "");
|
|
14909
|
-
throw new Error(`DashScope embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
|
|
14910
|
-
}
|
|
14911
|
-
const data = await resp.json();
|
|
14912
|
-
const emb = data.output?.embeddings?.[0]?.embedding ?? data.output?.embeddings?.[0]?.vector ?? data.output?.embedding ?? data.data?.[0]?.embedding;
|
|
14913
|
-
return normalizeEmbedding(emb);
|
|
14914
|
-
}
|
|
14915
|
-
return {
|
|
14916
|
-
id: "dashscope",
|
|
14917
|
-
model: opts.model,
|
|
14918
|
-
instructionPrefix,
|
|
14919
|
-
async embed(text) {
|
|
14920
|
-
return requestEmbedding(text);
|
|
14921
|
-
},
|
|
14922
|
-
async embedQuery(query) {
|
|
14923
|
-
return requestEmbedding(buildQueryInput(query, instructionPrefix));
|
|
14924
|
-
}
|
|
14925
|
-
};
|
|
14926
|
-
}
|
|
14927
|
-
|
|
14928
|
-
// src/search/rerank-provider.ts
|
|
14929
|
-
function authHeader2(apiKey) {
|
|
14930
|
-
return apiKey.startsWith("Bearer ") ? apiKey : `Bearer ${apiKey}`;
|
|
14931
|
-
}
|
|
14932
|
-
function getRerankerProviderFromEnv() {
|
|
14933
|
-
const provider = (process.env.AGENT_MEMORY_RERANK_PROVIDER ?? "none").toLowerCase();
|
|
14934
|
-
if (provider === "none" || provider === "off") return null;
|
|
14935
|
-
if (provider === "openai" || provider === "jina" || provider === "cohere") {
|
|
14936
|
-
const apiKey = process.env.AGENT_MEMORY_RERANK_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
14937
|
-
const model = process.env.AGENT_MEMORY_RERANK_MODEL ?? "Qwen/Qwen3-Reranker-8B";
|
|
14938
|
-
const baseUrl = process.env.AGENT_MEMORY_RERANK_BASE_URL ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
|
|
14939
|
-
if (!apiKey) return null;
|
|
14940
|
-
return createOpenAIRerankProvider({ apiKey, model, baseUrl });
|
|
14941
|
-
}
|
|
14942
|
-
return null;
|
|
14943
|
-
}
|
|
14944
|
-
function createOpenAIRerankProvider(opts) {
|
|
14945
|
-
const baseUrl = opts.baseUrl ?? "https://api.openai.com/v1";
|
|
14946
|
-
return {
|
|
14947
|
-
id: "openai-rerank",
|
|
14948
|
-
model: opts.model,
|
|
14949
|
-
async rerank(query, documents) {
|
|
14950
|
-
const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/rerank`, {
|
|
14951
|
-
method: "POST",
|
|
14952
|
-
headers: {
|
|
14953
|
-
"content-type": "application/json",
|
|
14954
|
-
authorization: authHeader2(opts.apiKey)
|
|
14955
|
-
},
|
|
14956
|
-
body: JSON.stringify({ model: opts.model, query, documents })
|
|
14957
|
-
});
|
|
14958
|
-
if (!resp.ok) {
|
|
14959
|
-
const body = await resp.text().catch(() => "");
|
|
14960
|
-
throw new Error(`Rerank API failed: ${resp.status} ${resp.statusText} ${body}`.trim());
|
|
14961
|
-
}
|
|
14962
|
-
const data = await resp.json();
|
|
14963
|
-
const results = data.results ?? [];
|
|
14964
|
-
return results.map((r) => {
|
|
14965
|
-
const index = typeof r.index === "number" ? r.index : Number.NaN;
|
|
14966
|
-
const relevance = typeof r.relevance_score === "number" ? r.relevance_score : Number.NaN;
|
|
14967
|
-
return { index, relevance_score: relevance };
|
|
14968
|
-
}).filter((r) => Number.isInteger(r.index) && Number.isFinite(r.relevance_score));
|
|
14969
|
-
}
|
|
14970
|
-
};
|
|
14971
|
-
}
|
|
14972
|
-
|
|
14973
|
-
// src/search/embed.ts
|
|
14974
|
-
async function embedMemory(db, memoryId, provider, opts) {
|
|
14975
|
-
const row = db.prepare("SELECT id, agent_id, content FROM memories WHERE id = ?").get(memoryId);
|
|
14976
|
-
if (!row) return false;
|
|
14977
|
-
if (opts?.agent_id && row.agent_id !== opts.agent_id) return false;
|
|
14978
|
-
const model = opts?.model ?? provider.model;
|
|
14979
|
-
const maxChars = opts?.maxChars ?? 2e3;
|
|
14980
|
-
const text = row.content.length > maxChars ? row.content.slice(0, maxChars) : row.content;
|
|
14981
|
-
const vector = await provider.embed(text);
|
|
14982
|
-
upsertEmbedding(db, {
|
|
14983
|
-
agent_id: row.agent_id,
|
|
14984
|
-
memory_id: row.id,
|
|
14985
|
-
model,
|
|
14986
|
-
vector
|
|
14987
|
-
});
|
|
14988
|
-
return true;
|
|
14989
|
-
}
|
|
14990
|
-
|
|
14991
14421
|
// src/sleep/sync.ts
|
|
14992
14422
|
init_memory();
|
|
14993
14423
|
|
|
@@ -15121,7 +14551,6 @@ function syncOne(db, input) {
|
|
|
15121
14551
|
}
|
|
15122
14552
|
case "update": {
|
|
15123
14553
|
if (!guardResult.existingId) return { action: "skipped", reason: "No existing ID for update" };
|
|
15124
|
-
createSnapshot(db, guardResult.existingId, "update", "sync");
|
|
15125
14554
|
updateMemory(db, guardResult.existingId, { content: input.content });
|
|
15126
14555
|
return { action: "updated", memoryId: guardResult.existingId, reason: guardResult.reason };
|
|
15127
14556
|
}
|
|
@@ -15129,7 +14558,6 @@ function syncOne(db, input) {
|
|
|
15129
14558
|
if (!guardResult.existingId || !guardResult.mergedContent) {
|
|
15130
14559
|
return { action: "skipped", reason: "Missing merge data" };
|
|
15131
14560
|
}
|
|
15132
|
-
createSnapshot(db, guardResult.existingId, "merge", "sync");
|
|
15133
14561
|
updateMemory(db, guardResult.existingId, { content: guardResult.mergedContent });
|
|
15134
14562
|
return { action: "merged", memoryId: guardResult.existingId, reason: guardResult.reason };
|
|
15135
14563
|
}
|
|
@@ -15201,18 +14629,12 @@ function getDecayedMemories(db, threshold = 0.05, opts) {
|
|
|
15201
14629
|
init_memory();
|
|
15202
14630
|
function runTidy(db, opts) {
|
|
15203
14631
|
const threshold = opts?.vitalityThreshold ?? 0.05;
|
|
15204
|
-
const maxSnapshots = opts?.maxSnapshotsPerMemory ?? 10;
|
|
15205
14632
|
const agentId = opts?.agent_id;
|
|
15206
14633
|
let archived = 0;
|
|
15207
14634
|
let orphansCleaned = 0;
|
|
15208
|
-
let snapshotsPruned = 0;
|
|
15209
14635
|
const transaction = db.transaction(() => {
|
|
15210
14636
|
const decayed = getDecayedMemories(db, threshold, agentId ? { agent_id: agentId } : void 0);
|
|
15211
14637
|
for (const mem of decayed) {
|
|
15212
|
-
try {
|
|
15213
|
-
createSnapshot(db, mem.id, "delete", "tidy");
|
|
15214
|
-
} catch {
|
|
15215
|
-
}
|
|
15216
14638
|
deleteMemory(db, mem.id);
|
|
15217
14639
|
archived++;
|
|
15218
14640
|
}
|
|
@@ -15224,35 +14646,15 @@ function runTidy(db, opts) {
|
|
|
15224
14646
|
"DELETE FROM paths WHERE memory_id NOT IN (SELECT id FROM memories)"
|
|
15225
14647
|
).run();
|
|
15226
14648
|
orphansCleaned = orphans.changes;
|
|
15227
|
-
const memoriesWithSnapshots = agentId ? db.prepare(
|
|
15228
|
-
`SELECT s.memory_id, COUNT(*) as cnt
|
|
15229
|
-
FROM snapshots s
|
|
15230
|
-
JOIN memories m ON m.id = s.memory_id
|
|
15231
|
-
WHERE m.agent_id = ?
|
|
15232
|
-
GROUP BY s.memory_id HAVING cnt > ?`
|
|
15233
|
-
).all(agentId, maxSnapshots) : db.prepare(
|
|
15234
|
-
`SELECT memory_id, COUNT(*) as cnt FROM snapshots
|
|
15235
|
-
GROUP BY memory_id HAVING cnt > ?`
|
|
15236
|
-
).all(maxSnapshots);
|
|
15237
|
-
for (const { memory_id } of memoriesWithSnapshots) {
|
|
15238
|
-
const pruned = db.prepare(
|
|
15239
|
-
`DELETE FROM snapshots WHERE id NOT IN (
|
|
15240
|
-
SELECT id FROM snapshots WHERE memory_id = ?
|
|
15241
|
-
ORDER BY created_at DESC LIMIT ?
|
|
15242
|
-
) AND memory_id = ?`
|
|
15243
|
-
).run(memory_id, maxSnapshots, memory_id);
|
|
15244
|
-
snapshotsPruned += pruned.changes;
|
|
15245
|
-
}
|
|
15246
14649
|
});
|
|
15247
14650
|
transaction();
|
|
15248
|
-
return { archived, orphansCleaned
|
|
14651
|
+
return { archived, orphansCleaned };
|
|
15249
14652
|
}
|
|
15250
14653
|
|
|
15251
14654
|
// src/sleep/govern.ts
|
|
15252
14655
|
function runGovern(db, opts) {
|
|
15253
14656
|
const agentId = opts?.agent_id;
|
|
15254
14657
|
let orphanPaths = 0;
|
|
15255
|
-
let orphanLinks = 0;
|
|
15256
14658
|
let emptyMemories = 0;
|
|
15257
14659
|
const transaction = db.transaction(() => {
|
|
15258
14660
|
const pathResult = agentId ? db.prepare(
|
|
@@ -15261,23 +14663,11 @@ function runGovern(db, opts) {
|
|
|
15261
14663
|
AND memory_id NOT IN (SELECT id FROM memories WHERE agent_id = ?)`
|
|
15262
14664
|
).run(agentId, agentId) : db.prepare("DELETE FROM paths WHERE memory_id NOT IN (SELECT id FROM memories)").run();
|
|
15263
14665
|
orphanPaths = pathResult.changes;
|
|
15264
|
-
const linkResult = agentId ? db.prepare(
|
|
15265
|
-
`DELETE FROM links WHERE
|
|
15266
|
-
agent_id = ? AND (
|
|
15267
|
-
source_id NOT IN (SELECT id FROM memories WHERE agent_id = ?) OR
|
|
15268
|
-
target_id NOT IN (SELECT id FROM memories WHERE agent_id = ?)
|
|
15269
|
-
)`
|
|
15270
|
-
).run(agentId, agentId, agentId) : db.prepare(
|
|
15271
|
-
`DELETE FROM links WHERE
|
|
15272
|
-
source_id NOT IN (SELECT id FROM memories) OR
|
|
15273
|
-
target_id NOT IN (SELECT id FROM memories)`
|
|
15274
|
-
).run();
|
|
15275
|
-
orphanLinks = linkResult.changes;
|
|
15276
14666
|
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();
|
|
15277
14667
|
emptyMemories = emptyResult.changes;
|
|
15278
14668
|
});
|
|
15279
14669
|
transaction();
|
|
15280
|
-
return { orphanPaths,
|
|
14670
|
+
return { orphanPaths, emptyMemories };
|
|
15281
14671
|
}
|
|
15282
14672
|
|
|
15283
14673
|
// src/sleep/boot.ts
|
|
@@ -15333,17 +14723,400 @@ function boot(db, opts) {
|
|
|
15333
14723
|
};
|
|
15334
14724
|
}
|
|
15335
14725
|
|
|
14726
|
+
// src/ingest/ingest.ts
|
|
14727
|
+
function slugify2(input) {
|
|
14728
|
+
return input.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, " ").trim().replace(/\s+/g, "-").slice(0, 64) || "item";
|
|
14729
|
+
}
|
|
14730
|
+
function classifyIngestType(text) {
|
|
14731
|
+
const lower = text.toLowerCase();
|
|
14732
|
+
if (/##\s*身份|\bidentity\b|\b我是\b|我是/.test(text)) {
|
|
14733
|
+
return "identity";
|
|
14734
|
+
}
|
|
14735
|
+
if (/##\s*情感|❤️|💕|爱你|感动|难过|开心|害怕|想念|表白/.test(text)) {
|
|
14736
|
+
return "emotion";
|
|
14737
|
+
}
|
|
14738
|
+
if (/##\s*决策|##\s*技术|选型|教训|\bknowledge\b|⚠️|复盘|经验/.test(text)) {
|
|
14739
|
+
return "knowledge";
|
|
14740
|
+
}
|
|
14741
|
+
if (/\d{4}-\d{2}-\d{2}|发生了|完成了|今天|昨日|刚刚|部署|上线/.test(text)) {
|
|
14742
|
+
return "event";
|
|
14743
|
+
}
|
|
14744
|
+
if (lower.length <= 12) return "event";
|
|
14745
|
+
return "knowledge";
|
|
14746
|
+
}
|
|
14747
|
+
function splitIngestBlocks(text) {
|
|
14748
|
+
const headingRegex = /^##\s+(.+)$/gm;
|
|
14749
|
+
const matches = [...text.matchAll(headingRegex)];
|
|
14750
|
+
const blocks = [];
|
|
14751
|
+
if (matches.length > 0) {
|
|
14752
|
+
for (let i = 0; i < matches.length; i++) {
|
|
14753
|
+
const match = matches[i];
|
|
14754
|
+
const start = match.index ?? 0;
|
|
14755
|
+
const end = i + 1 < matches.length ? matches[i + 1].index ?? text.length : text.length;
|
|
14756
|
+
const raw = text.slice(start, end).trim();
|
|
14757
|
+
const lines = raw.split("\n");
|
|
14758
|
+
const title = lines[0].replace(/^##\s+/, "").trim();
|
|
14759
|
+
const content = lines.slice(1).join("\n").trim();
|
|
14760
|
+
if (content) blocks.push({ title, content });
|
|
14761
|
+
}
|
|
14762
|
+
return blocks;
|
|
14763
|
+
}
|
|
14764
|
+
const bullets = text.split("\n").map((line) => line.trim()).filter((line) => /^[-*]\s+/.test(line)).map((line) => line.replace(/^[-*]\s+/, "").trim()).filter(Boolean);
|
|
14765
|
+
if (bullets.length > 0) {
|
|
14766
|
+
return bullets.map((content, i) => ({ title: `bullet-${i + 1}`, content }));
|
|
14767
|
+
}
|
|
14768
|
+
const plain = text.trim();
|
|
14769
|
+
if (!plain) return [];
|
|
14770
|
+
return [{ title: "ingest", content: plain }];
|
|
14771
|
+
}
|
|
14772
|
+
function extractIngestItems(text, source) {
|
|
14773
|
+
const blocks = splitIngestBlocks(text);
|
|
14774
|
+
return blocks.map((block, index) => {
|
|
14775
|
+
const merged = `${block.title}
|
|
14776
|
+
${block.content}`;
|
|
14777
|
+
const type = classifyIngestType(merged);
|
|
14778
|
+
const domain2 = type === "identity" ? "core" : type;
|
|
14779
|
+
const sourcePart = slugify2(source ?? "ingest");
|
|
14780
|
+
const uri = `${domain2}://ingest/${sourcePart}/${index + 1}-${slugify2(block.title)}`;
|
|
14781
|
+
return {
|
|
14782
|
+
index,
|
|
14783
|
+
title: block.title,
|
|
14784
|
+
content: block.content,
|
|
14785
|
+
type,
|
|
14786
|
+
uri
|
|
14787
|
+
};
|
|
14788
|
+
});
|
|
14789
|
+
}
|
|
14790
|
+
function ingestText(db, options) {
|
|
14791
|
+
const extracted = extractIngestItems(options.text, options.source);
|
|
14792
|
+
const dryRun = options.dryRun ?? false;
|
|
14793
|
+
const agentId = options.agentId ?? "default";
|
|
14794
|
+
if (dryRun) {
|
|
14795
|
+
return {
|
|
14796
|
+
extracted: extracted.length,
|
|
14797
|
+
written: 0,
|
|
14798
|
+
skipped: extracted.length,
|
|
14799
|
+
dry_run: true,
|
|
14800
|
+
details: extracted.map((item) => ({
|
|
14801
|
+
index: item.index,
|
|
14802
|
+
type: item.type,
|
|
14803
|
+
uri: item.uri,
|
|
14804
|
+
preview: item.content.slice(0, 80)
|
|
14805
|
+
}))
|
|
14806
|
+
};
|
|
14807
|
+
}
|
|
14808
|
+
let written = 0;
|
|
14809
|
+
let skipped = 0;
|
|
14810
|
+
const details = [];
|
|
14811
|
+
for (const item of extracted) {
|
|
14812
|
+
const result = syncOne(db, {
|
|
14813
|
+
content: item.content,
|
|
14814
|
+
type: item.type,
|
|
14815
|
+
uri: item.uri,
|
|
14816
|
+
source: `auto:${options.source ?? "ingest"}`,
|
|
14817
|
+
agent_id: agentId
|
|
14818
|
+
});
|
|
14819
|
+
if (result.action === "added" || result.action === "updated" || result.action === "merged") {
|
|
14820
|
+
written++;
|
|
14821
|
+
} else {
|
|
14822
|
+
skipped++;
|
|
14823
|
+
}
|
|
14824
|
+
details.push({
|
|
14825
|
+
index: item.index,
|
|
14826
|
+
type: item.type,
|
|
14827
|
+
uri: item.uri,
|
|
14828
|
+
action: result.action,
|
|
14829
|
+
reason: result.reason,
|
|
14830
|
+
memoryId: result.memoryId
|
|
14831
|
+
});
|
|
14832
|
+
}
|
|
14833
|
+
return {
|
|
14834
|
+
extracted: extracted.length,
|
|
14835
|
+
written,
|
|
14836
|
+
skipped,
|
|
14837
|
+
dry_run: false,
|
|
14838
|
+
details
|
|
14839
|
+
};
|
|
14840
|
+
}
|
|
14841
|
+
|
|
14842
|
+
// src/ingest/watcher.ts
|
|
14843
|
+
import { existsSync, readFileSync as readFileSync2, readdirSync, statSync, watch } from "fs";
|
|
14844
|
+
import { join, relative, resolve } from "path";
|
|
14845
|
+
function runAutoIngestWatcher(options) {
|
|
14846
|
+
const workspaceDir = resolve(options.workspaceDir);
|
|
14847
|
+
const memoryDir = join(workspaceDir, "memory");
|
|
14848
|
+
const memoryMdPath = join(workspaceDir, "MEMORY.md");
|
|
14849
|
+
const debounceMs = options.debounceMs ?? 1200;
|
|
14850
|
+
const initialScan = options.initialScan ?? true;
|
|
14851
|
+
const logger = options.logger ?? console;
|
|
14852
|
+
const timers = /* @__PURE__ */ new Map();
|
|
14853
|
+
const watchers = [];
|
|
14854
|
+
const stats = {
|
|
14855
|
+
triggers: 0,
|
|
14856
|
+
filesProcessed: 0,
|
|
14857
|
+
extracted: 0,
|
|
14858
|
+
written: 0,
|
|
14859
|
+
skipped: 0,
|
|
14860
|
+
errors: 0
|
|
14861
|
+
};
|
|
14862
|
+
let stopped = false;
|
|
14863
|
+
let queue = Promise.resolve();
|
|
14864
|
+
const toSource = (absPath) => {
|
|
14865
|
+
const rel = relative(workspaceDir, absPath).replace(/\\/g, "/");
|
|
14866
|
+
return rel || absPath;
|
|
14867
|
+
};
|
|
14868
|
+
const isTrackedMarkdownFile = (absPath) => {
|
|
14869
|
+
if (!absPath.endsWith(".md")) return false;
|
|
14870
|
+
if (resolve(absPath) === memoryMdPath) return true;
|
|
14871
|
+
const rel = relative(memoryDir, absPath).replace(/\\/g, "/");
|
|
14872
|
+
if (rel.startsWith("..") || rel === "") return false;
|
|
14873
|
+
return !rel.includes("/");
|
|
14874
|
+
};
|
|
14875
|
+
const ingestFile = (absPath, reason) => {
|
|
14876
|
+
if (stopped) return;
|
|
14877
|
+
if (!existsSync(absPath)) {
|
|
14878
|
+
logger.log(`[auto-ingest] skip missing file: ${toSource(absPath)} (reason=${reason})`);
|
|
14879
|
+
return;
|
|
14880
|
+
}
|
|
14881
|
+
let isFile = false;
|
|
14882
|
+
try {
|
|
14883
|
+
isFile = statSync(absPath).isFile();
|
|
14884
|
+
} catch (err) {
|
|
14885
|
+
stats.errors += 1;
|
|
14886
|
+
logger.warn(`[auto-ingest] stat failed for ${toSource(absPath)}: ${String(err)}`);
|
|
14887
|
+
return;
|
|
14888
|
+
}
|
|
14889
|
+
if (!isFile) return;
|
|
14890
|
+
try {
|
|
14891
|
+
const text = readFileSync2(absPath, "utf-8");
|
|
14892
|
+
const source = toSource(absPath);
|
|
14893
|
+
const result = ingestText(options.db, {
|
|
14894
|
+
text,
|
|
14895
|
+
source,
|
|
14896
|
+
agentId: options.agentId
|
|
14897
|
+
});
|
|
14898
|
+
stats.filesProcessed += 1;
|
|
14899
|
+
stats.extracted += result.extracted;
|
|
14900
|
+
stats.written += result.written;
|
|
14901
|
+
stats.skipped += result.skipped;
|
|
14902
|
+
logger.log(
|
|
14903
|
+
`[auto-ingest] file=${source} reason=${reason} extracted=${result.extracted} written=${result.written} skipped=${result.skipped}`
|
|
14904
|
+
);
|
|
14905
|
+
} catch (err) {
|
|
14906
|
+
stats.errors += 1;
|
|
14907
|
+
logger.error(`[auto-ingest] ingest failed for ${toSource(absPath)}: ${String(err)}`);
|
|
14908
|
+
}
|
|
14909
|
+
};
|
|
14910
|
+
const scheduleIngest = (absPath, reason) => {
|
|
14911
|
+
if (stopped) return;
|
|
14912
|
+
if (!isTrackedMarkdownFile(absPath)) return;
|
|
14913
|
+
stats.triggers += 1;
|
|
14914
|
+
const previous = timers.get(absPath);
|
|
14915
|
+
if (previous) clearTimeout(previous);
|
|
14916
|
+
const timer = setTimeout(() => {
|
|
14917
|
+
timers.delete(absPath);
|
|
14918
|
+
queue = queue.then(() => {
|
|
14919
|
+
ingestFile(absPath, reason);
|
|
14920
|
+
}).catch((err) => {
|
|
14921
|
+
stats.errors += 1;
|
|
14922
|
+
logger.error(`[auto-ingest] queue error: ${String(err)}`);
|
|
14923
|
+
});
|
|
14924
|
+
}, debounceMs);
|
|
14925
|
+
timers.set(absPath, timer);
|
|
14926
|
+
};
|
|
14927
|
+
const safeWatch = (dir, onEvent) => {
|
|
14928
|
+
if (!existsSync(dir)) {
|
|
14929
|
+
logger.warn(`[auto-ingest] watch path does not exist, skipping: ${dir}`);
|
|
14930
|
+
return;
|
|
14931
|
+
}
|
|
14932
|
+
try {
|
|
14933
|
+
const watcher = watch(dir, { persistent: true }, (eventType, filename) => {
|
|
14934
|
+
if (!filename) return;
|
|
14935
|
+
onEvent(eventType, filename.toString());
|
|
14936
|
+
});
|
|
14937
|
+
watchers.push(watcher);
|
|
14938
|
+
logger.log(`[auto-ingest] watching ${dir}`);
|
|
14939
|
+
} catch (err) {
|
|
14940
|
+
stats.errors += 1;
|
|
14941
|
+
logger.error(`[auto-ingest] failed to watch ${dir}: ${String(err)}`);
|
|
14942
|
+
}
|
|
14943
|
+
};
|
|
14944
|
+
safeWatch(workspaceDir, (eventType, filename) => {
|
|
14945
|
+
if (filename === "MEMORY.md") {
|
|
14946
|
+
scheduleIngest(join(workspaceDir, filename), `workspace:${eventType}`);
|
|
14947
|
+
}
|
|
14948
|
+
});
|
|
14949
|
+
safeWatch(memoryDir, (eventType, filename) => {
|
|
14950
|
+
if (filename.endsWith(".md")) {
|
|
14951
|
+
scheduleIngest(join(memoryDir, filename), `memory:${eventType}`);
|
|
14952
|
+
}
|
|
14953
|
+
});
|
|
14954
|
+
if (initialScan) {
|
|
14955
|
+
scheduleIngest(memoryMdPath, "initial");
|
|
14956
|
+
if (existsSync(memoryDir)) {
|
|
14957
|
+
for (const file2 of readdirSync(memoryDir)) {
|
|
14958
|
+
if (file2.endsWith(".md")) {
|
|
14959
|
+
scheduleIngest(join(memoryDir, file2), "initial");
|
|
14960
|
+
}
|
|
14961
|
+
}
|
|
14962
|
+
}
|
|
14963
|
+
}
|
|
14964
|
+
return {
|
|
14965
|
+
close: () => {
|
|
14966
|
+
if (stopped) return;
|
|
14967
|
+
stopped = true;
|
|
14968
|
+
for (const timer of timers.values()) {
|
|
14969
|
+
clearTimeout(timer);
|
|
14970
|
+
}
|
|
14971
|
+
timers.clear();
|
|
14972
|
+
for (const watcher of watchers) {
|
|
14973
|
+
try {
|
|
14974
|
+
watcher.close();
|
|
14975
|
+
} catch {
|
|
14976
|
+
}
|
|
14977
|
+
}
|
|
14978
|
+
logger.log(
|
|
14979
|
+
`[auto-ingest] stopped triggers=${stats.triggers} files=${stats.filesProcessed} extracted=${stats.extracted} written=${stats.written} skipped=${stats.skipped} errors=${stats.errors}`
|
|
14980
|
+
);
|
|
14981
|
+
}
|
|
14982
|
+
};
|
|
14983
|
+
}
|
|
14984
|
+
|
|
15336
14985
|
// src/mcp/server.ts
|
|
15337
14986
|
var DB_PATH = process.env.AGENT_MEMORY_DB ?? "./agent-memory.db";
|
|
15338
14987
|
var AGENT_ID = process.env.AGENT_MEMORY_AGENT_ID ?? "default";
|
|
14988
|
+
var PRIORITY_WEIGHT = {
|
|
14989
|
+
0: 4,
|
|
14990
|
+
1: 3,
|
|
14991
|
+
2: 2,
|
|
14992
|
+
3: 1
|
|
14993
|
+
};
|
|
14994
|
+
function formatMemory(memory, score) {
|
|
14995
|
+
return {
|
|
14996
|
+
id: memory.id,
|
|
14997
|
+
uri: null,
|
|
14998
|
+
content: memory.content,
|
|
14999
|
+
type: memory.type,
|
|
15000
|
+
priority: memory.priority,
|
|
15001
|
+
vitality: memory.vitality,
|
|
15002
|
+
score,
|
|
15003
|
+
updated_at: memory.updated_at
|
|
15004
|
+
};
|
|
15005
|
+
}
|
|
15006
|
+
function formatWarmBootNarrative(identities, emotions, knowledges, events, totalStats) {
|
|
15007
|
+
const now2 = Date.now();
|
|
15008
|
+
const sevenDaysAgo = now2 - 7 * 24 * 60 * 60 * 1e3;
|
|
15009
|
+
const recentEvents = events.filter((e) => new Date(e.updated_at).getTime() >= sevenDaysAgo);
|
|
15010
|
+
const olderEventCount = Math.max(0, events.length - recentEvents.length);
|
|
15011
|
+
const avgVitalitySource = [...identities, ...emotions, ...knowledges, ...events];
|
|
15012
|
+
const avgVitality = avgVitalitySource.length ? avgVitalitySource.reduce((s, m) => s + m.vitality, 0) / avgVitalitySource.length : 0;
|
|
15013
|
+
const lines = [];
|
|
15014
|
+
lines.push("## \u{1FAAA} \u6211\u662F\u8C01");
|
|
15015
|
+
if (identities.length === 0) {
|
|
15016
|
+
lines.push("\u6682\u65E0\u8EAB\u4EFD\u8BB0\u5FC6\u3002");
|
|
15017
|
+
} else {
|
|
15018
|
+
for (const m of identities.slice(0, 6)) {
|
|
15019
|
+
lines.push(`- ${m.content.slice(0, 140)}`);
|
|
15020
|
+
}
|
|
15021
|
+
}
|
|
15022
|
+
lines.push("", "## \u{1F495} \u6700\u8FD1\u7684\u60C5\u611F");
|
|
15023
|
+
if (emotions.length === 0) {
|
|
15024
|
+
lines.push("\u6682\u65E0\u60C5\u611F\u8BB0\u5FC6\u3002");
|
|
15025
|
+
} else {
|
|
15026
|
+
for (const m of emotions.slice(0, 6)) {
|
|
15027
|
+
lines.push(`- ${m.content.slice(0, 140)}\uFF08vitality: ${m.vitality.toFixed(2)}\uFF09`);
|
|
15028
|
+
}
|
|
15029
|
+
}
|
|
15030
|
+
lines.push("", "## \u{1F9E0} \u5173\u952E\u77E5\u8BC6");
|
|
15031
|
+
if (knowledges.length === 0) {
|
|
15032
|
+
lines.push("\u6682\u65E0\u77E5\u8BC6\u8BB0\u5FC6\u3002");
|
|
15033
|
+
} else {
|
|
15034
|
+
lines.push(`\u5171 ${knowledges.length} \u6761\u6D3B\u8DC3\u77E5\u8BC6\u8BB0\u5FC6`);
|
|
15035
|
+
for (const m of knowledges.slice(0, 8)) {
|
|
15036
|
+
lines.push(`- ${m.content.slice(0, 140)}\uFF08vitality: ${m.vitality.toFixed(2)}\uFF09`);
|
|
15037
|
+
}
|
|
15038
|
+
}
|
|
15039
|
+
lines.push("", "## \u{1F4C5} \u8FD1\u671F\u4E8B\u4EF6");
|
|
15040
|
+
if (recentEvents.length === 0) {
|
|
15041
|
+
lines.push("\u6700\u8FD1 7 \u5929\u65E0\u4E8B\u4EF6\u8BB0\u5FC6\u3002");
|
|
15042
|
+
} else {
|
|
15043
|
+
lines.push("\u6700\u8FD1 7 \u5929\u5185\u7684\u4E8B\u4EF6\uFF1A");
|
|
15044
|
+
for (const m of recentEvents.slice(0, 8)) {
|
|
15045
|
+
const dateLabel = m.updated_at.slice(5, 10);
|
|
15046
|
+
lines.push(`- [${dateLabel}] ${m.content.slice(0, 120)}`);
|
|
15047
|
+
}
|
|
15048
|
+
}
|
|
15049
|
+
if (olderEventCount > 0) {
|
|
15050
|
+
lines.push(`- ... \u53CA ${olderEventCount} \u6761\u66F4\u65E9\u4E8B\u4EF6`);
|
|
15051
|
+
}
|
|
15052
|
+
lines.push("", "## \u{1F4CA} \u8BB0\u5FC6\u6982\u51B5");
|
|
15053
|
+
lines.push(
|
|
15054
|
+
`\u603B\u8BA1 ${totalStats.total} \u6761 | identity: ${totalStats.by_type.identity ?? 0} | emotion: ${totalStats.by_type.emotion ?? 0} | knowledge: ${totalStats.by_type.knowledge ?? 0} | event: ${totalStats.by_type.event ?? 0}`
|
|
15055
|
+
);
|
|
15056
|
+
lines.push(`\u5E73\u5747 vitality: ${avgVitality.toFixed(2)}`);
|
|
15057
|
+
return lines.join("\n");
|
|
15058
|
+
}
|
|
15059
|
+
function getSummaryStats(db, agentId) {
|
|
15060
|
+
const row = db.prepare("SELECT COUNT(*) as total, COALESCE(AVG(vitality), 0) as avg FROM memories WHERE agent_id = ?").get(agentId);
|
|
15061
|
+
return { total: row.total, avgVitality: row.avg };
|
|
15062
|
+
}
|
|
15063
|
+
function getMemoryUri(db, memoryId, agentId) {
|
|
15064
|
+
const row = db.prepare("SELECT uri FROM paths WHERE memory_id = ? AND agent_id = ? ORDER BY created_at DESC LIMIT 1").get(memoryId, agentId);
|
|
15065
|
+
return row?.uri ?? "(no-uri)";
|
|
15066
|
+
}
|
|
15067
|
+
function formatReflectReport(input) {
|
|
15068
|
+
const lines = [];
|
|
15069
|
+
lines.push("## \u{1F319} Sleep Cycle \u62A5\u544A", "");
|
|
15070
|
+
if (input.phase === "decay" || input.phase === "all") {
|
|
15071
|
+
const decay = input.decaySummary ?? { updated: 0, decayed: 0, belowThreshold: 0 };
|
|
15072
|
+
lines.push("### Decay\uFF08\u8870\u51CF\uFF09");
|
|
15073
|
+
lines.push(`\u5904\u7406 ${decay.updated} \u6761\u8BB0\u5FC6\uFF0C\u5176\u4E2D ${decay.decayed} \u6761 vitality \u4E0B\u964D\u3002`);
|
|
15074
|
+
const details = (input.decayDetails ?? []).slice(0, 8);
|
|
15075
|
+
if (details.length > 0) {
|
|
15076
|
+
for (const d of details) {
|
|
15077
|
+
lines.push(`- ${d.uri} | ${d.type} P${d.priority} | ${d.oldVitality.toFixed(2)} \u2192 ${d.newVitality.toFixed(2)} | ${d.content.slice(0, 64)}`);
|
|
15078
|
+
}
|
|
15079
|
+
if ((input.decayDetails?.length ?? 0) > details.length) {
|
|
15080
|
+
lines.push(`- ... \u53CA ${(input.decayDetails?.length ?? 0) - details.length} \u6761\u66F4\u591A\u8870\u51CF\u8BB0\u5F55`);
|
|
15081
|
+
}
|
|
15082
|
+
}
|
|
15083
|
+
lines.push("");
|
|
15084
|
+
}
|
|
15085
|
+
if (input.phase === "tidy" || input.phase === "all") {
|
|
15086
|
+
const tidy = input.tidySummary ?? { archived: 0, orphansCleaned: 0 };
|
|
15087
|
+
lines.push("### Tidy\uFF08\u6574\u7406\uFF09");
|
|
15088
|
+
lines.push(`\u5F52\u6863 ${tidy.archived} \u6761\u4F4E\u6D3B\u529B\u8BB0\u5FC6\uFF0C\u6E05\u7406\u5B64\u513F\u8DEF\u5F84 ${tidy.orphansCleaned} \u6761\u3002`);
|
|
15089
|
+
const archived = (input.archivedDetails ?? []).slice(0, 8);
|
|
15090
|
+
if (archived.length > 0) {
|
|
15091
|
+
for (const a of archived) {
|
|
15092
|
+
lines.push(`- \u5F52\u6863 ${a.uri} | P${a.priority} vitality=${a.vitality.toFixed(2)} | ${a.content.slice(0, 64)}`);
|
|
15093
|
+
}
|
|
15094
|
+
if ((input.archivedDetails?.length ?? 0) > archived.length) {
|
|
15095
|
+
lines.push(`- ... \u53CA ${(input.archivedDetails?.length ?? 0) - archived.length} \u6761\u66F4\u591A\u5F52\u6863\u8BB0\u5F55`);
|
|
15096
|
+
}
|
|
15097
|
+
}
|
|
15098
|
+
lines.push("");
|
|
15099
|
+
}
|
|
15100
|
+
if (input.phase === "govern" || input.phase === "all") {
|
|
15101
|
+
const govern = input.governSummary ?? { orphanPaths: 0, emptyMemories: 0 };
|
|
15102
|
+
lines.push("### Govern\uFF08\u6CBB\u7406\uFF09");
|
|
15103
|
+
lines.push(`\u5B64\u513F\u8DEF\u5F84\uFF1A${govern.orphanPaths} \u6761`);
|
|
15104
|
+
lines.push(`\u7A7A\u8BB0\u5FC6\uFF1A${govern.emptyMemories} \u6761`);
|
|
15105
|
+
lines.push("");
|
|
15106
|
+
}
|
|
15107
|
+
lines.push("### \u{1F4CA} \u603B\u7ED3");
|
|
15108
|
+
const delta = input.after.total - input.before.total;
|
|
15109
|
+
const deltaLabel = delta > 0 ? `+${delta}` : `${delta}`;
|
|
15110
|
+
lines.push(`\u8BB0\u5FC6\u603B\u6570\uFF1A${input.before.total} \u2192 ${input.after.total}\uFF08${deltaLabel}\uFF09`);
|
|
15111
|
+
lines.push(`\u5E73\u5747 vitality\uFF1A${input.before.avgVitality.toFixed(2)} \u2192 ${input.after.avgVitality.toFixed(2)}`);
|
|
15112
|
+
return lines.join("\n");
|
|
15113
|
+
}
|
|
15339
15114
|
function createMcpServer(dbPath, agentId) {
|
|
15340
15115
|
const db = openDatabase({ path: dbPath ?? DB_PATH });
|
|
15341
15116
|
const aid = agentId ?? AGENT_ID;
|
|
15342
|
-
const embeddingProvider = getEmbeddingProviderFromEnv();
|
|
15343
|
-
const rerankerProvider = getRerankerProviderFromEnv();
|
|
15344
15117
|
const server = new McpServer({
|
|
15345
15118
|
name: "agent-memory",
|
|
15346
|
-
version: "
|
|
15119
|
+
version: "3.0.0"
|
|
15347
15120
|
});
|
|
15348
15121
|
server.tool(
|
|
15349
15122
|
"remember",
|
|
@@ -15357,12 +15130,6 @@ function createMcpServer(dbPath, agentId) {
|
|
|
15357
15130
|
},
|
|
15358
15131
|
async ({ content, type, uri, emotion_val, source }) => {
|
|
15359
15132
|
const result = syncOne(db, { content, type, uri, emotion_val, source, agent_id: aid });
|
|
15360
|
-
if (embeddingProvider && result.memoryId && (result.action === "added" || result.action === "updated" || result.action === "merged")) {
|
|
15361
|
-
try {
|
|
15362
|
-
await embedMemory(db, result.memoryId, embeddingProvider, { agent_id: aid });
|
|
15363
|
-
} catch {
|
|
15364
|
-
}
|
|
15365
|
-
}
|
|
15366
15133
|
return {
|
|
15367
15134
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
15368
15135
|
};
|
|
@@ -15370,65 +15137,49 @@ function createMcpServer(dbPath, agentId) {
|
|
|
15370
15137
|
);
|
|
15371
15138
|
server.tool(
|
|
15372
15139
|
"recall",
|
|
15373
|
-
"Search memories using
|
|
15140
|
+
"Search memories using BM25 full-text retrieval with inline priority\xD7vitality weighting.",
|
|
15374
15141
|
{
|
|
15375
15142
|
query: external_exports.string().describe("Search query (natural language)"),
|
|
15376
15143
|
limit: external_exports.number().default(10).describe("Max results to return")
|
|
15377
15144
|
},
|
|
15378
15145
|
async ({ query, limit }) => {
|
|
15379
|
-
const
|
|
15380
|
-
const
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15146
|
+
const expandedLimit = Math.max(limit * 2, limit);
|
|
15147
|
+
const raw = searchBM25(db, query, { agent_id: aid, limit: expandedLimit });
|
|
15148
|
+
const scored = raw.map((r) => {
|
|
15149
|
+
const weight = PRIORITY_WEIGHT[r.memory.priority] ?? 1;
|
|
15150
|
+
const vitality = Math.max(0.1, r.memory.vitality);
|
|
15151
|
+
return {
|
|
15152
|
+
memory: r.memory,
|
|
15153
|
+
score: r.score * weight * vitality
|
|
15154
|
+
};
|
|
15155
|
+
}).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
15156
|
+
for (const r of scored) {
|
|
15157
|
+
recordAccess(db, r.memory.id);
|
|
15384
15158
|
}
|
|
15385
|
-
const results = rerank(raw, { ...strategy, limit });
|
|
15386
15159
|
const output = {
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
count: results.length,
|
|
15390
|
-
memories: results.map((r) => ({
|
|
15391
|
-
id: r.memory.id,
|
|
15392
|
-
content: r.memory.content,
|
|
15393
|
-
type: r.memory.type,
|
|
15394
|
-
priority: r.memory.priority,
|
|
15395
|
-
vitality: r.memory.vitality,
|
|
15396
|
-
score: r.score,
|
|
15397
|
-
updated_at: r.memory.updated_at
|
|
15398
|
-
}))
|
|
15160
|
+
count: scored.length,
|
|
15161
|
+
memories: scored.map((r) => formatMemory(r.memory, r.score))
|
|
15399
15162
|
};
|
|
15400
|
-
for (const r of results) {
|
|
15401
|
-
recordAccess(db, r.memory.id);
|
|
15402
|
-
}
|
|
15403
15163
|
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
15404
15164
|
}
|
|
15405
15165
|
);
|
|
15406
15166
|
server.tool(
|
|
15407
15167
|
"recall_path",
|
|
15408
|
-
"Read memory at a specific URI path, or list memories under a URI prefix.
|
|
15168
|
+
"Read memory at a specific URI path, or list memories under a URI prefix.",
|
|
15409
15169
|
{
|
|
15410
15170
|
uri: external_exports.string().describe("URI path (e.g. core://user/name) or prefix (e.g. core://user/)"),
|
|
15411
|
-
traverse_hops: external_exports.number().default(0).describe("
|
|
15171
|
+
traverse_hops: external_exports.number().default(0).describe("Traversal depth (deprecated, reserved for compatibility)")
|
|
15412
15172
|
},
|
|
15413
|
-
async ({ uri
|
|
15173
|
+
async ({ uri }) => {
|
|
15414
15174
|
const path = getPathByUri(db, uri, aid);
|
|
15415
15175
|
if (path) {
|
|
15416
15176
|
const mem = getMemory(db, path.memory_id);
|
|
15417
15177
|
if (mem && mem.agent_id === aid) {
|
|
15418
15178
|
recordAccess(db, mem.id);
|
|
15419
|
-
let related = [];
|
|
15420
|
-
if (traverse_hops > 0) {
|
|
15421
|
-
const hops = traverse(db, mem.id, traverse_hops, aid);
|
|
15422
|
-
related = hops.map((h) => {
|
|
15423
|
-
const m = getMemory(db, h.id);
|
|
15424
|
-
if (!m || m.agent_id !== aid) return { id: h.id, content: "", relation: h.relation, hop: h.hop };
|
|
15425
|
-
return { id: h.id, content: m.content, relation: h.relation, hop: h.hop };
|
|
15426
|
-
});
|
|
15427
|
-
}
|
|
15428
15179
|
return {
|
|
15429
15180
|
content: [{
|
|
15430
15181
|
type: "text",
|
|
15431
|
-
text: JSON.stringify({ found: true, memory: mem
|
|
15182
|
+
text: JSON.stringify({ found: true, memory: mem }, null, 2)
|
|
15432
15183
|
}]
|
|
15433
15184
|
};
|
|
15434
15185
|
}
|
|
@@ -15438,7 +15189,7 @@ function createMcpServer(dbPath, agentId) {
|
|
|
15438
15189
|
const memories = paths.map((p) => {
|
|
15439
15190
|
const m = getMemory(db, p.memory_id);
|
|
15440
15191
|
if (!m || m.agent_id !== aid) return { uri: p.uri, content: void 0, type: void 0, priority: void 0 };
|
|
15441
|
-
return { uri: p.uri, content: m.content, type: m.type, priority: m.priority };
|
|
15192
|
+
return { uri: p.uri, content: m.content, type: m.type, priority: m.priority, vitality: m.vitality };
|
|
15442
15193
|
});
|
|
15443
15194
|
return {
|
|
15444
15195
|
content: [{
|
|
@@ -15452,21 +15203,33 @@ function createMcpServer(dbPath, agentId) {
|
|
|
15452
15203
|
);
|
|
15453
15204
|
server.tool(
|
|
15454
15205
|
"boot",
|
|
15455
|
-
"Load
|
|
15456
|
-
{
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15206
|
+
"Load startup memories. Default output is narrative markdown; pass format=json for legacy output.",
|
|
15207
|
+
{
|
|
15208
|
+
format: external_exports.enum(["narrative", "json"]).default("narrative").optional()
|
|
15209
|
+
},
|
|
15210
|
+
async ({ format }) => {
|
|
15211
|
+
const outputFormat = format ?? "narrative";
|
|
15212
|
+
const base = boot(db, { agent_id: aid });
|
|
15213
|
+
if (outputFormat === "json") {
|
|
15214
|
+
const jsonOutput = {
|
|
15215
|
+
count: base.identityMemories.length,
|
|
15216
|
+
bootPaths: base.bootPaths,
|
|
15217
|
+
memories: base.identityMemories.map((m) => ({
|
|
15218
|
+
id: m.id,
|
|
15219
|
+
content: m.content,
|
|
15220
|
+
type: m.type,
|
|
15221
|
+
priority: m.priority
|
|
15222
|
+
}))
|
|
15223
|
+
};
|
|
15224
|
+
return { content: [{ type: "text", text: JSON.stringify(jsonOutput, null, 2) }] };
|
|
15225
|
+
}
|
|
15226
|
+
const identity = listMemories(db, { agent_id: aid, type: "identity", limit: 12 });
|
|
15227
|
+
const emotion = listMemories(db, { agent_id: aid, type: "emotion", min_vitality: 0.1, limit: 12 }).sort((a, b) => b.vitality - a.vitality);
|
|
15228
|
+
const knowledge = listMemories(db, { agent_id: aid, type: "knowledge", min_vitality: 0.1, limit: 16 }).sort((a, b) => b.vitality - a.vitality);
|
|
15229
|
+
const event = listMemories(db, { agent_id: aid, type: "event", min_vitality: 0, limit: 24 }).sort((a, b) => b.vitality - a.vitality);
|
|
15230
|
+
const stats = countMemories(db, aid);
|
|
15231
|
+
const narrative = formatWarmBootNarrative(identity.length > 0 ? identity : base.identityMemories, emotion, knowledge, event, stats);
|
|
15232
|
+
return { content: [{ type: "text", text: narrative }] };
|
|
15470
15233
|
}
|
|
15471
15234
|
);
|
|
15472
15235
|
server.tool(
|
|
@@ -15480,7 +15243,6 @@ function createMcpServer(dbPath, agentId) {
|
|
|
15480
15243
|
const mem = getMemory(db, id);
|
|
15481
15244
|
if (!mem || mem.agent_id !== aid) return { content: [{ type: "text", text: '{"error": "Memory not found"}' }] };
|
|
15482
15245
|
if (hard) {
|
|
15483
|
-
createSnapshot(db, id, "delete", "forget");
|
|
15484
15246
|
const { deleteMemory: deleteMemory2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
|
|
15485
15247
|
deleteMemory2(db, id);
|
|
15486
15248
|
return { content: [{ type: "text", text: JSON.stringify({ action: "deleted", id }) }] };
|
|
@@ -15494,97 +15256,80 @@ function createMcpServer(dbPath, agentId) {
|
|
|
15494
15256
|
};
|
|
15495
15257
|
}
|
|
15496
15258
|
);
|
|
15497
|
-
server.tool(
|
|
15498
|
-
"link",
|
|
15499
|
-
"Create or query associations between memories (knowledge graph).",
|
|
15500
|
-
{
|
|
15501
|
-
action: external_exports.enum(["create", "query", "traverse"]).describe("Action to perform"),
|
|
15502
|
-
source_id: external_exports.string().optional().describe("Source memory ID"),
|
|
15503
|
-
target_id: external_exports.string().optional().describe("Target memory ID (for create)"),
|
|
15504
|
-
relation: external_exports.enum(["related", "caused", "reminds", "evolved", "contradicts"]).optional().describe("Relation type"),
|
|
15505
|
-
max_hops: external_exports.number().default(2).describe("Max traversal depth (for traverse action)")
|
|
15506
|
-
},
|
|
15507
|
-
async ({ action, source_id, target_id, relation, max_hops }) => {
|
|
15508
|
-
if (action === "create" && source_id && target_id && relation) {
|
|
15509
|
-
const link = createLink(db, source_id, target_id, relation, 1, aid);
|
|
15510
|
-
return { content: [{ type: "text", text: JSON.stringify({ created: link }) }] };
|
|
15511
|
-
}
|
|
15512
|
-
if (action === "query" && source_id) {
|
|
15513
|
-
const links = getLinks(db, source_id, aid);
|
|
15514
|
-
return { content: [{ type: "text", text: JSON.stringify({ links }) }] };
|
|
15515
|
-
}
|
|
15516
|
-
if (action === "traverse" && source_id) {
|
|
15517
|
-
const nodes = traverse(db, source_id, max_hops, aid);
|
|
15518
|
-
const detailed = nodes.map((n) => {
|
|
15519
|
-
const m = getMemory(db, n.id);
|
|
15520
|
-
if (!m || m.agent_id !== aid) return { ...n, content: void 0 };
|
|
15521
|
-
return { ...n, content: m.content };
|
|
15522
|
-
});
|
|
15523
|
-
return { content: [{ type: "text", text: JSON.stringify({ nodes: detailed }) }] };
|
|
15524
|
-
}
|
|
15525
|
-
return { content: [{ type: "text", text: '{"error": "Invalid action or missing params"}' }] };
|
|
15526
|
-
}
|
|
15527
|
-
);
|
|
15528
|
-
server.tool(
|
|
15529
|
-
"snapshot",
|
|
15530
|
-
"View or rollback memory snapshots (version history).",
|
|
15531
|
-
{
|
|
15532
|
-
action: external_exports.enum(["list", "rollback"]).describe("list snapshots or rollback to one"),
|
|
15533
|
-
memory_id: external_exports.string().optional().describe("Memory ID (for list)"),
|
|
15534
|
-
snapshot_id: external_exports.string().optional().describe("Snapshot ID (for rollback)")
|
|
15535
|
-
},
|
|
15536
|
-
async ({ action, memory_id, snapshot_id }) => {
|
|
15537
|
-
if (action === "list" && memory_id) {
|
|
15538
|
-
const mem = getMemory(db, memory_id);
|
|
15539
|
-
if (!mem || mem.agent_id !== aid) return { content: [{ type: "text", text: '{"error": "Memory not found"}' }] };
|
|
15540
|
-
const snaps = getSnapshots(db, memory_id);
|
|
15541
|
-
return { content: [{ type: "text", text: JSON.stringify({ snapshots: snaps }) }] };
|
|
15542
|
-
}
|
|
15543
|
-
if (action === "rollback" && snapshot_id) {
|
|
15544
|
-
const snap = getSnapshot(db, snapshot_id);
|
|
15545
|
-
if (!snap) return { content: [{ type: "text", text: '{"error": "Snapshot not found"}' }] };
|
|
15546
|
-
const mem = getMemory(db, snap.memory_id);
|
|
15547
|
-
if (!mem || mem.agent_id !== aid) return { content: [{ type: "text", text: '{"error": "Snapshot not found"}' }] };
|
|
15548
|
-
const ok = rollback(db, snapshot_id);
|
|
15549
|
-
return { content: [{ type: "text", text: JSON.stringify({ rolled_back: ok }) }] };
|
|
15550
|
-
}
|
|
15551
|
-
return { content: [{ type: "text", text: '{"error": "Invalid action or missing params"}' }] };
|
|
15552
|
-
}
|
|
15553
|
-
);
|
|
15554
15259
|
server.tool(
|
|
15555
15260
|
"reflect",
|
|
15556
|
-
"Trigger sleep cycle phases
|
|
15261
|
+
"Trigger sleep cycle phases and return a human-readable markdown report.",
|
|
15557
15262
|
{
|
|
15558
15263
|
phase: external_exports.enum(["decay", "tidy", "govern", "all"]).describe("Which sleep phase to run")
|
|
15559
15264
|
},
|
|
15560
15265
|
async ({ phase }) => {
|
|
15561
|
-
const
|
|
15266
|
+
const before = getSummaryStats(db, aid);
|
|
15267
|
+
let decaySummary;
|
|
15268
|
+
let tidySummary;
|
|
15269
|
+
let governSummary;
|
|
15270
|
+
const decayDetails = [];
|
|
15271
|
+
const archivedDetails = [];
|
|
15562
15272
|
if (phase === "decay" || phase === "all") {
|
|
15563
|
-
|
|
15273
|
+
const beforeRows = db.prepare("SELECT id, type, priority, vitality, content FROM memories WHERE agent_id = ?").all(aid);
|
|
15274
|
+
const beforeMap = new Map(beforeRows.map((r) => [r.id, r]));
|
|
15275
|
+
decaySummary = runDecay(db, { agent_id: aid });
|
|
15276
|
+
const afterRows = db.prepare("SELECT id, vitality FROM memories WHERE agent_id = ?").all(aid);
|
|
15277
|
+
for (const row of afterRows) {
|
|
15278
|
+
const prev = beforeMap.get(row.id);
|
|
15279
|
+
if (!prev) continue;
|
|
15280
|
+
if (row.vitality < prev.vitality - 1e-3) {
|
|
15281
|
+
decayDetails.push({
|
|
15282
|
+
uri: getMemoryUri(db, row.id, aid),
|
|
15283
|
+
type: prev.type,
|
|
15284
|
+
priority: prev.priority,
|
|
15285
|
+
oldVitality: prev.vitality,
|
|
15286
|
+
newVitality: row.vitality,
|
|
15287
|
+
content: prev.content
|
|
15288
|
+
});
|
|
15289
|
+
}
|
|
15290
|
+
}
|
|
15291
|
+
decayDetails.sort((a, b) => b.oldVitality - b.newVitality - (a.oldVitality - a.newVitality));
|
|
15564
15292
|
}
|
|
15565
15293
|
if (phase === "tidy" || phase === "all") {
|
|
15566
|
-
|
|
15294
|
+
const candidates = db.prepare("SELECT id, content, vitality, priority FROM memories WHERE agent_id = ? AND vitality < 0.05 AND priority >= 3").all(aid);
|
|
15295
|
+
tidySummary = runTidy(db, { agent_id: aid });
|
|
15296
|
+
for (const c of candidates) {
|
|
15297
|
+
const uriBeforeDelete = getMemoryUri(db, c.id, aid);
|
|
15298
|
+
const exists = db.prepare("SELECT id FROM memories WHERE id = ?").get(c.id);
|
|
15299
|
+
if (!exists) {
|
|
15300
|
+
archivedDetails.push({
|
|
15301
|
+
uri: uriBeforeDelete,
|
|
15302
|
+
content: c.content,
|
|
15303
|
+
vitality: c.vitality,
|
|
15304
|
+
priority: c.priority
|
|
15305
|
+
});
|
|
15306
|
+
}
|
|
15307
|
+
}
|
|
15567
15308
|
}
|
|
15568
15309
|
if (phase === "govern" || phase === "all") {
|
|
15569
|
-
|
|
15570
|
-
}
|
|
15571
|
-
|
|
15310
|
+
governSummary = runGovern(db, { agent_id: aid });
|
|
15311
|
+
}
|
|
15312
|
+
const after = getSummaryStats(db, aid);
|
|
15313
|
+
const report = formatReflectReport({
|
|
15314
|
+
phase,
|
|
15315
|
+
decaySummary,
|
|
15316
|
+
decayDetails,
|
|
15317
|
+
tidySummary,
|
|
15318
|
+
archivedDetails,
|
|
15319
|
+
governSummary,
|
|
15320
|
+
before,
|
|
15321
|
+
after
|
|
15322
|
+
});
|
|
15323
|
+
return { content: [{ type: "text", text: report }] };
|
|
15572
15324
|
}
|
|
15573
15325
|
);
|
|
15574
15326
|
server.tool(
|
|
15575
15327
|
"status",
|
|
15576
|
-
"Get memory system statistics: counts by type/priority
|
|
15328
|
+
"Get memory system statistics: counts by type/priority and health metrics.",
|
|
15577
15329
|
{},
|
|
15578
15330
|
async () => {
|
|
15579
15331
|
const stats = countMemories(db, aid);
|
|
15580
15332
|
const lowVitality = db.prepare("SELECT COUNT(*) as c FROM memories WHERE vitality < 0.1 AND agent_id = ?").get(aid);
|
|
15581
|
-
const totalSnapshots = db.prepare(
|
|
15582
|
-
`SELECT COUNT(*) as c
|
|
15583
|
-
FROM snapshots s
|
|
15584
|
-
JOIN memories m ON m.id = s.memory_id
|
|
15585
|
-
WHERE m.agent_id = ?`
|
|
15586
|
-
).get(aid);
|
|
15587
|
-
const totalLinks = db.prepare("SELECT COUNT(*) as c FROM links WHERE agent_id = ?").get(aid);
|
|
15588
15333
|
const totalPaths = db.prepare("SELECT COUNT(*) as c FROM paths WHERE agent_id = ?").get(aid);
|
|
15589
15334
|
return {
|
|
15590
15335
|
content: [{
|
|
@@ -15592,8 +15337,6 @@ function createMcpServer(dbPath, agentId) {
|
|
|
15592
15337
|
text: JSON.stringify({
|
|
15593
15338
|
...stats,
|
|
15594
15339
|
paths: totalPaths.c,
|
|
15595
|
-
links: totalLinks.c,
|
|
15596
|
-
snapshots: totalSnapshots.c,
|
|
15597
15340
|
low_vitality: lowVitality.c,
|
|
15598
15341
|
agent_id: aid
|
|
15599
15342
|
}, null, 2)
|
|
@@ -15601,11 +15344,104 @@ function createMcpServer(dbPath, agentId) {
|
|
|
15601
15344
|
};
|
|
15602
15345
|
}
|
|
15603
15346
|
);
|
|
15347
|
+
server.tool(
|
|
15348
|
+
"ingest",
|
|
15349
|
+
"Extract structured memories from markdown text and write via syncOne().",
|
|
15350
|
+
{
|
|
15351
|
+
text: external_exports.string().describe("Markdown/plain text to ingest"),
|
|
15352
|
+
source: external_exports.string().optional().describe("Source annotation, e.g. memory/2026-02-23.md"),
|
|
15353
|
+
dry_run: external_exports.boolean().default(false).optional().describe("Preview extraction without writing")
|
|
15354
|
+
},
|
|
15355
|
+
async ({ text, source, dry_run }) => {
|
|
15356
|
+
const result = ingestText(db, {
|
|
15357
|
+
text,
|
|
15358
|
+
source,
|
|
15359
|
+
dryRun: dry_run,
|
|
15360
|
+
agentId: aid
|
|
15361
|
+
});
|
|
15362
|
+
return {
|
|
15363
|
+
content: [{
|
|
15364
|
+
type: "text",
|
|
15365
|
+
text: JSON.stringify(result, null, 2)
|
|
15366
|
+
}]
|
|
15367
|
+
};
|
|
15368
|
+
}
|
|
15369
|
+
);
|
|
15370
|
+
server.tool(
|
|
15371
|
+
"surface",
|
|
15372
|
+
"Lightweight readonly memory surfacing: keyword OR search + priority\xD7vitality\xD7hitRatio ranking (no access recording).",
|
|
15373
|
+
{
|
|
15374
|
+
keywords: external_exports.array(external_exports.string()).min(1).describe("Keywords to surface related memories"),
|
|
15375
|
+
limit: external_exports.number().min(1).max(20).default(5).optional().describe("Max results (default 5, max 20)"),
|
|
15376
|
+
types: external_exports.array(external_exports.enum(["identity", "emotion", "knowledge", "event"])).optional().describe("Optional type filter"),
|
|
15377
|
+
min_vitality: external_exports.number().min(0).max(1).default(0.1).optional().describe("Minimum vitality filter")
|
|
15378
|
+
},
|
|
15379
|
+
async ({ keywords, limit, types, min_vitality }) => {
|
|
15380
|
+
const maxResults = limit ?? 5;
|
|
15381
|
+
const minVitality = min_vitality ?? 0.1;
|
|
15382
|
+
const normalizedKeywords = keywords.map((k) => k.trim()).filter(Boolean);
|
|
15383
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
15384
|
+
for (const kw of normalizedKeywords) {
|
|
15385
|
+
const results = searchBM25(db, kw, { agent_id: aid, limit: 50, min_vitality: minVitality });
|
|
15386
|
+
for (const r of results) {
|
|
15387
|
+
const existing = candidates.get(r.memory.id);
|
|
15388
|
+
if (existing) {
|
|
15389
|
+
existing.hits += 1;
|
|
15390
|
+
} else {
|
|
15391
|
+
candidates.set(r.memory.id, { memory: r.memory, hits: 1 });
|
|
15392
|
+
}
|
|
15393
|
+
}
|
|
15394
|
+
}
|
|
15395
|
+
const scored = [...candidates.values()].filter((c) => c.memory.vitality >= minVitality).filter((c) => types?.length ? types.includes(c.memory.type) : true).map((c) => {
|
|
15396
|
+
const weight = PRIORITY_WEIGHT[c.memory.priority] ?? 1;
|
|
15397
|
+
const hitRatio = normalizedKeywords.length > 0 ? c.hits / normalizedKeywords.length : 0;
|
|
15398
|
+
const score = weight * c.memory.vitality * hitRatio;
|
|
15399
|
+
return {
|
|
15400
|
+
memory: c.memory,
|
|
15401
|
+
hits: c.hits,
|
|
15402
|
+
score,
|
|
15403
|
+
hitRatio
|
|
15404
|
+
};
|
|
15405
|
+
}).sort((a, b) => b.score - a.score).slice(0, maxResults);
|
|
15406
|
+
const output = {
|
|
15407
|
+
count: scored.length,
|
|
15408
|
+
results: scored.map((s) => ({
|
|
15409
|
+
id: s.memory.id,
|
|
15410
|
+
uri: getMemoryUri(db, s.memory.id, aid),
|
|
15411
|
+
type: s.memory.type,
|
|
15412
|
+
priority: s.memory.priority,
|
|
15413
|
+
vitality: s.memory.vitality,
|
|
15414
|
+
content: s.memory.content,
|
|
15415
|
+
score: s.score,
|
|
15416
|
+
keyword_hits: s.hits,
|
|
15417
|
+
updated_at: s.memory.updated_at
|
|
15418
|
+
}))
|
|
15419
|
+
};
|
|
15420
|
+
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
15421
|
+
}
|
|
15422
|
+
);
|
|
15604
15423
|
return { server, db };
|
|
15605
15424
|
}
|
|
15606
15425
|
async function main() {
|
|
15607
|
-
const { server } = createMcpServer();
|
|
15426
|
+
const { server, db } = createMcpServer();
|
|
15608
15427
|
const transport = new StdioServerTransport();
|
|
15428
|
+
const autoIngestEnabled = process.env.AGENT_MEMORY_AUTO_INGEST !== "0";
|
|
15429
|
+
const workspaceDir = process.env.AGENT_MEMORY_WORKSPACE ?? `${process.env.HOME ?? "."}/.openclaw/workspace`;
|
|
15430
|
+
const agentId = process.env.AGENT_MEMORY_AGENT_ID ?? "default";
|
|
15431
|
+
const watcher = autoIngestEnabled ? runAutoIngestWatcher({
|
|
15432
|
+
db,
|
|
15433
|
+
workspaceDir,
|
|
15434
|
+
agentId
|
|
15435
|
+
}) : null;
|
|
15436
|
+
const shutdown = () => {
|
|
15437
|
+
try {
|
|
15438
|
+
watcher?.close();
|
|
15439
|
+
} catch {
|
|
15440
|
+
}
|
|
15441
|
+
};
|
|
15442
|
+
process.once("SIGINT", shutdown);
|
|
15443
|
+
process.once("SIGTERM", shutdown);
|
|
15444
|
+
process.once("exit", shutdown);
|
|
15609
15445
|
await server.connect(transport);
|
|
15610
15446
|
}
|
|
15611
15447
|
var isMain = process.argv[1]?.includes("server");
|