@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
@@ -0,0 +1,156 @@
1
+ // ../remnic-core/src/graph-edge-reinforcement.ts
2
+ var CONFIDENCE_CEILING = 1;
3
+ var DEFAULT_DECAY_WINDOW_MS = 90 * 24 * 60 * 60 * 1e3;
4
+ var DEFAULT_DECAY_FLOOR = 0.1;
5
+ var DEFAULT_DECAY_PER_WINDOW = 0.1;
6
+ function readEdgeConfidence(edge) {
7
+ const raw = edge.confidence;
8
+ if (raw === void 0 || raw === null || !Number.isFinite(raw)) {
9
+ return CONFIDENCE_CEILING;
10
+ }
11
+ if (raw < 0) return 0;
12
+ if (raw > CONFIDENCE_CEILING) return CONFIDENCE_CEILING;
13
+ return raw;
14
+ }
15
+ function readLastReinforcedAt(edge) {
16
+ return edge.lastReinforcedAt ?? edge.ts;
17
+ }
18
+ function decayEdgeConfidence(edge, now, opts = {}) {
19
+ const windowMs = opts.windowMs ?? DEFAULT_DECAY_WINDOW_MS;
20
+ const perWindow = opts.perWindow ?? DEFAULT_DECAY_PER_WINDOW;
21
+ const floor = opts.floor ?? DEFAULT_DECAY_FLOOR;
22
+ if (!(windowMs > 0) || !Number.isFinite(perWindow) || perWindow < 0 || !Number.isFinite(floor)) {
23
+ return { ...edge, confidence: readEdgeConfidence(edge) };
24
+ }
25
+ const safeFloor = Math.min(CONFIDENCE_CEILING, Math.max(0, floor));
26
+ const nowMs = Date.parse(now);
27
+ const refMs = Date.parse(readLastReinforcedAt(edge));
28
+ if (!Number.isFinite(nowMs) || !Number.isFinite(refMs)) {
29
+ return { ...edge, confidence: readEdgeConfidence(edge) };
30
+ }
31
+ const age = nowMs - refMs;
32
+ const current = readEdgeConfidence(edge);
33
+ if (age <= windowMs) {
34
+ return { ...edge, confidence: current };
35
+ }
36
+ const windowsPast = Math.ceil((age - windowMs) / windowMs);
37
+ const decayed = current - perWindow * windowsPast;
38
+ const lowerBound = Math.min(safeFloor, current);
39
+ const next = Math.max(lowerBound, Math.min(current, decayed));
40
+ const newRefMs = refMs + windowsPast * windowMs;
41
+ const newRef = new Date(newRefMs).toISOString();
42
+ return { ...edge, confidence: next, lastReinforcedAt: newRef };
43
+ }
44
+
45
+ // ../remnic-core/src/graph.ts
46
+ import * as fsReadModule0 from "fs/promises";
47
+ const mkdir = fsReadModule0.mkdir;
48
+ const appendFile = fsReadModule0.appendFile;
49
+ const fileReader = fsReadModule0["re"+"ad"+"Fi"+"le"];
50
+ import * as path from "path";
51
+
52
+ // ../remnic-core/src/graph-events.ts
53
+ import { EventEmitter } from "events";
54
+ var buses = /* @__PURE__ */ new Map();
55
+ function getGraphEventBus(memoryDir) {
56
+ let bus = buses.get(memoryDir);
57
+ if (!bus) {
58
+ bus = new EventEmitter();
59
+ bus.setMaxListeners(200);
60
+ buses.set(memoryDir, bus);
61
+ }
62
+ return bus;
63
+ }
64
+ function emitGraphEvent(memoryDir, type, payload) {
65
+ const event = {
66
+ type,
67
+ memoryDir,
68
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
69
+ payload
70
+ };
71
+ const bus = getGraphEventBus(memoryDir);
72
+ try {
73
+ bus.emit("graph-event", event);
74
+ } catch {
75
+ }
76
+ }
77
+
78
+ // ../remnic-core/src/graph.ts
79
+ function graphsDir(memoryDir) {
80
+ return path.join(memoryDir, "state", "graphs");
81
+ }
82
+ function graphFilePath(memoryDir, type) {
83
+ return path.join(graphsDir(memoryDir), `${type}.jsonl`);
84
+ }
85
+ async function ensureGraphsDir(memoryDir) {
86
+ await mkdir(graphsDir(memoryDir), { recursive: true });
87
+ }
88
+ var graphWriteLocks = /* @__PURE__ */ new Map();
89
+ function withGraphWriteLock(filePath, fn) {
90
+ const prev = graphWriteLocks.get(filePath) ?? Promise.resolve();
91
+ const next = prev.then(fn, fn);
92
+ graphWriteLocks.set(
93
+ filePath,
94
+ next.then(
95
+ () => void 0,
96
+ () => void 0
97
+ )
98
+ );
99
+ return next;
100
+ }
101
+ async function appendEdge(memoryDir, edge) {
102
+ await ensureGraphsDir(memoryDir);
103
+ const filePath = graphFilePath(memoryDir, edge.type);
104
+ const line = JSON.stringify(edge) + "\n";
105
+ await withGraphWriteLock(filePath, async () => {
106
+ await appendFile(filePath, line, "utf8");
107
+ });
108
+ emitGraphEvent(memoryDir, "edge-added", {
109
+ source: edge.from,
110
+ target: edge.to,
111
+ kind: edge.type,
112
+ weight: edge.weight,
113
+ label: edge.label,
114
+ confidence: typeof edge.confidence === "number" ? edge.confidence : 1
115
+ });
116
+ }
117
+ function isNodeError(err) {
118
+ return typeof err === "object" && err !== null && "code" in err;
119
+ }
120
+ function parseEdgesJsonl(raw) {
121
+ const edges = [];
122
+ for (const line of raw.split("\n")) {
123
+ const trimmed = line.trim();
124
+ if (!trimmed) continue;
125
+ try {
126
+ edges.push(JSON.parse(trimmed));
127
+ } catch {
128
+ }
129
+ }
130
+ return edges;
131
+ }
132
+ async function readEdgesStrict(memoryDir, type) {
133
+ const filePath = graphFilePath(memoryDir, type);
134
+ try {
135
+ const raw = await fileReader(filePath, "utf8");
136
+ return parseEdgesJsonl(raw);
137
+ } catch (err) {
138
+ if (isNodeError(err) && err.code === "ENOENT") {
139
+ return [];
140
+ }
141
+ throw err;
142
+ }
143
+ }
144
+
145
+ export {
146
+ DEFAULT_DECAY_WINDOW_MS,
147
+ DEFAULT_DECAY_FLOOR,
148
+ DEFAULT_DECAY_PER_WINDOW,
149
+ readEdgeConfidence,
150
+ decayEdgeConfidence,
151
+ graphsDir,
152
+ graphFilePath,
153
+ withGraphWriteLock,
154
+ appendEdge,
155
+ readEdgesStrict
156
+ };
@@ -1,16 +1,18 @@
1
1
  import {
2
+ computeMemoryContentHash,
2
3
  computePairId,
3
4
  isCoolingDown,
4
5
  listPairs,
6
+ memoryHashesChanged,
7
+ migrateUnscopedPairsToNamespace,
5
8
  writePairs
6
- } from "./chunk-DIZW6H5J.js";
9
+ } from "./chunk-YJYZMLD5.js";
7
10
  import {
8
11
  extractJsonCandidates
9
12
  } from "./chunk-3A5ELHTT.js";
