@remnic/plugin-openclaw 1.0.34 → 1.0.36

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 +59 -10
  2. package/dist/{calibration-JD4AU7FB.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-YPDWRB3C.js +17 -0
  6. package/dist/capsule-import-SWPOFG6F.js +16 -0
  7. package/dist/{capsule-merge-IWOQ34KL.js → capsule-merge-YXAF7ZJW.js} +7 -7
  8. package/dist/{causal-chain-WYN5QOPS.js → causal-chain-BVTOWZKC.js} +4 -4
  9. package/dist/{causal-consolidation-DSLFN64P.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-FGTYFLL5.js → chunk-3IHGISUN.js} +29 -32
  14. package/dist/{chunk-6UFI73TJ.js → chunk-3IKMUNW5.js} +53 -46
  15. package/dist/{chunk-EXDYWXMB.js → chunk-4XDQ3KEC.js} +1 -2
  16. package/dist/{chunk-4UA6KMRO.js → chunk-6O3H3DPL.js} +2 -2
  17. package/dist/{chunk-7NMHI4IC.js → chunk-BLC3RQNV.js} +5 -555
  18. package/dist/{chunk-4G2XCSD2.js → chunk-BZ4EYURA.js} +0 -5
  19. package/dist/{chunk-4LYQ4ONL.js → chunk-E4RM7637.js} +1 -1
  20. package/dist/{chunk-TDRJVMUP.js → chunk-EH4AXGRO.js} +0 -12
  21. package/dist/{chunk-ZXLYEVOP.js → chunk-G3CZA4SD.js} +60 -362
  22. package/dist/chunk-I2KLQ2HA.js +22 -0
  23. package/dist/chunk-IO5WWY6A.js +156 -0
  24. package/dist/{contradiction-scan-U3QKHWQN.js → chunk-JC3FCKYL.js} +191 -87
  25. package/dist/{chunk-SVSQAG6M.js → chunk-KC7KSQR4.js} +47 -28
  26. package/dist/chunk-LZCGPRHS.js +228 -0
  27. package/dist/{chunk-CXM7EBAO.js → chunk-MXFJXUHC.js} +1 -1
  28. package/dist/{chunk-L6I4MQKO.js → chunk-NNAN63QK.js} +6 -6
  29. package/dist/{chunk-VRGUUHBV.js → chunk-NUWDSTP7.js} +1 -1
  30. package/dist/{chunk-6OJAU466.js → chunk-QMUQV5NP.js} +0 -1
  31. package/dist/{chunk-LLUROTZJ.js → chunk-QQXJODFL.js} +9 -9
  32. package/dist/{chunk-6F6EKSVP.js → chunk-QXXEF7VI.js} +1 -1
  33. package/dist/{chunk-NDZNURDM.js → chunk-SEGEX7W4.js} +73 -241
  34. package/dist/{chunk-7NUFIRM3.js → chunk-SWOYEQN2.js} +97 -21
  35. package/dist/chunk-TH5FF5SC.js +16 -0
  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-57HLTQBN.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-33SPYXQY.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 +7187 -71983
  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-FEQCA35V.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-R3V6ZFQT.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-7L34HYDW.js} +3 -3
  71. package/openclaw.plugin.json +153 -20
  72. package/package.json +18 -9
  73. package/scripts/faiss_index.py +756 -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";
