@remnic/plugin-openclaw 1.0.53 → 9.3.515
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +510 -2071
- package/openclaw.plugin.json +3 -3
- package/package.json +4 -3
- package/scripts/faiss_index.py +141 -29
- package/dist/calibration-RKL2LRW4.js +0 -250
- package/dist/capsule-cli-EHZPMXBC.js +0 -33
- package/dist/capsule-crypto-JS67OSWM.js +0 -17
- package/dist/capsule-export-DX53CPIT.js +0 -17
- package/dist/capsule-import-4OXCPHOT.js +0 -16
- package/dist/capsule-merge-25AUN33Q.js +0 -195
- package/dist/causal-chain-BVTOWZKC.js +0 -23
- package/dist/causal-consolidation-V533DIGW.js +0 -233
- package/dist/causal-retrieval-XAP6QKHZ.js +0 -182
- package/dist/causal-trajectory-graph-ZWQWZ7N5.js +0 -59
- package/dist/chunk-25J4PXDH.js +0 -30
- package/dist/chunk-2RQU6GVD.js +0 -453
- package/dist/chunk-3A5ELHTT.js +0 -61
- package/dist/chunk-3G7FAF6S.js +0 -60
- package/dist/chunk-3IKMUNW5.js +0 -177
- package/dist/chunk-4XDQ3KEC.js +0 -4048
- package/dist/chunk-5ZW5XJQ6.js +0 -128
- package/dist/chunk-6IWEAUN6.js +0 -148
- package/dist/chunk-7JOLBJJ5.js +0 -874
- package/dist/chunk-B52XADV3.js +0 -244
- package/dist/chunk-BQZ7JACP.js +0 -389
- package/dist/chunk-BU5KJVWF.js +0 -78
- package/dist/chunk-BZ4EYURA.js +0 -181
- package/dist/chunk-CEL5ZLKP.js +0 -233
- package/dist/chunk-EH4AXGRO.js +0 -48
- package/dist/chunk-I2KLQ2HA.js +0 -22
- package/dist/chunk-IO5WWY6A.js +0 -156
- package/dist/chunk-JC3FCKYL.js +0 -516
- package/dist/chunk-JZBOXOUC.js +0 -259
- package/dist/chunk-KC7KSQR4.js +0 -302
- package/dist/chunk-LZCGPRHS.js +0 -228
- package/dist/chunk-MBIFE6SA.js +0 -250
- package/dist/chunk-MWLY7VQX.js +0 -896
- package/dist/chunk-MXFJXUHC.js +0 -292
- package/dist/chunk-NUWDSTP7.js +0 -233
- package/dist/chunk-PSAPZGDX.js +0 -725
- package/dist/chunk-QCCP4RU5.js +0 -190
- package/dist/chunk-QMUQV5NP.js +0 -146
- package/dist/chunk-QQXJODFL.js +0 -328
- package/dist/chunk-QVTGUY3H.js +0 -1295
- package/dist/chunk-SWOYEQN2.js +0 -1005
- package/dist/chunk-TH5FF5SC.js +0 -16
- package/dist/chunk-TXOEHSVP.js +0 -275
- package/dist/chunk-UFU5GGGA.js +0 -47
- package/dist/chunk-UZJ7EERS.js +0 -272
- package/dist/chunk-W6EEFUCJ.js +0 -349
- package/dist/chunk-YGGGUTG3.js +0 -125
- package/dist/chunk-YJYZMLD5.js +0 -360
- package/dist/chunk-YKV4EFUI.js +0 -199
- package/dist/chunk-YXDXRE6N.js +0 -6500
- package/dist/chunk-ZS6VABML.js +0 -185
- package/dist/cipher-E23BHBSO.js +0 -27
- package/dist/consolidation-undo-FKJZCJHS.js +0 -426
- package/dist/contradiction-review-WJRWNQ5N.js +0 -29
- package/dist/contradiction-scan-5X423QGT.js +0 -12
- package/dist/dreams-ledger-KDX44I7R.js +0 -290
- package/dist/engine-7VG3IXKN.js +0 -16
- package/dist/extraction-judge-telemetry-O4ZVGLTU.js +0 -14
- package/dist/fallback-llm-43UMEXNJ.js +0 -10
- package/dist/first-start-migration-H2SAXAGR.js +0 -263
- package/dist/forget-ZECIDNL5.js +0 -68
- package/dist/fs-utils-OYXSZSVV.js +0 -39
- package/dist/graph-edge-decay-24ZKD5QL.js +0 -202
- package/dist/kdf-RXKIWHRU.js +0 -25
- package/dist/logger-XG7JKLPS.js +0 -9
- package/dist/memory-governance-TK4B4ZZ2.js +0 -25
- package/dist/metadata-WK2TRPYZ.js +0 -20
- package/dist/migrate-from-identity-anchor-SNDNKHZD.js +0 -7
- package/dist/path-ZKO74XXC.js +0 -7
- package/dist/peers-W53WSDXG.js +0 -43
- package/dist/purge-IKJISXEQ.js +0 -204
- package/dist/resolution-BN35OXDS.js +0 -11
- package/dist/secure-store-6RWYM6GK.js +0 -155
- package/dist/state-4ITLYMAU.js +0 -7
- package/dist/state-store-ET3ADVY5.js +0 -14
- package/dist/storage-HUNVVHZ7.js +0 -27
- package/dist/tier-stats-ZRQBV6G2.js +0 -147
- package/dist/trace-IL2Y34EH.js +0 -289
- package/dist/tui-7KRDCMYK.js +0 -12
- package/dist/types-MBUINTB2.js +0 -30
package/dist/chunk-JC3FCKYL.js
DELETED
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
computeMemoryContentHash,
|
|
3
|
-
computePairId,
|
|
4
|
-
isCoolingDown,
|
|
5
|
-
listPairs,
|
|
6
|
-
memoryHashesChanged,
|
|
7
|
-
migrateUnscopedPairsToNamespace,
|
|
8
|
-
writePairs
|
|
9
|
-
} from "./chunk-YJYZMLD5.js";
|
|
10
|
-
import {
|
|
11
|
-
extractJsonCandidates
|
|
12
|
-
} from "./chunk-3A5ELHTT.js";
|
|
13
|
-
import {
|
|
14
|
-
log
|
|
15
|
-
} from "./chunk-UFU5GGGA.js";
|
|
16
|
-
|
|
17
|
-
// ../remnic-core/src/contradiction/contradiction-judge.ts
|
|
18
|
-
import { createHash } from "crypto";
|
|
19
|
-
var CONTRADICTION_JUDGE_PROMPT = `You are a memory contradiction classifier. You will receive pairs of stored memories and must classify their semantic relationship.
|
|
20
|
-
|
|
21
|
-
For each pair, respond with a JSON array where each element has:
|
|
22
|
-
- "pairKey": the pairKey provided in the input
|
|
23
|
-
- "verdict": one of "contradicts", "independent", "duplicates", "needs-user"
|
|
24
|
-
- "rationale": one sentence explaining why
|
|
25
|
-
- "confidence": number between 0 and 1
|
|
26
|
-
|
|
27
|
-
VERDICT DEFINITIONS:
|
|
28
|
-
- "contradicts": The two memories make claims that cannot both be true. One must be wrong or outdated.
|
|
29
|
-
- "duplicates": The two memories convey essentially the same information (near-paraphrase).
|
|
30
|
-
- "independent": The memories are topically similar but do not conflict or duplicate.
|
|
31
|
-
- "needs-user": Cannot determine with sufficient confidence; requires human review.
|
|
32
|
-
|
|
33
|
-
IMPORTANT:
|
|
34
|
-
- Be conservative. When in doubt, prefer "needs-user" over a wrong classification.
|
|
35
|
-
- Two memories about the same entity/topic are NOT necessarily contradictory.
|
|
36
|
-
- Temporal changes ("Joshua uses pnpm" vs "Joshua switched to npm") ARE contradictions.
|
|
37
|
-
- Different aspects of the same entity ("Joshua uses pnpm" vs "Joshua works on Remnic") are "independent".`;
|
|
38
|
-
var defaultVerdictCache = /* @__PURE__ */ new Map();
|
|
39
|
-
var CACHE_MAX = 1e4;
|
|
40
|
-
function pairKey(idA, idB) {
|
|
41
|
-
const sorted = [idA, idB].sort();
|
|
42
|
-
return `${sorted[0]}:${sorted[1]}`;
|
|
43
|
-
}
|
|
44
|
-
function contentHash(a) {
|
|
45
|
-
const sides = [
|
|
46
|
-
[a.textA.trim(), (a.categoryA ?? "").trim()].join("|"),
|
|
47
|
-
[a.textB.trim(), (a.categoryB ?? "").trim()].join("|")
|
|
48
|
-
].sort();
|
|
49
|
-
const normalized = sides.join("|||");
|
|
50
|
-
return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
51
|
-
}
|
|
52
|
-
async function judgeContradictionPairs(pairs, config, localLlm, fallbackLlm, cache) {
|
|
53
|
-
const startTime = Date.now();
|
|
54
|
-
const results = /* @__PURE__ */ new Map();
|
|
55
|
-
const activeCache = cache ?? defaultVerdictCache;
|
|
56
|
-
let cached = 0;
|
|
57
|
-
let judged = 0;
|
|
58
|
-
const toJudge = [];
|
|
59
|
-
for (const pair of pairs) {
|
|
60
|
-
const key = pairKey(pair.memoryIdA, pair.memoryIdB);
|
|
61
|
-
const hash = contentHash(pair);
|
|
62
|
-
const cachedResult = activeCache.get(hash);
|
|
63
|
-
if (cachedResult) {
|
|
64
|
-
results.set(key, { ...cachedResult, memoryIdA: pair.memoryIdA, memoryIdB: pair.memoryIdB });
|
|
65
|
-
cached++;
|
|
66
|
-
} else {
|
|
67
|
-
toJudge.push(pair);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (toJudge.length === 0) {
|
|
71
|
-
return { results, cached, judged, elapsed: Date.now() - startTime };
|
|
72
|
-
}
|
|
73
|
-
const pairDescriptions = toJudge.map((p, i) => {
|
|
74
|
-
const pk = pairKey(p.memoryIdA, p.memoryIdB);
|
|
75
|
-
const catA = p.categoryA ? ` [${p.categoryA}]` : "";
|
|
76
|
-
const catB = p.categoryB ? ` [${p.categoryB}]` : "";
|
|
77
|
-
return `Pair ${i + 1} (pairKey: "${pk}"):${catA} "${p.textA}"${catB} "${p.textB}"`;
|
|
78
|
-
});
|
|
79
|
-
const userMessage = `Classify these ${toJudge.length} memory pair(s):
|
|
80
|
-
|
|
81
|
-
${pairDescriptions.join("\n\n")}`;
|
|
82
|
-
let llmResponse = null;
|
|
83
|
-
if (localLlm) {
|
|
84
|
-
try {
|
|
85
|
-
llmResponse = await callLlm(localLlm, config, userMessage);
|
|
86
|
-
} catch (err) {
|
|
87
|
-
log.warn("[contradiction-judge] local LLM call failed: %s", err instanceof Error ? err.message : err);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (!llmResponse && fallbackLlm) {
|
|
91
|
-
try {
|
|
92
|
-
llmResponse = await callLlm(fallbackLlm, config, userMessage);
|
|
93
|
-
} catch (err) {
|
|
94
|
-
log.warn("[contradiction-judge] fallback LLM call failed: %s", err instanceof Error ? err.message : err);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (llmResponse) {
|
|
98
|
-
const candidates = extractJsonCandidates(llmResponse);
|
|
99
|
-
const parsed = parseJudgeResponse(candidates, toJudge);
|
|
100
|
-
for (const result of parsed) {
|
|
101
|
-
const key = pairKey(result.memoryIdA, result.memoryIdB);
|
|
102
|
-
results.set(key, result);
|
|
103
|
-
const input = toJudge.find(
|
|
104
|
-
(p) => pairKey(p.memoryIdA, p.memoryIdB) === key
|
|
105
|
-
);
|
|
106
|
-
if (input) {
|
|
107
|
-
const hash = contentHash(input);
|
|
108
|
-
if (activeCache.size >= CACHE_MAX) {
|
|
109
|
-
const firstKey = activeCache.keys().next().value;
|
|
110
|
-
if (firstKey !== void 0) activeCache.delete(firstKey);
|
|
111
|
-
}
|
|
112
|
-
activeCache.set(hash, result);
|
|
113
|
-
}
|
|
114
|
-
judged++;
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
for (const pair of toJudge) {
|
|
118
|
-
const key = pairKey(pair.memoryIdA, pair.memoryIdB);
|
|
119
|
-
const result = {
|
|
120
|
-
memoryIdA: pair.memoryIdA,
|
|
121
|
-
memoryIdB: pair.memoryIdB,
|
|
122
|
-
verdict: "needs-user",
|
|
123
|
-
rationale: "LLM call failed; requires manual review",
|
|
124
|
-
confidence: 0
|
|
125
|
-
};
|
|
126
|
-
results.set(key, result);
|
|
127
|
-
judged++;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return { results, cached, judged, elapsed: Date.now() - startTime };
|
|
131
|
-
}
|
|
132
|
-
async function callLlm(client, config, userMessage) {
|
|
133
|
-
const messages = [
|
|
134
|
-
{ role: "system", content: CONTRADICTION_JUDGE_PROMPT },
|
|
135
|
-
{ role: "user", content: userMessage }
|
|
136
|
-
];
|
|
137
|
-
if ("chatCompletion" in client && typeof client.chatCompletion === "function") {
|
|
138
|
-
const result = await client.chatCompletion(messages, {
|
|
139
|
-
temperature: 0.1,
|
|
140
|
-
maxTokens: 4096,
|
|
141
|
-
operation: "contradiction-judge"
|
|
142
|
-
});
|
|
143
|
-
return result?.content ?? "";
|
|
144
|
-
}
|
|
145
|
-
if ("complete" in client && typeof client.complete === "function") {
|
|
146
|
-
const result = await client.complete(messages);
|
|
147
|
-
return result.content ?? "";
|
|
148
|
-
}
|
|
149
|
-
return "";
|
|
150
|
-
}
|
|
151
|
-
function parseJudgeResponse(candidates, inputs) {
|
|
152
|
-
const VALID_VERDICTS = ["contradicts", "independent", "duplicates", "needs-user"];
|
|
153
|
-
for (const candidate of candidates) {
|
|
154
|
-
try {
|
|
155
|
-
const parsed = JSON.parse(candidate);
|
|
156
|
-
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
157
|
-
const results = [];
|
|
158
|
-
const matchedKeys = /* @__PURE__ */ new Set();
|
|
159
|
-
for (const item of items) {
|
|
160
|
-
if (!item || typeof item !== "object") continue;
|
|
161
|
-
const verdict = typeof item.verdict === "string" && VALID_VERDICTS.includes(item.verdict) ? item.verdict : "needs-user";
|
|
162
|
-
const pairKeyVal = typeof item.pairKey === "string" && item.pairKey.length > 0 ? item.pairKey : null;
|
|
163
|
-
const input = pairKeyVal ? inputs.find((p) => pairKey(p.memoryIdA, p.memoryIdB) === pairKeyVal) : null;
|
|
164
|
-
if (!input) continue;
|
|
165
|
-
matchedKeys.add(pairKey(input.memoryIdA, input.memoryIdB));
|
|
166
|
-
const confidence = typeof item.confidence === "number" ? Math.min(1, Math.max(0, item.confidence)) : 0.5;
|
|
167
|
-
results.push({
|
|
168
|
-
memoryIdA: input.memoryIdA,
|
|
169
|
-
memoryIdB: input.memoryIdB,
|
|
170
|
-
verdict,
|
|
171
|
-
rationale: typeof item.rationale === "string" ? item.rationale : "No rationale provided",
|
|
172
|
-
confidence
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
for (const inp of inputs) {
|
|
176
|
-
const key = pairKey(inp.memoryIdA, inp.memoryIdB);
|
|
177
|
-
if (!matchedKeys.has(key)) {
|
|
178
|
-
results.push({
|
|
179
|
-
memoryIdA: inp.memoryIdA,
|
|
180
|
-
memoryIdB: inp.memoryIdB,
|
|
181
|
-
verdict: "needs-user",
|
|
182
|
-
rationale: "LLM response omitted this pair",
|
|
183
|
-
confidence: 0
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (results.length > 0) return results;
|
|
188
|
-
} catch {
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return inputs.map((p) => ({
|
|
193
|
-
memoryIdA: p.memoryIdA,
|
|
194
|
-
memoryIdB: p.memoryIdB,
|
|
195
|
-
verdict: "needs-user",
|
|
196
|
-
rationale: "Failed to parse judge response",
|
|
197
|
-
confidence: 0
|
|
198
|
-
}));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// ../remnic-core/src/contradiction/contradiction-scan.ts
|
|
202
|
-
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["active"]);
|
|
203
|
-
var SCAN_CATEGORIES = /* @__PURE__ */ new Set([
|
|
204
|
-
"decision",
|
|
205
|
-
"principle",
|
|
206
|
-
"rule",
|
|
207
|
-
"entity",
|
|
208
|
-
"fact",
|
|
209
|
-
"preference"
|
|
210
|
-
]);
|
|
211
|
-
async function runContradictionScan(deps) {
|
|
212
|
-
const startTime = Date.now();
|
|
213
|
-
const { config, memoryDir, embeddingLookup, embeddingLookupFactory, localLlm, fallbackLlm } = deps;
|
|
214
|
-
const requestedNamespace = normalizeScanNamespace(deps.namespace);
|
|
215
|
-
const scanConfig = config.contradictionScan;
|
|
216
|
-
if (!scanConfig.enabled) {
|
|
217
|
-
log.info("[contradiction-scan] disabled by config");
|
|
218
|
-
return { scanned: 0, candidates: 0, judged: 0, queued: 0, cooledDown: 0, elapsedMs: 0 };
|
|
219
|
-
}
|
|
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);
|
|
229
|
-
log.info("[contradiction-scan] loaded %d eligible memories", memories.length);
|
|
230
|
-
if (memories.length < 2) {
|
|
231
|
-
return { scanned: memories.length, candidates: 0, judged: 0, queued: 0, cooledDown: 0, elapsedMs: Date.now() - startTime };
|
|
232
|
-
}
|
|
233
|
-
const existingPairs = listPairs(memoryDir, { filter: "all", namespace, limit: 1e4 }).pairs;
|
|
234
|
-
const existingMap = /* @__PURE__ */ new Map();
|
|
235
|
-
for (const p of existingPairs) {
|
|
236
|
-
existingMap.set(p.pairId, p);
|
|
237
|
-
}
|
|
238
|
-
const candidates = await generatePairs(memories, existingMap, scanConfig, namespace, scopedEmbeddingLookup);
|
|
239
|
-
const cooledDown = candidates.skipped;
|
|
240
|
-
log.info("[contradiction-scan] generated %d candidates (%d cooled down)", candidates.pairs.length, cooledDown);
|
|
241
|
-
if (candidates.pairs.length === 0) {
|
|
242
|
-
return {
|
|
243
|
-
scanned: memories.length,
|
|
244
|
-
candidates: 0,
|
|
245
|
-
judged: 0,
|
|
246
|
-
queued: 0,
|
|
247
|
-
cooledDown,
|
|
248
|
-
elapsedMs: Date.now() - startTime
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
const judgeInputs = candidates.pairs.map((pair) => ({
|
|
252
|
-
memoryIdA: pair.idA,
|
|
253
|
-
memoryIdB: pair.idB,
|
|
254
|
-
textA: pair.textA,
|
|
255
|
-
textB: pair.textB,
|
|
256
|
-
categoryA: pair.categoryA,
|
|
257
|
-
categoryB: pair.categoryB
|
|
258
|
-
}));
|
|
259
|
-
const scanCache = /* @__PURE__ */ new Map();
|
|
260
|
-
const judgeResult = await judgeContradictionPairs(judgeInputs, config, localLlm, fallbackLlm, scanCache);
|
|
261
|
-
log.info("[contradiction-scan] judge completed: %d judged, %d cached in %dms", judgeResult.judged, judgeResult.cached, judgeResult.elapsed);
|
|
262
|
-
const queueEntries = [];
|
|
263
|
-
const currentMemoryHashes = memoryContentHashMap(memories);
|
|
264
|
-
for (const [key, result] of judgeResult.results) {
|
|
265
|
-
queueEntries.push({
|
|
266
|
-
memoryIds: [result.memoryIdA, result.memoryIdB],
|
|
267
|
-
verdict: result.verdict,
|
|
268
|
-
rationale: result.rationale,
|
|
269
|
-
confidence: result.confidence,
|
|
270
|
-
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
271
|
-
// Set lastReviewedAt for non-actionable verdicts so cooldown prevents re-judging
|
|
272
|
-
lastReviewedAt: result.verdict === "independent" ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
|
|
273
|
-
memoryContentHashes: pairMemoryContentHashes(result.memoryIdA, result.memoryIdB, currentMemoryHashes),
|
|
274
|
-
namespace
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
const written = writePairs(memoryDir, queueEntries, { cooldownDays: scanConfig.cooldownDays });
|
|
278
|
-
const elapsed = Date.now() - startTime;
|
|
279
|
-
log.info("[contradiction-scan] complete: %d queued in %dms", written.length, elapsed);
|
|
280
|
-
return {
|
|
281
|
-
scanned: memories.length,
|
|
282
|
-
candidates: candidates.pairs.length,
|
|
283
|
-
judged: judgeResult.judged,
|
|
284
|
-
queued: written.length,
|
|
285
|
-
cooledDown,
|
|
286
|
-
elapsedMs: elapsed
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
async function generatePairs(memories, existingPairs, scanConfig, namespace, embeddingLookup) {
|
|
290
|
-
const pairs = [];
|
|
291
|
-
let skipped = 0;
|
|
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
|
-
};
|
|
305
|
-
const byEntity = /* @__PURE__ */ new Map();
|
|
306
|
-
for (const mem of orderedMemories) {
|
|
307
|
-
const entity = mem.frontmatter.entityRef;
|
|
308
|
-
if (entity) {
|
|
309
|
-
const existing = byEntity.get(entity) ?? [];
|
|
310
|
-
existing.push(mem);
|
|
311
|
-
byEntity.set(entity, existing);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
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);
|
|
355
|
-
if (seen.has(pairId)) continue;
|
|
356
|
-
seen.add(pairId);
|
|
357
|
-
const existing = existingPairs.get(pairId);
|
|
358
|
-
if (isSkippedByCooldown(existing)) {
|
|
359
|
-
skipped++;
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
pushCandidate({
|
|
363
|
-
idA: a.frontmatter.id,
|
|
364
|
-
idB: b.frontmatter.id,
|
|
365
|
-
textA: a.content,
|
|
366
|
-
textB: b.content,
|
|
367
|
-
categoryA: a.frontmatter.category,
|
|
368
|
-
categoryB: b.frontmatter.category
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
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
|
-
});
|
|
404
|
-
}
|
|
405
|
-
} catch {
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return { pairs, skipped };
|
|
410
|
-
}
|
|
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) {
|
|
463
|
-
let all;
|
|
464
|
-
try {
|
|
465
|
-
all = await storage.readAllMemories();
|
|
466
|
-
} catch {
|
|
467
|
-
return [];
|
|
468
|
-
}
|
|
469
|
-
return all.filter((mem) => {
|
|
470
|
-
const fm = mem.frontmatter;
|
|
471
|
-
const status = fm.status ?? "active";
|
|
472
|
-
if (!ACTIVE_STATUSES.has(status)) return false;
|
|
473
|
-
const category = fm.category;
|
|
474
|
-
if (category && !SCAN_CATEGORIES.has(category)) return false;
|
|
475
|
-
if (!mem.content || mem.content.trim().length === 0) return false;
|
|
476
|
-
if (!fm.id) return false;
|
|
477
|
-
return true;
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
function jaccardOverlap(a, b) {
|
|
481
|
-
if (a.length === 0 && b.length === 0) return 0;
|
|
482
|
-
const setA = new Set(a.map((t) => t.toLowerCase()));
|
|
483
|
-
const setB = new Set(b.map((t) => t.toLowerCase()));
|
|
484
|
-
let intersection = 0;
|
|
485
|
-
for (const item of setA) {
|
|
486
|
-
if (setB.has(item)) intersection++;
|
|
487
|
-
}
|
|
488
|
-
const union = setA.size + setB.size - intersection;
|
|
489
|
-
return union === 0 ? 0 : intersection / union;
|
|
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
|
-
|
|
513
|
-
export {
|
|
514
|
-
ACTIVE_STATUSES,
|
|
515
|
-
runContradictionScan
|
|
516
|
-
};
|