@remnic/plugin-openclaw 1.0.35 → 1.0.37

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.
Files changed (87) hide show
  1. package/README.md +38 -4
  2. package/dist/{calibration-Z5WWNV7U.js → calibration-RKL2LRW4.js} +4 -4
  3. package/dist/{capsule-cli-GBM3WPAM.js → capsule-cli-EHZPMXBC.js} +2 -2
  4. package/dist/{capsule-crypto-K3IRTKRH.js → capsule-crypto-JS67OSWM.js} +3 -3
  5. package/dist/capsule-export-DX53CPIT.js +17 -0
  6. package/dist/capsule-import-4OXCPHOT.js +16 -0
  7. package/dist/{capsule-merge-IWOQ34KL.js → capsule-merge-25AUN33Q.js} +7 -7
  8. package/dist/{causal-chain-WYN5QOPS.js → causal-chain-BVTOWZKC.js} +4 -4
  9. package/dist/{causal-consolidation-C64NNE4T.js → causal-consolidation-DRPM2KOE.js} +13 -10
  10. package/dist/{causal-retrieval-NZHQOZOE.js → causal-retrieval-XAP6QKHZ.js} +4 -5
  11. package/dist/{causal-trajectory-graph-VBPE2WPM.js → causal-trajectory-graph-ZWQWZ7N5.js} +2 -2
  12. package/dist/{chunk-5LE4HTVL.js → chunk-25J4PXDH.js} +0 -18
  13. package/dist/{chunk-6UFI73TJ.js → chunk-3IKMUNW5.js} +53 -46
  14. package/dist/{chunk-EXDYWXMB.js → chunk-4XDQ3KEC.js} +1 -2
  15. package/dist/{chunk-JGIUTWZS.js → chunk-6O3H3DPL.js} +2 -2
  16. package/dist/{chunk-UTDLHBBV.js → chunk-BLC3RQNV.js} +5 -555
  17. package/dist/{chunk-4G2XCSD2.js → chunk-BZ4EYURA.js} +0 -5
  18. package/dist/{chunk-L6I4MQKO.js → chunk-CEL5ZLKP.js} +6 -6
  19. package/dist/{chunk-TDRJVMUP.js → chunk-EH4AXGRO.js} +0 -12
  20. package/dist/{chunk-EYCLXMIV.js → chunk-G3CZA4SD.js} +9 -427
  21. package/dist/chunk-I2KLQ2HA.js +22 -0
  22. package/dist/chunk-IO5WWY6A.js +156 -0
  23. package/dist/{contradiction-scan-A5NOTZPN.js → chunk-JC3FCKYL.js} +189 -86
  24. package/dist/{chunk-SVSQAG6M.js → chunk-KC7KSQR4.js} +47 -28
  25. package/dist/chunk-LZCGPRHS.js +228 -0
  26. package/dist/{chunk-CXM7EBAO.js → chunk-MXFJXUHC.js} +1 -1
  27. package/dist/{chunk-VRGUUHBV.js → chunk-NUWDSTP7.js} +1 -1
  28. package/dist/{chunk-4LYQ4ONL.js → chunk-QCCP4RU5.js} +8 -3
  29. package/dist/{chunk-6OJAU466.js → chunk-QMUQV5NP.js} +0 -1
  30. package/dist/{chunk-LLUROTZJ.js → chunk-QQXJODFL.js} +9 -9
  31. package/dist/{chunk-6F6EKSVP.js → chunk-QXXEF7VI.js} +1 -1
  32. package/dist/{chunk-CMKR6NDQ.js → chunk-SEGEX7W4.js} +73 -241
  33. package/dist/{chunk-VFULKFKI.js → chunk-SWOYEQN2.js} +42 -17
  34. package/dist/chunk-TH5FF5SC.js +16 -0
  35. package/dist/{chunk-FGTYFLL5.js → chunk-TXOEHSVP.js} +29 -32
  36. package/dist/chunk-UZJ7EERS.js +272 -0
  37. package/dist/chunk-YJYZMLD5.js +360 -0
  38. package/dist/{chunk-NKVIN6RD.js → chunk-YKV4EFUI.js} +84 -2
  39. package/dist/{chunk-SSFTU6LP.js → chunk-ZS6VABML.js} +4 -4
  40. package/dist/{cipher-VHAFCG7Z.js → cipher-E23BHBSO.js} +1 -1
  41. package/dist/{consolidation-undo-5ZSX4MWO.js → consolidation-undo-FKJZCJHS.js} +2 -2
  42. package/dist/contradiction-review-WJRWNQ5N.js +29 -0
  43. package/dist/contradiction-scan-5X423QGT.js +12 -0
  44. package/dist/{dreams-ledger-3I52ISYR.js → dreams-ledger-KDX44I7R.js} +1 -1
  45. package/dist/{engine-47AKKYJ4.js → engine-5P774HTZ.js} +6 -6
  46. package/dist/{extraction-judge-telemetry-GHOTVYMP.js → extraction-judge-telemetry-O4ZVGLTU.js} +1 -1
  47. package/dist/{fallback-llm-45A755XP.js → fallback-llm-43UMEXNJ.js} +3 -3
  48. package/dist/{first-start-migration-I24M2JEE.js → first-start-migration-H2SAXAGR.js} +4 -4
  49. package/dist/{forget-NI4RBDPB.js → forget-ZECIDNL5.js} +1 -1
  50. package/dist/{fs-utils-PZRI2HDZ.js → fs-utils-OYXSZSVV.js} +12 -2
  51. package/dist/{graph-edge-decay-5CVKWBYH.js → graph-edge-decay-24ZKD5QL.js} +5 -5
  52. package/dist/index.js +7098 -84293
  53. package/dist/{kdf-H5B23ZM2.js → kdf-RXKIWHRU.js} +1 -1
  54. package/dist/legacy-hook-compat-QHHKF4GK.js +2 -0
  55. package/dist/{logger-TNOKCH7X.js → logger-XG7JKLPS.js} +1 -1
  56. package/dist/{memory-governance-QS7Z425Y.js → memory-governance-6K4M4YXD.js} +5 -5
  57. package/dist/{metadata-JAGIWHEA.js → metadata-WK2TRPYZ.js} +1 -1
  58. package/dist/{migrate-from-identity-anchor-7MMSPEUM.js → migrate-from-identity-anchor-SNDNKHZD.js} +1 -1
  59. package/dist/path-ZKO74XXC.js +7 -0
  60. package/dist/{peers-KRFXWRQ6.js → peers-W53WSDXG.js} +1 -1
  61. package/dist/{purge-XN2VSPZ2.js → purge-IKJISXEQ.js} +1 -1
  62. package/dist/resolution-BN35OXDS.js +11 -0
  63. package/dist/{secure-store-A4NGCNXV.js → secure-store-F75I54O5.js} +3 -3
  64. package/dist/{state-PVISYXRH.js → state-4ITLYMAU.js} +1 -1
  65. package/dist/{state-store-N6TFBFSP.js → state-store-ET3ADVY5.js} +3 -3
  66. package/dist/{storage-DDYQGLXA.js → storage-5EY6T7ON.js} +3 -3
  67. package/dist/{tier-stats-IZNW66NC.js → tier-stats-ZRQBV6G2.js} +4 -4
  68. package/dist/{trace-NJESSGH7.js → trace-IL2Y34EH.js} +1 -1
  69. package/dist/{tui-MGK2LYJY.js → tui-7KRDCMYK.js} +1 -1
  70. package/dist/{types-R4DO7AKM.js → types-MBUINTB2.js} +3 -3
  71. package/openclaw.plugin.json +164 -8
  72. package/package.json +9 -6
  73. package/scripts/faiss_index.py +816 -0
  74. package/scripts/faiss_requirements.txt +3 -0
  75. package/dist/capsule-export-IXVERCQG.js +0 -17
  76. package/dist/capsule-import-IA6VIOPQ.js +0 -16
  77. package/dist/chunk-3GUF7RQI.js +0 -559
  78. package/dist/chunk-7OQEPGQF.js +0 -533
  79. package/dist/chunk-DIZW6H5J.js +0 -136
  80. package/dist/chunk-FQRSVYY4.js +0 -110
  81. package/dist/chunk-GUSMRW4H.js +0 -12
  82. package/dist/chunk-MLKGABMK.js +0 -9
  83. package/dist/chunk-WPINX4MF.js +0 -380
  84. package/dist/contradiction-review-SVGBS3V5.js +0 -21
  85. package/dist/legacy-hook-compat-XQ7FP6FV.js +0 -35
  86. package/dist/path-JIEGNWFL.js +0 -7
  87. package/dist/resolution-YITUVUTH.js +0 -100