@@ -135,7 +137,8 @@ async function callLlm(client, config, userMessage) {
135
137
  if ("chatCompletion" in client && typeof client.chatCompletion === "function") {
136
138
  const result = await client.chatCompletion(messages, {
137
139
  temperature: 0.1,
138
- maxTokens: 4096
140
+ maxTokens: 4096,
141
+ operation: "contradiction-judge"
139
142
  });
140
143
  return result?.content ?? "";
141
144
  }
@@ -156,15 +159,14 @@ function parseJudgeResponse(candidates, inputs) {
156
159
  for (const item of items) {
157
160
  if (!item || typeof item !== "object") continue;
158
161
  const verdict = typeof item.verdict === "string" && VALID_VERDICTS.includes(item.verdict) ? item.verdict : "needs-user";
159
- const pairKeyVal = typeof item.pairKey === "string" ? item.pairKey : null;
162
+ const pairKeyVal = typeof item.pairKey === "string" && item.pairKey.length > 0 ? item.pairKey : null;
160
163
  const input = pairKeyVal ? inputs.find((p) => pairKey(p.memoryIdA, p.memoryIdB) === pairKeyVal) : null;
161
- const fallbackInput = input ?? inputs[results.length] ?? inputs[0];
162
- if (!fallbackInput) continue;
163
- matchedKeys.add(pairKey(fallbackInput.memoryIdA, fallbackInput.memoryIdB));
164
+ if (!input) continue;
165
+ matchedKeys.add(pairKey(input.memoryIdA, input.memoryIdB));
164
166
  const confidence = typeof item.confidence === "number" ? Math.min(1, Math.max(0, item.confidence)) : 0.5;
165
167
  results.push({
166
- memoryIdA: fallbackInput.memoryIdA,
167
- memoryIdB: fallbackInput.memoryIdB,
168
+ memoryIdA: input.memoryIdA,
169
+ memoryIdB: input.memoryIdB,
168
170
  verdict,
169
171
  rationale: typeof item.rationale === "string" ? item.rationale : "No rationale provided",
170
172
  confidence
@@ -208,14 +210,22 @@ var SCAN_CATEGORIES = /* @__PURE__ */ new Set([
208
210
  ]);
209
211
  async function runContradictionScan(deps) {
210
212
  const startTime = Date.now();
211
- 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);
212
215
  const scanConfig = config.contradictionScan;
213
- const scopedEmbeddingLookup = embeddingLookupFactory ? embeddingLookupFactory(storage) : embeddingLookup;
214
216
  if (!scanConfig.enabled) {
215
217
  log.info("[contradiction-scan] disabled by config");
216
218
  return { scanned: 0, candidates: 0, judged: 0, queued: 0, cooledDown: 0, elapsedMs: 0 };
217
219
  }
218
- 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);
219
229
  log.info("[contradiction-scan] loaded %d eligible memories", memories.length);
220
230
  if (memories.length < 2) {
221
231
  return { scanned: memories.length, candidates: 0, judged: 0, queued: 0, cooledDown: 0, elapsedMs: Date.now() - startTime };
@@ -225,7 +235,7 @@ async function runContradictionScan(deps) {
225
235
  for (const p of existingPairs) {
226
236
  existingMap.set(p.pairId, p);
227
237
  }
228
- const candidates = await generatePairs(memories, existingMap, scanConfig, scopedEmbeddingLookup);
238
+ const candidates = await generatePairs(memories, existingMap, scanConfig, namespace, scopedEmbeddingLookup);
229
239
  const cooledDown = candidates.skipped;
230
240
  log.info("[contradiction-scan] generated %d candidates (%d cooled down)", candidates.pairs.length, cooledDown);
231
241
  if (candidates.pairs.length === 0) {
@@ -238,8 +248,7 @@ async function runContradictionScan(deps) {
238
248
  elapsedMs: Date.now() - startTime
239
249
  };
240
250
  }
241
- const capped = candidates.pairs.sort((a, b) => computePairId(a.idA, a.idB).localeCompare(computePairId(b.idA, b.idB))).slice(0, scanConfig.maxPairsPerRun);
242
- const judgeInputs = capped.map((pair) => ({
251
+ const judgeInputs = candidates.pairs.map((pair) => ({
243
252
  memoryIdA: pair.idA,
244
253
  memoryIdB: pair.idB,
245
254
  textA: pair.textA,
@@ -251,6 +260,7 @@ async function runContradictionScan(deps) {
251
260
  const judgeResult = await judgeContradictionPairs(judgeInputs, config, localLlm, fallbackLlm, scanCache);
252
261
  log.info("[contradiction-scan] judge completed: %d judged, %d cached in %dms", judgeResult.judged, judgeResult.cached, judgeResult.elapsed);
253
262
  const queueEntries = [];
263
+ const currentMemoryHashes = memoryContentHashMap(memories);
254
264
  for (const [key, result] of judgeResult.results) {
255
265
  queueEntries.push({
256
266
  memoryIds: [result.memoryIdA, result.memoryIdB],
@@ -260,10 +270,11 @@ async function runContradictionScan(deps) {
260
270
  detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
261
271
  // Set lastReviewedAt for non-actionable verdicts so cooldown prevents re-judging
262
272
  lastReviewedAt: result.verdict === "independent" ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
273
+ memoryContentHashes: pairMemoryContentHashes(result.memoryIdA, result.memoryIdB, currentMemoryHashes),
263
274
  namespace
264
275
  });
265
276
  }
266
- const written = writePairs(memoryDir, queueEntries);
277
+ const written = writePairs(memoryDir, queueEntries, { cooldownDays: scanConfig.cooldownDays });
267
278
  const elapsed = Date.now() - startTime;
268
279
  log.info("[contradiction-scan] complete: %d queued in %dms", written.length, elapsed);
269
280
  return {
@@ -275,13 +286,24 @@ async function runContradictionScan(deps) {
275
286
  elapsedMs: elapsed
276
287
  };
277
288
  }
278
- async function generatePairs(memories, existingPairs, scanConfig, embeddingLookup) {
289
+ async function generatePairs(memories, existingPairs, scanConfig, namespace, embeddingLookup) {
279
290
  const pairs = [];
280
- const embeddingPairs = [];
281
291
  let skipped = 0;
282
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
+ };
283
305
  const byEntity = /* @__PURE__ */ new Map();
284
- for (const mem of memories) {
306
+ for (const mem of orderedMemories) {
285
307
  const entity = mem.frontmatter.entityRef;
286
308
  if (entity) {
287
309
  const existing = byEntity.get(entity) ?? [];
@@ -289,21 +311,55 @@ async function generatePairs(memories, existingPairs, scanConfig, embeddingLooku
289
311
  byEntity.set(entity, existing);
290
312
  }
291
313
  }
292
- for (const [, group] of byEntity) {
293
- if (group.length < 2) continue;
294
- for (let i = 0; i < group.length; i++) {
295
- for (let j = i + 1; j < group.length; j++) {
296
- const a = group[i];
297
- const b = group[j];
298
- 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);
299
355
  if (seen.has(pairId)) continue;
300
356
  seen.add(pairId);
301
357
  const existing = existingPairs.get(pairId);
302
- if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {
358
+ if (isSkippedByCooldown(existing)) {
303
359
  skipped++;
304
360
  continue;
305
361
  }
306
- pairs.push({
362
+ pushCandidate({
307
363
  idA: a.frontmatter.id,
308
364
  idB: b.frontmatter.id,
309
365
  textA: a.content,
@@ -313,71 +369,97 @@ async function generatePairs(memories, existingPairs, scanConfig, embeddingLooku
313
369
  });
314
370
  }
315
371
  }
316
- }
317
- const noEntity = memories.filter((m) => !m.frontmatter.entityRef);
318
- for (let i = 0; i < noEntity.length; i++) {
319
- for (let j = i + 1; j < noEntity.length; j++) {
320
- const a = noEntity[i];
321
- const b = noEntity[j];
322
- const overlap = jaccardOverlap(
323
- a.frontmatter.tags ?? [],
324
- b.frontmatter.tags ?? []
325
- );
326
- if (overlap < scanConfig.topicOverlapFloor) continue;
327
- const pairId = computePairId(a.frontmatter.id, b.frontmatter.id);
328
- if (seen.has(pairId)) continue;
329
- seen.add(pairId);
330
- const existing = existingPairs.get(pairId);
331
- if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {
332
- skipped++;
333
- continue;
334
- }
335
- pairs.push({
336
- idA: a.frontmatter.id,
337
- idB: b.frontmatter.id,
338
- textA: a.content,
339
- textB: b.content,
340
- categoryA: a.frontmatter.category,
341
- categoryB: b.frontmatter.category
342
- });
343
- }
344
- }
345
- if (embeddingLookup) {
346
- const memoryById = new Map(memories.map((m) => [m.frontmatter.id, m]));
347
- for (const mem of memories) {
348
- const id = mem.frontmatter.id;
349
- try {
350
- const hits = await embeddingLookup(mem.content, 20);
351
- for (const hit of hits) {
352
- if (hit.score < scanConfig.similarityFloor) continue;
353
- if (hit.id === id) continue;
354
- const peer = memoryById.get(hit.id);
355
- if (!peer) continue;
356
- const pairId = computePairId(id, hit.id);
357
- if (seen.has(pairId)) continue;
358
- seen.add(pairId);
359
- const existing = existingPairs.get(pairId);
360
- if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {
361
- skipped++;
362
- 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
+ });
363
404
  }
364
- embeddingPairs.push({
365
- idA: id,
366
- idB: hit.id,
367
- textA: mem.content,
368
- textB: peer.content,
369
- categoryA: mem.frontmatter.category,
370
- categoryB: peer.frontmatter.category
371
- });
405
+ } catch {
372
406
  }
373
- } catch {
374
407
  }
375
- }
376
408
  }
377
- pairs.push(...embeddingPairs);
378
409
  return { pairs, skipped };
379
410
  }
380
- 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) {
381
463
  let all;
382
464
  try {
383
465
  all = await storage.readAllMemories();
@@ -406,6 +488,28 @@ function jaccardOverlap(a, b) {
406
488
  const union = setA.size + setB.size - intersection;
407
489
  return union === 0 ? 0 : intersection / union;
408
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
+
409
513
  export {
410
514
  ACTIVE_STATUSES,
411
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 {