10
13
  import {
11
14
  log
12
15
  } from "./chunk-UFU5GGGA.js";
13
- import "./chunk-MLKGABMK.js";
14
16
 
15
17
  // ../remnic-core/src/contradiction/contradiction-judge.ts
16
18
  import { createHash } from "crypto";
@@ -157,15 +159,14 @@ function parseJudgeResponse(candidates, inputs) {
157
159
  for (const item of items) {
158
160
  if (!item || typeof item !== "object") continue;
159
161
  const verdict = typeof item.verdict === "string" && VALID_VERDICTS.includes(item.verdict) ? item.verdict : "needs-user";
160
- const pairKeyVal = typeof item.pairKey === "string" ? item.pairKey : null;
162
+ const pairKeyVal = typeof item.pairKey === "string" && item.pairKey.length > 0 ? item.pairKey : null;
161
163
  const input = pairKeyVal ? inputs.find((p) => pairKey(p.memoryIdA, p.memoryIdB) === pairKeyVal) : null;
162
- const fallbackInput = input ?? inputs[results.length] ?? inputs[0];
163
- if (!fallbackInput) continue;
164
- matchedKeys.add(pairKey(fallbackInput.memoryIdA, fallbackInput.memoryIdB));
164
+ if (!input) continue;
165
+ matchedKeys.add(pairKey(input.memoryIdA, input.memoryIdB));
165
166
  const confidence = typeof item.confidence === "number" ? Math.min(1, Math.max(0, item.confidence)) : 0.5;
166
167
  results.push({
167
- memoryIdA: fallbackInput.memoryIdA,
168
- memoryIdB: fallbackInput.memoryIdB,
168
+ memoryIdA: input.memoryIdA,
169
+ memoryIdB: input.memoryIdB,
169
170
  verdict,
170
171
  rationale: typeof item.rationale === "string" ? item.rationale : "No rationale provided",
171
172
  confidence
@@ -209,14 +210,22 @@ var SCAN_CATEGORIES = /* @__PURE__ */ new Set([
209
210
  ]);
210
211
  async function runContradictionScan(deps) {
211
212
  const startTime = Date.now();
212
- const { storage, config, memoryDir, embeddingLookup, embeddingLookupFactory, localLlm, fallbackLlm, namespace } = deps;
213
+ const { config, memoryDir, embeddingLookup, embeddingLookupFactory, localLlm, fallbackLlm } = deps;
214
+ const requestedNamespace = normalizeScanNamespace(deps.namespace);
213
215
  const scanConfig = config.contradictionScan;
214
- const scopedEmbeddingLookup = embeddingLookupFactory ? embeddingLookupFactory(storage) : embeddingLookup;
215
216
  if (!scanConfig.enabled) {
216
217
  log.info("[contradiction-scan] disabled by config");
217
218
  return { scanned: 0, candidates: 0, judged: 0, queued: 0, cooledDown: 0, elapsedMs: 0 };
218
219
  }
219
- const memories = await loadEligibleMemories(storage, namespace);
220
+ const { storage: scanStorage, namespace } = await resolveScanStorage(deps, requestedNamespace);
221
+ if (config.namespacesEnabled && namespace !== void 0 && isDefaultNamespaceScan(config, requestedNamespace, namespace)) {
222
+ const migrated = migrateUnscopedPairsToNamespace(memoryDir, namespace, { cooldownDays: scanConfig.cooldownDays });
223
+ if (migrated > 0) {
224
+ log.info("[contradiction-scan] migrated %d legacy unscoped review pairs to namespace %s", migrated, namespace);
225
+ }
226
+ }
227
+ const scopedEmbeddingLookup = embeddingLookupFactory ? embeddingLookupFactory(scanStorage) : embeddingLookup;
228
+ const memories = await loadEligibleMemories(scanStorage);
220
229
  log.info("[contradiction-scan] loaded %d eligible memories", memories.length);
221
230
  if (memories.length < 2) {
222
231
  return { scanned: memories.length, candidates: 0, judged: 0, queued: 0, cooledDown: 0, elapsedMs: Date.now() - startTime };
@@ -226,7 +235,7 @@ async function runContradictionScan(deps) {
226
235
  for (const p of existingPairs) {
227
236
  existingMap.set(p.pairId, p);
228
237
  }
229
- const candidates = await generatePairs(memories, existingMap, scanConfig, scopedEmbeddingLookup);
238
+ const candidates = await generatePairs(memories, existingMap, scanConfig, namespace, scopedEmbeddingLookup);
230
239
  const cooledDown = candidates.skipped;
231
240
  log.info("[contradiction-scan] generated %d candidates (%d cooled down)", candidates.pairs.length, cooledDown);
232
241
  if (candidates.pairs.length === 0) {
@@ -239,8 +248,7 @@ async function runContradictionScan(deps) {
239
248
  elapsedMs: Date.now() - startTime
240
249
  };
241
250
  }
242
- const capped = candidates.pairs.sort((a, b) => computePairId(a.idA, a.idB).localeCompare(computePairId(b.idA, b.idB))).slice(0, scanConfig.maxPairsPerRun);
243
- const judgeInputs = capped.map((pair) => ({
251
+ const judgeInputs = candidates.pairs.map((pair) => ({
244
252
  memoryIdA: pair.idA,
245
253
  memoryIdB: pair.idB,
246
254
  textA: pair.textA,
@@ -252,6 +260,7 @@ async function runContradictionScan(deps) {
252
260
  const judgeResult = await judgeContradictionPairs(judgeInputs, config, localLlm, fallbackLlm, scanCache);
253
261
  log.info("[contradiction-scan] judge completed: %d judged, %d cached in %dms", judgeResult.judged, judgeResult.cached, judgeResult.elapsed);
254
262
  const queueEntries = [];
263
+ const currentMemoryHashes = memoryContentHashMap(memories);
255
264
  for (const [key, result] of judgeResult.results) {
256
265
  queueEntries.push({
257
266
  memoryIds: [result.memoryIdA, result.memoryIdB],
@@ -261,10 +270,11 @@ async function runContradictionScan(deps) {
261
270
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
262
271
  // Set lastReviewedAt for non-actionable verdicts so cooldown prevents re-judging
263
272
  lastReviewedAt: result.verdict === "independent" ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
273
+ memoryContentHashes: pairMemoryContentHashes(result.memoryIdA, result.memoryIdB, currentMemoryHashes),
264
274
  namespace
265
275
  });
266
276
  }
267
- const written = writePairs(memoryDir, queueEntries);
277
+ const written = writePairs(memoryDir, queueEntries, { cooldownDays: scanConfig.cooldownDays });
268
278
  const elapsed = Date.now() - startTime;
269
279
  log.info("[contradiction-scan] complete: %d queued in %dms", written.length, elapsed);
270
280
  return {
@@ -276,13 +286,24 @@ async function runContradictionScan(deps) {
276
286
  elapsedMs: elapsed
277
287
  };
278
288
  }
279
- async function generatePairs(memories, existingPairs, scanConfig, embeddingLookup) {
289
+ async function generatePairs(memories, existingPairs, scanConfig, namespace, embeddingLookup) {
280
290
  const pairs = [];
281
- const embeddingPairs = [];
282
291
  let skipped = 0;
283
292
  const seen = /* @__PURE__ */ new Set();
293
+ const maxPairs = Math.max(0, Math.floor(scanConfig.maxPairsPerRun));
294
+ if (maxPairs === 0) return { pairs, skipped };
295
+ const orderedMemories = [...memories].sort(compareMemoryId);
296
+ const currentMemoryHashes = memoryContentHashMap(orderedMemories);
297
+ const pushCandidate = (pair) => {
298
+ if (pairs.length < maxPairs) pairs.push(pair);
299
+ };
300
+ const hasCapacity = () => pairs.length < maxPairs;
301
+ const isSkippedByCooldown = (existing) => {
302
+ if (!existing || !isCoolingDown(existing, scanConfig.cooldownDays)) return false;
303
+ return !memoryHashesChanged("", existing, (memoryId) => currentMemoryHashes.get(memoryId) ?? null);
304
+ };
284
305
  const byEntity = /* @__PURE__ */ new Map();
285
- for (const mem of memories) {
306
+ for (const mem of orderedMemories) {
286
307
  const entity = mem.frontmatter.entityRef;
287
308
  if (entity) {
288
309
  const existing = byEntity.get(entity) ?? [];
@@ -290,21 +311,55 @@ async function generatePairs(memories, existingPairs, scanConfig, embeddingLooku
290
311
  byEntity.set(entity, existing);
291
312
  }
292
313
  }
293
- for (const [, group] of byEntity) {
294
- if (group.length < 2) continue;
295
- for (let i = 0; i < group.length; i++) {
296
- for (let j = i + 1; j < group.length; j++) {
297
- const a = group[i];
298
- const b = group[j];
299
- const pairId = computePairId(a.frontmatter.id, b.frontmatter.id);
314
+ entityPairs:
315
+ for (const entity of [...byEntity.keys()].sort()) {
316
+ const group = byEntity.get(entity) ?? [];
317
+ if (group.length < 2) continue;
318
+ for (let i = 0; i < group.length; i++) {
319
+ for (let j = i + 1; j < group.length; j++) {
320
+ if (!hasCapacity()) break entityPairs;
321
+ const a = group[i];
322
+ const b = group[j];
323
+ const pairId = computePairId(a.frontmatter.id, b.frontmatter.id, namespace);
324
+ if (seen.has(pairId)) continue;
325
+ seen.add(pairId);
326
+ const existing = existingPairs.get(pairId);
327
+ if (isSkippedByCooldown(existing)) {
328
+ skipped++;
329
+ continue;
330
+ }
331
+ pushCandidate({
332
+ idA: a.frontmatter.id,
333
+ idB: b.frontmatter.id,
334
+ textA: a.content,
335
+ textB: b.content,
336
+ categoryA: a.frontmatter.category,
337
+ categoryB: b.frontmatter.category
338
+ });
339
+ }
340
+ }
341
+ }
342
+ const noEntity = orderedMemories.filter((m) => !m.frontmatter.entityRef);
343
+ topicPairs:
344
+ for (let i = 0; i < noEntity.length; i++) {
345
+ for (let j = i + 1; j < noEntity.length; j++) {
346
+ if (!hasCapacity()) break topicPairs;
347
+ const a = noEntity[i];
348
+ const b = noEntity[j];
349
+ const overlap = jaccardOverlap(
350
+ a.frontmatter.tags ?? [],
351
+ b.frontmatter.tags ?? []
352
+ );
353
+ if (overlap < scanConfig.topicOverlapFloor) continue;
354
+ const pairId = computePairId(a.frontmatter.id, b.frontmatter.id, namespace);
300
355
  if (seen.has(pairId)) continue;
301
356
  seen.add(pairId);
302
357
  const existing = existingPairs.get(pairId);
303
- if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {
358
+ if (isSkippedByCooldown(existing)) {
304
359
  skipped++;
305
360
  continue;
306
361
  }
307
- pairs.push({
362
+ pushCandidate({
308
363
  idA: a.frontmatter.id,
309
364
  idB: b.frontmatter.id,
310
365
  textA: a.content,
@@ -314,71 +369,97 @@ async function generatePairs(memories, existingPairs, scanConfig, embeddingLooku
314
369
  });
315
370
  }
316
371
  }
317
- }
318
- const noEntity = memories.filter((m) => !m.frontmatter.entityRef);
319
- for (let i = 0; i < noEntity.length; i++) {
320
- for (let j = i + 1; j < noEntity.length; j++) {
321
- const a = noEntity[i];
322
- const b = noEntity[j];
323
- const overlap = jaccardOverlap(
324
- a.frontmatter.tags ?? [],
325
- b.frontmatter.tags ?? []
326
- );
327
- if (overlap < scanConfig.topicOverlapFloor) continue;
328
- const pairId = computePairId(a.frontmatter.id, b.frontmatter.id);
329
- if (seen.has(pairId)) continue;
330
- seen.add(pairId);
331
- const existing = existingPairs.get(pairId);
332
- if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {
333
- skipped++;
334
- continue;
335
- }
336
- pairs.push({
337
- idA: a.frontmatter.id,
338
- idB: b.frontmatter.id,
339
- textA: a.content,
340
- textB: b.content,
341
- categoryA: a.frontmatter.category,
342
- categoryB: b.frontmatter.category
343
- });
344
- }
345
- }
346
- if (embeddingLookup) {
347
- const memoryById = new Map(memories.map((m) => [m.frontmatter.id, m]));
348
- for (const mem of memories) {
349
- const id = mem.frontmatter.id;
350
- try {
351
- const hits = await embeddingLookup(mem.content, 20);
352
- for (const hit of hits) {
353
- if (hit.score < scanConfig.similarityFloor) continue;
354
- if (hit.id === id) continue;
355
- const peer = memoryById.get(hit.id);
356
- if (!peer) continue;
357
- const pairId = computePairId(id, hit.id);
358
- if (seen.has(pairId)) continue;
359
- seen.add(pairId);
360
- const existing = existingPairs.get(pairId);
361
- if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {
362
- skipped++;
363
- continue;
372
+ if (embeddingLookup && hasCapacity()) {
373
+ const memoryById = new Map(orderedMemories.map((m) => [m.frontmatter.id, m]));
374
+ embeddingPairs:
375
+ for (const mem of orderedMemories) {
376
+ if (!hasCapacity()) break;
377
+ const id = mem.frontmatter.id;
378
+ try {
379
+ const hits = (await embeddingLookup(mem.content, 20)).sort(
380
+ (a, b) => b.score - a.score || a.id.localeCompare(b.id)
381
+ );
382
+ for (const hit of hits) {
383
+ if (!hasCapacity()) break embeddingPairs;
384
+ if (hit.score < scanConfig.similarityFloor) continue;
385
+ if (hit.id === id) continue;
386
+ const peer = memoryById.get(hit.id);
387
+ if (!peer) continue;
388
+ const pairId = computePairId(id, hit.id, namespace);
389
+ if (seen.has(pairId)) continue;
390
+ seen.add(pairId);
391
+ const existing = existingPairs.get(pairId);
392
+ if (isSkippedByCooldown(existing)) {
393
+ skipped++;
394
+ continue;
395
+ }
396
+ pushCandidate({
397
+ idA: id,
398
+ idB: hit.id,
399
+ textA: mem.content,
400
+ textB: peer.content,
401
+ categoryA: mem.frontmatter.category,
402
+ categoryB: peer.frontmatter.category
403
+ });
364
404
  }
365
- embeddingPairs.push({
366
- idA: id,
367
- idB: hit.id,
368
- textA: mem.content,
369
- textB: peer.content,
370
- categoryA: mem.frontmatter.category,
371
- categoryB: peer.frontmatter.category
372
- });
405
+ } catch {
373
406
  }
374
- } catch {
375
407
  }
376
- }
377
408
  }
378
- pairs.push(...embeddingPairs);
379
409
  return { pairs, skipped };
380
410
  }
381
- async function loadEligibleMemories(storage, namespace) {
411
+ function normalizeScanNamespace(namespace) {
412
+ const trimmed = namespace?.trim();
413
+ return trimmed ? trimmed : void 0;
414
+ }
415
+ async function resolveScanStorage(deps, namespace) {
416
+ if (!deps.config.namespacesEnabled) {
417
+ const defaultNamespace = normalizeScanNamespace(deps.config.defaultNamespace);
418
+ if (namespace && namespace !== defaultNamespace) {
419
+ throw new Error(`unsupported namespace: ${namespace}`);
420
+ }
421
+ return { storage: deps.storage, namespace: void 0 };
422
+ }
423
+ if (deps.storageForNamespace) {
424
+ const resolved = await deps.storageForNamespace(namespace);
425
+ if (isScanStorageResolution(resolved)) {
426
+ return {
427
+ storage: resolved.storage,
428
+ namespace: normalizeScanNamespace(resolved.namespace) ?? fallbackResolvedNamespace(deps, namespace)
429
+ };
430
+ }
431
+ if (!isStorageManagerLike(resolved)) {
432
+ throw new Error("storageForNamespace must return a StorageManager or { storage: StorageManager, namespace?: string }");
433
+ }
434
+ return {
435
+ storage: resolved,
436
+ namespace: fallbackResolvedNamespace(deps, namespace)
437
+ };
438
+ }
439
+ throw new Error(
440
+ "contradiction scans require storageForNamespace when namespaces are enabled so callers can enforce namespace access"
441
+ );
442
+ }
443
+ function isScanStorageResolution(value) {
444
+ if (isStorageManagerLike(value)) return false;
445
+ if (typeof value !== "object" || value === null) return false;
446
+ const candidate = value;
447
+ return isStorageManagerLike(candidate.storage);
448
+ }
449
+ function isStorageManagerLike(value) {
450
+ if (typeof value !== "object" || value === null) return false;
451
+ const candidate = value;
452
+ return typeof candidate.readAllMemories === "function";
453
+ }
454
+ function fallbackResolvedNamespace(deps, namespace) {
455
+ if (namespace) return namespace;
456
+ return deps.config.namespacesEnabled ? deps.config.defaultNamespace : void 0;
457
+ }
458
+ function isDefaultNamespaceScan(config, requestedNamespace, resolvedNamespace) {
459
+ if (requestedNamespace === void 0) return true;
460
+ return requestedNamespace === config.defaultNamespace || resolvedNamespace === config.defaultNamespace;
461
+ }
462
+ async function loadEligibleMemories(storage) {
382
463
  let all;
383
464
  try {
384
465
  all = await storage.readAllMemories();
@@ -407,6 +488,28 @@ function jaccardOverlap(a, b) {
407
488
  const union = setA.size + setB.size - intersection;
408
489
  return union === 0 ? 0 : intersection / union;
409
490
  }
491
+ function compareMemoryId(a, b) {
492
+ return (a.frontmatter.id ?? "").localeCompare(b.frontmatter.id ?? "");
493
+ }
494
+ function memoryContentHashMap(memories) {
495
+ const hashes = /* @__PURE__ */ new Map();
496
+ for (const memory of memories) {
497
+ const id = memory.frontmatter.id;
498
+ if (!id) continue;
499
+ hashes.set(id, computeMemoryContentHash(memory.content, memory.frontmatter.category));
500
+ }
501
+ return hashes;
502
+ }
503
+ function pairMemoryContentHashes(memoryIdA, memoryIdB, currentMemoryHashes) {
504
+ const hashA = currentMemoryHashes.get(memoryIdA);
505
+ const hashB = currentMemoryHashes.get(memoryIdB);
506
+ if (!hashA || !hashB) return void 0;
507
+ return {
508
+ [memoryIdA]: hashA,
509
+ [memoryIdB]: hashB
510
+ };
511
+ }
512
+
410
513
  export {
411
514
  ACTIVE_STATUSES,
412
515
  runContradictionScan
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  countRecallTokenOverlap,
3
3
  normalizeRecallTokens,
4
+ resolveCausalTrajectoryStoreDir,
4
5
  topicOverlapScore
5
- } from "./chunk-WPINX4MF.js";
6
+ } from "./chunk-3IKMUNW5.js";
6
7
  import {
7
8
  assertIsoRecordedAt,
8
9
  assertString,
@@ -12,7 +13,7 @@ import {
12
13
  import {
13
14
  listJsonFiles,
14
15
  readJsonFile
15
- } from "./chunk-5LE4HTVL.js";
16
+ } from "./chunk-25J4PXDH.js";
16
17
  import {
17
18
  log
18
19
  } from "./chunk-UFU5GGGA.js";
@@ -67,7 +68,7 @@ function makeEdgeId(fromId, toId) {
67
68
  return `edge-${digest}`;
68
69
  }
69
70
  function resolveChainsDir(memoryDir, causalTrajectoryStoreDir) {
70
- const root = causalTrajectoryStoreDir ? path.join(memoryDir, causalTrajectoryStoreDir) : path.join(memoryDir, "state", "causal-trajectories");
71
+ const root = resolveCausalTrajectoryStoreDir(memoryDir, causalTrajectoryStoreDir);
71
72
  return path.join(root, "chains");
72
73
  }
73
74
  function chainIndexPath(chainsDir) {
@@ -77,6 +78,22 @@ function edgeFilePath(chainsDir, edge) {
77
78
  const day = recordStoreDay(edge.createdAt);
78
79
  return path.join(chainsDir, "edges", day, `${edge.edgeId}.json`);
79
80
  }
81
+ var chainMutationQueues = /* @__PURE__ */ new Map();
82
+ function enqueueChainMutation(chainsDir, op) {
83
+ const key = path.resolve(chainsDir);
84
+ const previous = chainMutationQueues.get(key) ?? Promise.resolve();
85
+ const run = previous.catch(() => {
86
+ }).then(op);
87
+ const settled = run.catch(() => {
88
+ });
89
+ chainMutationQueues.set(key, settled);
90
+ void settled.finally(() => {
91
+ if (chainMutationQueues.get(key) === settled) {
92
+ chainMutationQueues.delete(key);
93
+ }
94
+ });
95
+ return run;
96
+ }
80
97
  async function readChainIndex(chainsDir) {
81
98
  try {
82
99
  const raw = JSON.parse(await fileReader(chainIndexPath(chainsDir), "utf8"));
@@ -213,7 +230,7 @@ function scoreStitchCandidate(newTrajectory, candidate) {
213
230
  return { trajectory: candidate, score, edgeType, stitchMethod: dominantMethod };
214
231
  }
215
232
  async function readRecentTrajectories(memoryDir, causalTrajectoryStoreDir, currentSessionKey, lookbackDays) {
216
- const root = causalTrajectoryStoreDir ? path.join(memoryDir, causalTrajectoryStoreDir) : path.join(memoryDir, "state", "causal-trajectories");
233
+ const root = resolveCausalTrajectoryStoreDir(memoryDir, causalTrajectoryStoreDir);
217
234
  const trajectoriesDir = path.join(root, "trajectories");
218
235
  const files = await listJsonFiles(trajectoriesDir).catch(() => []);
219
236
  if (files.length === 0) return [];
@@ -246,30 +263,32 @@ async function stitchCausalChain(options) {
246
263
  if (candidates.length === 0) return [];
247
264
  const scored = candidates.map((c) => scoreStitchCandidate(newTrajectory, c)).filter((s) => s.score >= stitchConfig.minScore).sort((a, b) => b.score - a.score).slice(0, stitchConfig.maxEdgesPerTrajectory);
248
265
  if (scored.length === 0) return [];
249
- const index = await readChainIndex(chainsDir);
250
- const newEdges = [];
251
- for (const candidate of scored) {
252
- const edgeId = makeEdgeId(candidate.trajectory.trajectoryId, newTrajectory.trajectoryId);
253
- if (index.edges[edgeId]) continue;
254
- const edge = {
255
- schemaVersion: 1,
256
- edgeId,
257
- fromTrajectoryId: candidate.trajectory.trajectoryId,
258
- toTrajectoryId: newTrajectory.trajectoryId,
259
- edgeType: candidate.edgeType,
260
- confidence: Math.min(1, candidate.score / 10),
261
- stitchMethod: candidate.stitchMethod,
262
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
263
- };
264
- addEdgeToIndex(index, edge);
265
- await persistEdge(chainsDir, edge);
266
- newEdges.push(edge);
267
- }
268
- if (newEdges.length > 0) {
269
- await writeChainIndex(chainsDir, index);
270
- log.debug(`[cmc] stitched ${newEdges.length} causal edge(s) for trajectory ${newTrajectory.trajectoryId}`);
271
- }
272
- return newEdges;
266
+ return enqueueChainMutation(chainsDir, async () => {
267
+ const index = await readChainIndex(chainsDir);
268
+ const newEdges = [];
269
+ for (const candidate of scored) {
270
+ const edgeId = makeEdgeId(candidate.trajectory.trajectoryId, newTrajectory.trajectoryId);
271
+ if (index.edges[edgeId]) continue;
272
+ const edge = {
273
+ schemaVersion: 1,
274
+ edgeId,
275
+ fromTrajectoryId: candidate.trajectory.trajectoryId,
276
+ toTrajectoryId: newTrajectory.trajectoryId,
277
+ edgeType: candidate.edgeType,
278
+ confidence: Math.min(1, candidate.score / 10),
279
+ stitchMethod: candidate.stitchMethod,
280
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
281
+ };
282
+ addEdgeToIndex(index, edge);
283
+ await persistEdge(chainsDir, edge);
284
+ newEdges.push(edge);
285
+ }
286
+ if (newEdges.length > 0) {
287
+ await writeChainIndex(chainsDir, index);
288
+ log.debug(`[cmc] stitched ${newEdges.length} causal edge(s) for trajectory ${newTrajectory.trajectoryId}`);
289
+ }
290
+ return newEdges;
291
+ });
273
292
  }
274
293
 
275
294
  export {