@@ -1,18 +1,18 @@
1
- import {
2
- CAPSULE_ID_PATTERN,
3
- CapsuleBlockSchema,
4
- ExportBundleV2Schema,
5
- ExportManifestV2Schema
6
- } from "./chunk-4LYQ4ONL.js";
7
1
  import {
8
2
  listFilesRecursive,
9
3
  sha256File,
10
4
  toPosixRelPath,
11
5
  writeJsonFile
12
- } from "./chunk-NKVIN6RD.js";
6
+ } from "./chunk-YKV4EFUI.js";
7
+ import {
8
+ CAPSULE_ID_PATTERN,
9
+ CapsuleBlockSchema,
10
+ ExportBundleV2Schema,
11
+ ExportManifestV2Schema
12
+ } from "./chunk-QCCP4RU5.js";
13
13
  import {
14
14
  encryptCapsuleFile
15
- } from "./chunk-SSFTU6LP.js";
15
+ } from "./chunk-ZS6VABML.js";
16
16
 
17
17
  // ../remnic-core/src/transfer/capsule-export.ts
18
18
  import * as fsReadModule0 from "fs/promises";
@@ -20,27 +20,33 @@ const mkdir = fsReadModule0.mkdir;
20
20
  const fileReader = fsReadModule0["re"+"ad"+"Fi"+"le"];
