@hasna/mementos 0.10.15 → 0.10.17
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/dist/mcp/index.js +89 -5
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -9826,6 +9826,73 @@ ${lines.join(`
|
|
|
9826
9826
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9827
9827
|
}
|
|
9828
9828
|
});
|
|
9829
|
+
server.tool("memory_health", "Comprehensive health check for memories. Detects: stale (old + 0 access), high-importance-forgotten (importance>=7 + not accessed in 60d), and possibly-superseded (newer memory with similar key). Returns actionable summary.", {
|
|
9830
|
+
stale_days: exports_external.coerce.number().optional().describe("Days with no access to consider a memory stale (default: 30)"),
|
|
9831
|
+
forgotten_days: exports_external.coerce.number().optional().describe("Days since access for high-importance memories (default: 60)"),
|
|
9832
|
+
project_id: exports_external.string().optional(),
|
|
9833
|
+
agent_id: exports_external.string().optional(),
|
|
9834
|
+
limit: exports_external.coerce.number().optional().describe("Max per category (default: 10)")
|
|
9835
|
+
}, async (args) => {
|
|
9836
|
+
try {
|
|
9837
|
+
const db = getDatabase();
|
|
9838
|
+
const staleDays = args.stale_days ?? 30;
|
|
9839
|
+
const forgottenDays = args.forgotten_days ?? 60;
|
|
9840
|
+
const limit = args.limit ?? 10;
|
|
9841
|
+
const extraWhere = [
|
|
9842
|
+
...args.project_id ? ["project_id = ?"] : [],
|
|
9843
|
+
...args.agent_id ? ["agent_id = ?"] : []
|
|
9844
|
+
].join(" AND ");
|
|
9845
|
+
const extraParams = [
|
|
9846
|
+
...args.project_id ? [args.project_id] : [],
|
|
9847
|
+
...args.agent_id ? [args.agent_id] : []
|
|
9848
|
+
];
|
|
9849
|
+
const base = `status = 'active' AND pinned = 0${extraWhere ? " AND " + extraWhere : ""}`;
|
|
9850
|
+
const stale = db.prepare(`SELECT id, key, value, importance, scope, created_at FROM memories
|
|
9851
|
+
WHERE ${base} AND access_count = 0 AND created_at < datetime('now', '-${staleDays} days')
|
|
9852
|
+
ORDER BY created_at ASC LIMIT ?`).all(...extraParams, limit);
|
|
9853
|
+
const forgotten = db.prepare(`SELECT id, key, value, importance, scope, accessed_at FROM memories
|
|
9854
|
+
WHERE ${base} AND importance >= 7
|
|
9855
|
+
AND (accessed_at IS NULL OR accessed_at < datetime('now', '-${forgottenDays} days'))
|
|
9856
|
+
ORDER BY importance DESC, COALESCE(accessed_at, created_at) ASC LIMIT ?`).all(...extraParams, limit);
|
|
9857
|
+
const dupes = db.prepare(`SELECT key, COUNT(*) as cnt, MAX(updated_at) as latest, MIN(created_at) as oldest
|
|
9858
|
+
FROM memories WHERE ${base}
|
|
9859
|
+
GROUP BY key HAVING cnt > 1
|
|
9860
|
+
ORDER BY cnt DESC LIMIT ?`).all(...extraParams, limit);
|
|
9861
|
+
const parts = [`Memory Health Report
|
|
9862
|
+
`];
|
|
9863
|
+
if (stale.length > 0) {
|
|
9864
|
+
parts.push(`\u26A0\uFE0F STALE (${stale.length}) \u2014 created ${staleDays}d+ ago, never accessed:`);
|
|
9865
|
+
for (const m of stale) {
|
|
9866
|
+
parts.push(` \u2022 [${m.importance}] ${m.key} (${m.scope}) \u2014 created ${m.created_at.slice(0, 10)}`);
|
|
9867
|
+
}
|
|
9868
|
+
parts.push("");
|
|
9869
|
+
}
|
|
9870
|
+
if (forgotten.length > 0) {
|
|
9871
|
+
parts.push(`\uD83D\uDD14 HIGH-IMPORTANCE FORGOTTEN (${forgotten.length}) \u2014 importance\u22657, not accessed in ${forgottenDays}d+:`);
|
|
9872
|
+
for (const m of forgotten) {
|
|
9873
|
+
parts.push(` \u2022 [${m.importance}] ${m.key} (${m.scope}) \u2014 last: ${m.accessed_at?.slice(0, 10) || "never"}`);
|
|
9874
|
+
}
|
|
9875
|
+
parts.push("");
|
|
9876
|
+
}
|
|
9877
|
+
if (dupes.length > 0) {
|
|
9878
|
+
parts.push(`\uD83D\uDD04 POSSIBLY SUPERSEDED (${dupes.length}) \u2014 same key with multiple versions:`);
|
|
9879
|
+
for (const d of dupes) {
|
|
9880
|
+
parts.push(` \u2022 ${d.key} \xD7 ${d.cnt} copies \u2014 newest: ${d.latest.slice(0, 10)}`);
|
|
9881
|
+
}
|
|
9882
|
+
parts.push("");
|
|
9883
|
+
}
|
|
9884
|
+
if (stale.length === 0 && forgotten.length === 0 && dupes.length === 0) {
|
|
9885
|
+
parts.push("\u2713 No health issues found. All memories look fresh.");
|
|
9886
|
+
} else {
|
|
9887
|
+
parts.push(`Summary: ${stale.length} stale, ${forgotten.length} forgotten, ${dupes.length} possibly-superseded.`);
|
|
9888
|
+
parts.push("Suggested actions: archive stale memories, review forgotten ones, merge duplicates.");
|
|
9889
|
+
}
|
|
9890
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
9891
|
+
`) }] };
|
|
9892
|
+
} catch (e) {
|
|
9893
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
9894
|
+
}
|
|
9895
|
+
});
|
|
9829
9896
|
server.tool("memory_search", "Search memories by keyword across key, value, summary, and tags", {
|
|
9830
9897
|
query: exports_external.string(),
|
|
9831
9898
|
scope: exports_external.enum(["global", "shared", "private"]).optional(),
|
|
@@ -10753,11 +10820,13 @@ Summary: ${newMems.length} new, ${updatedMems.length} updated, ${expiredMems.len
|
|
|
10753
10820
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10754
10821
|
}
|
|
10755
10822
|
});
|
|
10756
|
-
server.tool("memory_context", "Get memories relevant to current context
|
|
10823
|
+
server.tool("memory_context", "Get memories relevant to current context. Uses time-weighted scoring: score = importance \xD7 decay(age). Pinned memories are exempt. Returns effective_score on each memory.", {
|
|
10757
10824
|
agent_id: exports_external.string().optional(),
|
|
10758
10825
|
project_id: exports_external.string().optional(),
|
|
10759
10826
|
scope: exports_external.enum(["global", "shared", "private"]).optional(),
|
|
10760
|
-
limit: exports_external.coerce.number().optional()
|
|
10827
|
+
limit: exports_external.coerce.number().optional(),
|
|
10828
|
+
decay_halflife_days: exports_external.coerce.number().optional().describe("Importance half-life in days (default: 90). Lower = more weight on recent memories."),
|
|
10829
|
+
no_decay: exports_external.coerce.boolean().optional().describe("Set true to disable decay and sort purely by importance.")
|
|
10761
10830
|
}, async (args) => {
|
|
10762
10831
|
try {
|
|
10763
10832
|
const filter = {
|
|
@@ -10765,16 +10834,31 @@ server.tool("memory_context", "Get memories relevant to current context, filtere
|
|
|
10765
10834
|
agent_id: args.agent_id,
|
|
10766
10835
|
project_id: args.project_id,
|
|
10767
10836
|
status: "active",
|
|
10768
|
-
limit: args.limit || 30
|
|
10837
|
+
limit: (args.limit || 30) * 2
|
|
10769
10838
|
};
|
|
10770
10839
|
const memories = listMemories(filter);
|
|
10771
10840
|
if (memories.length === 0) {
|
|
10772
10841
|
return { content: [{ type: "text", text: "No memories in current context." }] };
|
|
10773
10842
|
}
|
|
10774
|
-
|
|
10843
|
+
const halflifeDays = args.decay_halflife_days ?? 90;
|
|
10844
|
+
const now2 = Date.now();
|
|
10845
|
+
const scored = memories.map((m) => {
|
|
10846
|
+
let effectiveScore = m.importance;
|
|
10847
|
+
if (!args.no_decay && !m.pinned) {
|
|
10848
|
+
const ageMs = now2 - new Date(m.updated_at).getTime();
|
|
10849
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
10850
|
+
const decayFactor = Math.pow(0.5, ageDays / halflifeDays);
|
|
10851
|
+
effectiveScore = m.importance * decayFactor;
|
|
10852
|
+
}
|
|
10853
|
+
return { ...m, effective_score: Math.round(effectiveScore * 100) / 100 };
|
|
10854
|
+
});
|
|
10855
|
+
const limit = args.limit || 30;
|
|
10856
|
+
scored.sort((a, b) => b.effective_score - a.effective_score);
|
|
10857
|
+
const top = scored.slice(0, limit);
|
|
10858
|
+
for (const m of top) {
|
|
10775
10859
|
touchMemory(m.id);
|
|
10776
10860
|
}
|
|
10777
|
-
const lines =
|
|
10861
|
+
const lines = top.map((m) => `[${m.scope}/${m.category}] ${m.key}: ${m.value} (score: ${m.effective_score}, raw: ${m.importance}${m.pinned ? ", pinned" : ""})`);
|
|
10778
10862
|
return { content: [{ type: "text", text: lines.join(`
|
|
10779
10863
|
`) }] };
|
|
10780
10864
|
} catch (e) {
|