th-memory-mcp 2.2.8 → 2.3.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.
@@ -7,7 +7,7 @@ export const forgetInput = {
7
7
  .positive()
8
8
  .describe("Row id to delete (id returned by remember/save_lesson)"),
9
9
  type: z
10
- .enum(["preference", "lesson", "interaction"])
10
+ .enum(["memory", "preference", "lesson", "interaction"])
11
11
  .optional()
12
12
  .describe("Which table the id belongs to. Recommended whenever known, because numeric ids can coincide across tables."),
13
13
  };
@@ -15,10 +15,14 @@ const indexTables = db.prepare("SELECT DISTINCT ref_table FROM search_index WHER
15
15
  const existsPreference = db.prepare("SELECT id FROM preferences WHERE id = ?");
16
16
  const existsLesson = db.prepare("SELECT id FROM lessons WHERE id = ?");
17
17
  const existsInteraction = db.prepare("SELECT id FROM interactions WHERE id = ?");
18
+ const existsMemory = db.prepare("SELECT id FROM memories WHERE id = ? AND status <> 'deleted'");
18
19
  const delPreference = db.prepare("DELETE FROM preferences WHERE id = ?");
19
20
  const delLesson = db.prepare("DELETE FROM lessons WHERE id = ?");
20
21
  const delInteraction = db.prepare("DELETE FROM interactions WHERE id = ?");
22
+ const delMemoryLinks = db.prepare("DELETE FROM memory_links WHERE source_memory_id = ? OR target_memory_id = ?");
21
23
  function existsIn(kind, id) {
24
+ if (kind === "memories")
25
+ return !!existsMemory.get(id);
22
26
  if (kind === "preferences") {
23
27
  return !!existsPreference.get(id);
24
28
  }
@@ -31,6 +35,7 @@ export async function forgetHandler(args) {
31
35
  try {
32
36
  const id = args.target_id;
33
37
  const kindOf = {
38
+ memory: "memories",
34
39
  preference: "preferences",
35
40
  lesson: "lessons",
36
41
  interaction: "interactions",
@@ -44,21 +49,43 @@ export async function forgetHandler(args) {
44
49
  const indexed = indexTables.all(id);
45
50
  const evidence = indexed
46
51
  .map((r) => r.ref_table)
47
- .filter((t) => t === "preferences" || t === "lessons" || t === "interactions");
52
+ .filter((t) => t === "memories" || t === "preferences" || t === "lessons" || t === "interactions");
48
53
  const candidates = evidence.length > 0
49
54
  ? evidence
50
- : ["preferences", "lessons", "interactions"];
55
+ : ["memories", "preferences", "lessons", "interactions"];
51
56
  targets = candidates.filter((k) => existsIn(k, id));
52
57
  }
53
58
  if (targets.length === 0) {
54
59
  return ok(`nothing found with id=${id}`);
55
60
  }
61
+ // Batch A-1: never silently delete across tables. Without an explicit
62
+ // type, a numeric id can coincide in several tables (each AUTOINCREMENTs
63
+ // from 1). If it matches >1 table, fail closed and ask for `type`.
64
+ if (!typedKind && targets.length > 1) {
65
+ return err(`ambiguous id=${id}: found in ${targets.join(", ")}. Specify type explicitly (one of memory, preference, lesson, interaction)`);
66
+ }
56
67
  const removed = [];
68
+ let linksRemoved = 0;
57
69
  db.transaction(() => {
58
70
  for (const kind of targets) {
59
71
  if (!existsIn(kind, id))
60
72
  continue;
61
- if (kind === "preferences") {
73
+ if (kind === "memories") {
74
+ db.prepare("UPDATE memories SET status = 'deleted' WHERE id = ?").run(id);
75
+ removeSearchIndex("memories", id);
76
+ removeEmbedding("memories", id);
77
+ // Batch A: cascade-delete memory_links referencing this id on BOTH sides
78
+ // in the same transaction so forget never leaves dangling graph edges.
79
+ // NOTE: entities/relations are intentionally NOT deleted here — they are
80
+ // shared graph nodes that may still be referenced by other memories;
81
+ // deleting them would corrupt the graph. Orphaned rows stay harmless
82
+ // and can be reclaimed by consolidate/GC later.
83
+ const info = delMemoryLinks.run(id, id);
84
+ const n = Number(info.changes ?? 0);
85
+ linksRemoved += n;
86
+ removed.push(`memory #${id} (${n} link(s) removed)`);
87
+ }
88
+ else if (kind === "preferences") {
62
89
  delPreference.run(id);
63
90
  removeSearchIndex("preferences", id);
64
91
  removeEmbedding("preferences", id);