21
21
  const stat = fsReadModule0.stat;
22
22
  const writeFile = fsReadModule0.writeFile;
23
- import path from "path";
23
+ import path2 from "path";
24
24
  import { gzipSync } from "zlib";
25
25
 
26
26
  // ../remnic-core/src/transfer/constants.ts
27
27
  var EXPORT_FORMAT = "openclaw-engram-export";
28
- var EXPORT_SCHEMA_VERSION = 1;
29
28
  var CAPSULE_SCHEMA_VERSION = 2;
30
29
 
31
- // ../remnic-core/src/transfer/capsule-export.ts
32
- var DEFAULT_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
30
+ // ../remnic-core/src/transfer/exclusions.ts
31
+ import path from "path";
32
+ var DEFAULT_TRANSFER_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
33
33
  "node_modules",
34
34
  ".git",
35
- // Never export the secure-store directory: it contains the encryption
36
- // header (KDF params + verifier) which is security-sensitive and
37
- // machine-specific. The passphrase is not stored here, but including
38
- // the header in a capsule would let an attacker brute-force the
39
- // passphrase offline if the capsule is intercepted.
40
35
  ".secure-store",
41
- // Exclude .capsules to avoid recursive self-inclusion.
42
36
  ".capsules"
43
37
  ]);
38
+ function computeTransferOutputRel(rootAbs, outputAbs) {
39
+ const rel = path.relative(rootAbs, outputAbs);
40
+ if (rel === "") {
41
+ throw new Error("transfer export output path must not equal the memory directory");
42
+ }
43
+ if (rel === ".." || rel.startsWith(`..${path.sep}`)) return null;
44
+ if (path.isAbsolute(rel)) return null;
45
+ return rel.split(path.sep).join("/");
46
+ }
47
+
48
+ // ../remnic-core/src/transfer/capsule-export.ts
49
+ var DEFAULT_EXCLUDE_DIRS = DEFAULT_TRANSFER_EXCLUDE_DIRS;
44
50
  var TRANSCRIPTS_DIR = "transcripts";
45
51
  var PEERS_DIR = "peers";
46
52
  async function exportCapsule(opts) {
@@ -49,16 +55,16 @@ async function exportCapsule(opts) {
49
55
  const includeKinds = normalizeIncludeKinds(opts.includeKinds);
50
56
  const peerFilter = normalizePeerIds(opts.peerIds);
51
57
  const transcriptsOverride = opts.includeTranscripts === true;
52
- const rootAbs = path.resolve(opts.root);
58
+ const rootAbs = path2.resolve(opts.root);
53
59
  await assertIsDirectory(rootAbs);
54
- const outDirAbs = path.resolve(opts.outDir ?? path.join(rootAbs, ".capsules"));
60
+ const outDirAbs = path2.resolve(opts.outDir ?? path2.join(rootAbs, ".capsules"));
55
61
  if (outDirAbs === rootAbs) {
56
62
  throw new Error(
57
63
  "exportCapsule: 'outDir' must not equal 'root'. Choose a separate directory (default: <root>/.capsules) so the export does not overwrite or shadow the source tree."
58
64
  );
59
65
  }
60
66
  await mkdir(outDirAbs, { recursive: true });
61
- const outDirRelPosix = computeOutDirRel(rootAbs, outDirAbs);
67
+ const outDirRelPosix = computeTransferOutputRel(rootAbs, outDirAbs);
62
68
  const filesAbs = await listFilesRecursive(rootAbs);
63
69
  const records = [];
64
70
  const manifestFiles = [];
@@ -92,8 +98,8 @@ async function exportCapsule(opts) {
92
98
  manifest,
93
99
  records
94
100
  });
95
- const archivePath = path.join(outDirAbs, `${opts.name}.capsule.json.gz`);
96
- const manifestPath = path.join(outDirAbs, `${opts.name}.manifest.json`);
101
+ const archivePath = path2.join(outDirAbs, `${opts.name}.capsule.json.gz`);
102
+ const manifestPath = path2.join(outDirAbs, `${opts.name}.manifest.json`);
97
103
  await writeJsonFile(manifestPath, manifest);
98
104
  const json = JSON.stringify(bundle);
99
105
  const gz = gzipSync(Buffer.from(json, "utf-8"));
@@ -209,13 +215,6 @@ function normalizePeerIds(peerIds) {
209
215
  }
210
216
  return set;
211
217
  }
212
- function computeOutDirRel(rootAbs, outDirAbs) {
213
- const rel = path.relative(rootAbs, outDirAbs);
214
- if (rel === "") return null;
215
- if (rel === ".." || rel.startsWith(`..${path.sep}`)) return null;
216
- if (path.isAbsolute(rel)) return null;
217
- return rel.split(path.sep).join("/");
218
- }
219
218
  async function assertIsDirectory(absPath) {
220
219
  const st = await stat(absPath).catch(() => null);
221
220
  if (!st || !st.isDirectory()) {
@@ -271,8 +270,6 @@ function buildCapsuleBlock(name, override) {
271
270
  }
272
271
 
273
272
  export {
274
- EXPORT_FORMAT,
275
- EXPORT_SCHEMA_VERSION,
276
273
  exportCapsule,
277
274
  isValidCapsuleSince
278
275
  };
@@ -0,0 +1,272 @@
1
+ import {
2
+ readPair,
3
+ resolvePair
4
+ } from "./chunk-YJYZMLD5.js";
5
+ import {
6
+ log
7
+ } from "./chunk-UFU5GGGA.js";
8
+
9
+ // ../remnic-core/src/contradiction/resolution.ts
10
+ var VALID_VERBS = ["keep-a", "keep-b", "merge", "both-valid", "needs-more-context"];
11
+ function isValidResolutionVerb(value) {
12
+ return VALID_VERBS.includes(value);
13
+ }
14
+ async function executeResolution(memoryDir, storage, pairId, verb, options = {}) {
15
+ if (typeof verb !== "string" || !isValidResolutionVerb(verb)) {
16
+ throw new Error(`Invalid contradiction resolution verb: ${String(verb)}`);
17
+ }
18
+ const pair = readPair(memoryDir, pairId);
19
+ if (!pair) {
20
+ return { pairId, verb, affectedIds: [], message: `Pair ${pairId} not found` };
21
+ }
22
+ if (pair.namespace && !options.storageForNamespace) {
23
+ throw new Error(
24
+ "contradiction resolution requires storageForNamespace for namespaced pairs so callers resolve the correct namespace storage"
25
+ );
26
+ }
27
+ const resolutionStorage = options.storageForNamespace ? await options.storageForNamespace(pair.namespace) : storage;
28
+ if (pair.resolution && pair.resolution !== "needs-more-context") {
29
+ return { pairId, verb, affectedIds: [], message: `Pair already resolved with verb "${pair.resolution}"` };
30
+ }
31
+ const [idA, idB] = pair.memoryIds;
32
+ const affectedIds = [];
33
+ let message = "";
34
+ let supersedeFailed = false;
35
+ switch (verb) {
36
+ case "keep-a": {
37
+ const keepTarget = await validateKeepTarget(resolutionStorage, pairId, idA);
38
+ if (!keepTarget.ok) {
39
+ supersedeFailed = true;
40
+ message = keepTarget.message;
41
+ break;
42
+ }
43
+ const sourceB = await loadSourceSnapshot(resolutionStorage, idB);
44
+ const ok = sourceB ? await supersedeSafe(resolutionStorage, idB, idA, "contradiction-resolution:keep-a") : false;
45
+ if (ok) {
46
+ affectedIds.push(idB);
47
+ message = `Kept ${idA}, superseded ${idB}`;
48
+ } else {
49
+ supersedeFailed = true;
50
+ const rolledBack = sourceB ? await restoreMemorySnapshot(resolutionStorage, sourceB, "contradiction-resolution:keep-a-rollback") : false;
51
+ message = rolledBack ? `Supersede failed for ${idB}; restored ${idB} and did not resolve` : `Supersede failed for ${idB}; rollback incomplete for ${idB} and pair is not resolved`;
52
+ }
53
+ break;
54
+ }
55
+ case "keep-b": {
56
+ const keepTarget = await validateKeepTarget(resolutionStorage, pairId, idB);
57
+ if (!keepTarget.ok) {
58
+ supersedeFailed = true;
59
+ message = keepTarget.message;
60
+ break;
61
+ }
62
+ const sourceA = await loadSourceSnapshot(resolutionStorage, idA);
63
+ const ok = sourceA ? await supersedeSafe(resolutionStorage, idA, idB, "contradiction-resolution:keep-b") : false;
64
+ if (ok) {
65
+ affectedIds.push(idA);
66
+ message = `Kept ${idB}, superseded ${idA}`;
67
+ } else {
68
+ supersedeFailed = true;
69
+ const rolledBack = sourceA ? await restoreMemorySnapshot(resolutionStorage, sourceA, "contradiction-resolution:keep-b-rollback") : false;
70
+ message = rolledBack ? `Supersede failed for ${idA}; restored ${idA} and did not resolve` : `Supersede failed for ${idA}; rollback incomplete for ${idA} and pair is not resolved`;
71
+ }
72
+ break;
73
+ }
74
+ case "merge": {
75
+ const replacement = await prepareMergeReplacement(resolutionStorage, pairId, idA, idB, options);
76
+ if (!replacement.ok) {
77
+ supersedeFailed = true;
78
+ message = replacement.message;
79
+ break;
80
+ }
81
+ const okA = await supersedeSafe(resolutionStorage, idA, replacement.mergedId, "contradiction-resolution:merge");
82
+ if (!okA) {
83
+ supersedeFailed = true;
84
+ const rolledBackA = await restoreMemorySnapshot(resolutionStorage, replacement.sourceA);
85
+ message = rolledBackA ? `Merge failed for ${idA}; restored ${idA} and did not resolve` : `Merge failed for ${idA}; rollback incomplete for ${idA} and pair is not resolved`;
86
+ if (rolledBackA) {
87
+ await cleanupCreatedReplacement(resolutionStorage, replacement);
88
+ }
89
+ break;
90
+ }
91
+ const okB = await supersedeSafe(resolutionStorage, idB, replacement.mergedId, "contradiction-resolution:merge");
92
+ if (!okB) {
93
+ supersedeFailed = true;
94
+ const rolledBackA = await restoreMemorySnapshot(resolutionStorage, replacement.sourceA);
95
+ const rolledBackB = await restoreMemorySnapshot(resolutionStorage, replacement.sourceB);
96
+ message = rolledBackA && rolledBackB ? `Merge failed for ${idB}; restored ${idA} and ${idB} and did not resolve` : `Merge failed for ${idB}; rollback incomplete for ${[
97
+ rolledBackA ? void 0 : idA,
98
+ rolledBackB ? void 0 : idB
99
+ ].filter(Boolean).join(", ")} and pair is not resolved`;
100
+ if (rolledBackA && rolledBackB) {
101
+ await cleanupCreatedReplacement(resolutionStorage, replacement);
102
+ }
103
+ break;
104
+ }
105
+ affectedIds.push(idA, idB);
106
+ message = `Both memories superseded by merged ${replacement.mergedId}`;
107
+ break;
108
+ }
109
+ case "both-valid": {
110
+ message = "Pair marked as both-valid; cooldown applied";
111
+ break;
112
+ }
113
+ case "needs-more-context": {
114
+ message = "Deferred; no action taken, short cooldown applied";
115
+ break;
116
+ }
117
+ }
118
+ if (!supersedeFailed) {
119
+ resolvePair(memoryDir, pairId, verb);
120
+ }
121
+ log.info("[contradiction-resolution] pair=%s verb=%s affected=%d", pairId, verb, affectedIds.length);
122
+ return { pairId, verb, affectedIds, message };
123
+ }
124
+ async function prepareMergeReplacement(storage, pairId, idA, idB, options) {
125
+ const sourceA = await storage.getMemoryById(idA);
126
+ const sourceB = await storage.getMemoryById(idB);
127
+ if (!sourceA || !sourceB) {
128
+ return { ok: false, message: `Merge requires both source memories to exist; not resolving ${pairId}` };
129
+ }
130
+ const requestedMergedId = options.mergedMemoryId?.trim();
131
+ if (requestedMergedId) {
132
+ if (requestedMergedId === idA || requestedMergedId === idB) {
133
+ return { ok: false, message: "Merge replacement must be distinct from both source memories; not resolving" };
134
+ }
135
+ const replacement2 = await storage.getMemoryById(requestedMergedId);
136
+ if (!replacement2) {
137
+ return { ok: false, message: `Merged memory ${requestedMergedId} not found; not resolving` };
138
+ }
139
+ const replacementStatus = replacement2.frontmatter.status ?? "active";
140
+ if (replacementStatus !== "active") {
141
+ return {
142
+ ok: false,
143
+ message: `Merged memory ${requestedMergedId} is ${replacementStatus}; not resolving`
144
+ };
145
+ }
146
+ return { ok: true, mergedId: requestedMergedId, sourceA, sourceB, created: false };
147
+ }
148
+ const mergedContent = options.mergedContent;
149
+ if (typeof mergedContent !== "string" || mergedContent.trim().length === 0) {
150
+ return {
151
+ ok: false,
152
+ message: "Merge requires mergedMemoryId or mergedContent; no memories changed"
153
+ };
154
+ }
155
+ const category = options.mergedCategory ?? mergedMemoryCategory(sourceA, sourceB);
156
+ let mergedId;
157
+ try {
158
+ mergedId = await storage.writeMemory(category, mergedContent, {
159
+ actor: "contradiction-resolution",
160
+ confidence: Math.min(sourceA.frontmatter.confidence ?? 0.8, sourceB.frontmatter.confidence ?? 0.8),
161
+ tags: ["contradiction-resolution", "merge"],
162
+ source: "contradiction-resolution",
163
+ lineage: [idA, idB],
164
+ derivedFrom: [idA, idB],
165
+ derivedVia: "merge"
166
+ });
167
+ } catch (err) {
168
+ log.warn(
169
+ "[contradiction-resolution] merged memory creation failed for %s: %s",
170
+ pairId,
171
+ err instanceof Error ? err.message : err
172
+ );
173
+ return { ok: false, message: `Merged memory could not be created; not resolving ${pairId}` };
174
+ }
175
+ const replacement = await storage.getMemoryById(mergedId);
176
+ if (!replacement) {
177
+ await cleanupMemoryId(storage, mergedId);
178
+ return { ok: false, message: `Merged memory ${mergedId} could not be verified; not resolving` };
179
+ }
180
+ return { ok: true, mergedId, sourceA, sourceB, created: true };
181
+ }
182
+ function mergedMemoryCategory(sourceA, sourceB) {
183
+ return sourceA.frontmatter.category === sourceB.frontmatter.category ? sourceA.frontmatter.category : "fact";
184
+ }
185
+ async function validateKeepTarget(storage, pairId, keepId) {
186
+ const target = await loadSourceSnapshot(storage, keepId);
187
+ if (!target) {
188
+ return { ok: false, message: `Kept memory ${keepId} not found; not resolving ${pairId}` };
189
+ }
190
+ const status = target.frontmatter.status ?? "active";
191
+ if (status !== "active") {
192
+ return { ok: false, message: `Kept memory ${keepId} is ${status}; not resolving ${pairId}` };
193
+ }
194
+ return { ok: true };
195
+ }
196
+ async function loadSourceSnapshot(storage, memoryId) {
197
+ try {
198
+ return await storage.getMemoryById(memoryId);
199
+ } catch (err) {
200
+ log.warn(
201
+ "[contradiction-resolution] source snapshot failed for %s: %s",
202
+ memoryId,
203
+ err instanceof Error ? err.message : err
204
+ );
205
+ return null;
206
+ }
207
+ }
208
+ async function restoreMemorySnapshot(storage, memory, reasonCode = "contradiction-resolution:merge-rollback") {
209
+ try {
210
+ const current = await storage.getMemoryById(memory.frontmatter.id);
211
+ if (!current) return false;
212
+ const restoredFrontmatter = {
213
+ ...memory.frontmatter,
214
+ status: memory.frontmatter.status,
215
+ supersededBy: memory.frontmatter.supersededBy,
216
+ supersededAt: memory.frontmatter.supersededAt
217
+ };
218
+ return await storage.writeMemoryFrontmatter(current, restoredFrontmatter, {
219
+ actor: "contradiction-resolution",
220
+ reasonCode
221
+ });
222
+ } catch (err) {
223
+ log.warn(
224
+ "[contradiction-resolution] rollback failed for %s: %s",
225
+ memory.frontmatter.id,
226
+ err instanceof Error ? err.message : err
227
+ );
228
+ return false;
229
+ }
230
+ }
231
+ async function cleanupCreatedReplacement(storage, replacement) {
232
+ if (!replacement.created) return;
233
+ await cleanupMemoryId(storage, replacement.mergedId);
234
+ }
235
+ async function cleanupMemoryId(storage, memoryId) {
236
+ try {
237
+ const memory = await storage.getMemoryById(memoryId);
238
+ const invalidated = await storage.invalidateMemory(memoryId);
239
+ if (invalidated && memory?.frontmatter.category === "fact") {
240
+ await storage.removeFactContentHashesForMemories([memory]);
241
+ }
242
+ } catch (err) {
243
+ log.warn(
244
+ "[contradiction-resolution] cleanup failed for merged memory %s: %s",
245
+ memoryId,
246
+ err instanceof Error ? err.message : err
247
+ );
248
+ }
249
+ }
250
+ async function supersedeSafe(storage, oldId, newId, reason) {
251
+ try {
252
+ const result = await storage.supersedeMemory(oldId, newId, reason);
253
+ if (result === false) {
254
+ log.warn("[contradiction-resolution] supersede returned false for %s \u2192 %s", oldId, newId);
255
+ return false;
256
+ }
257
+ return true;
258
+ } catch (err) {
259
+ log.warn(
260
+ "[contradiction-resolution] supersede failed %s \u2192 %s: %s",
261
+ oldId,
262
+ newId,
263
+ err instanceof Error ? err.message : err
264
+ );
265
+ return false;
266
+ }
267
+ }
268
+
269
+ export {
270
+ isValidResolutionVerb,
271
+ executeResolution
272
+ };