openclaw-cortex-memory 0.1.0-Alpha.3 → 0.1.0-Alpha.31
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/LICENSE +21 -0
- package/README.md +296 -203
- package/SIGNATURE.md +7 -0
- package/SKILL.md +92 -268
- package/dist/index.d.ts +100 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1249 -1252
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +501 -16
- package/dist/src/dedup/three_stage_deduplicator.d.ts +25 -0
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -0
- package/dist/src/dedup/three_stage_deduplicator.js +224 -0
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -0
- package/dist/src/engine/memory_engine.d.ts +6 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +242 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +1468 -52
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +29 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +125 -0
- package/dist/src/graph/ontology.d.ts.map +1 -0
- package/dist/src/graph/ontology.js +1237 -0
- package/dist/src/graph/ontology.js.map +1 -0
- package/dist/src/net/http_post.d.ts +17 -0
- package/dist/src/net/http_post.d.ts.map +1 -0
- package/dist/src/net/http_post.js +56 -0
- package/dist/src/net/http_post.js.map +1 -0
- package/dist/src/quality/llm_output_validator.d.ts +66 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +659 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts +7 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +352 -8
- package/dist/src/reflect/reflector.js.map +1 -1
- package/dist/src/rules/rule_store.d.ts.map +1 -1
- package/dist/src/rules/rule_store.js +75 -16
- package/dist/src/rules/rule_store.js.map +1 -1
- package/dist/src/session/session_end.d.ts +33 -0
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +67 -64
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +136 -0
- package/dist/src/store/archive_store.d.ts.map +1 -0
- package/dist/src/store/archive_store.js +635 -0
- package/dist/src/store/archive_store.js.map +1 -0
- package/dist/src/store/embedding_utils.d.ts +32 -0
- package/dist/src/store/embedding_utils.d.ts.map +1 -0
- package/dist/src/store/embedding_utils.js +173 -0
- package/dist/src/store/embedding_utils.js.map +1 -0
- package/dist/src/store/graph_memory_store.d.ts +114 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +841 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +89 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +2459 -28
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +45 -0
- package/dist/src/store/vector_store.d.ts.map +1 -0
- package/dist/src/store/vector_store.js +202 -0
- package/dist/src/store/vector_store.js.map +1 -0
- package/dist/src/store/write_store.d.ts +54 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +284 -6
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +119 -2
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +2377 -31
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/utils/runtime_env.d.ts +4 -0
- package/dist/src/utils/runtime_env.d.ts.map +1 -0
- package/dist/src/utils/runtime_env.js +51 -0
- package/dist/src/utils/runtime_env.js.map +1 -0
- package/dist/src/wiki/wiki_linter.d.ts +25 -0
- package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
- package/dist/src/wiki/wiki_linter.js +268 -0
- package/dist/src/wiki/wiki_linter.js.map +1 -0
- package/dist/src/wiki/wiki_logger.d.ts +10 -0
- package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
- package/dist/src/wiki/wiki_logger.js +78 -0
- package/dist/src/wiki/wiki_logger.js.map +1 -0
- package/dist/src/wiki/wiki_maintainer.d.ts +36 -0
- package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
- package/dist/src/wiki/wiki_maintainer.js +38 -0
- package/dist/src/wiki/wiki_maintainer.js.map +1 -0
- package/dist/src/wiki/wiki_projector.d.ts +33 -0
- package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
- package/dist/src/wiki/wiki_projector.js +633 -0
- package/dist/src/wiki/wiki_projector.js.map +1 -0
- package/dist/src/wiki/wiki_queue.d.ts +29 -0
- package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
- package/dist/src/wiki/wiki_queue.js +137 -0
- package/dist/src/wiki/wiki_queue.js.map +1 -0
- package/openclaw.plugin.json +501 -16
- package/package.json +58 -7
- package/schema/graph.schema.yaml +330 -0
- package/scripts/cli.js +19 -14
- package/scripts/repair-memory.js +321 -0
- package/scripts/uninstall.js +22 -5
- package/skills/cortex-memory/SKILL.md +49 -0
- package/skills/cortex-memory/references/agent-manual.md +115 -0
- package/skills/cortex-memory/references/configuration.md +92 -0
- package/skills/cortex-memory/references/publish-checklist.md +46 -0
- package/skills/cortex-memory/references/system-prompt-template.md +27 -0
- package/skills/cortex-memory/references/tools.md +181 -0
- package/skills/cortex-memory/scripts/smoke-check.ps1 +56 -0
- package/index.ts +0 -2142
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createGraphMemoryStore = createGraphMemoryStore;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
const ontology_1 = require("../graph/ontology");
|
|
41
|
+
const llm_output_validator_1 = require("../quality/llm_output_validator");
|
|
42
|
+
const wiki_queue_1 = require("../wiki/wiki_queue");
|
|
43
|
+
const wiki_logger_1 = require("../wiki/wiki_logger");
|
|
44
|
+
const wiki_maintainer_1 = require("../wiki/wiki_maintainer");
|
|
45
|
+
const SINGLE_VALUE_RELATION_TYPES = new Set([
|
|
46
|
+
"birthday_on",
|
|
47
|
+
"anniversary_on",
|
|
48
|
+
"has_spouse",
|
|
49
|
+
"lives_in",
|
|
50
|
+
]);
|
|
51
|
+
function ensureDirForFile(filePath) {
|
|
52
|
+
const dir = path.dirname(filePath);
|
|
53
|
+
if (!fs.existsSync(dir)) {
|
|
54
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function generateGraphId() {
|
|
58
|
+
return `gph_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
|
|
59
|
+
}
|
|
60
|
+
function generateConflictId() {
|
|
61
|
+
return `gcf_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
|
|
62
|
+
}
|
|
63
|
+
function relationKey(relation) {
|
|
64
|
+
const source = (relation.source || "").trim().toLowerCase();
|
|
65
|
+
const type = (relation.type || "related_to").trim().toLowerCase();
|
|
66
|
+
const target = (relation.target || "").trim().toLowerCase();
|
|
67
|
+
return `${source}|${type}|${target}`;
|
|
68
|
+
}
|
|
69
|
+
function relationBucketKey(relation) {
|
|
70
|
+
const source = (relation.source || "").trim().toLowerCase();
|
|
71
|
+
const type = (relation.type || "related_to").trim().toLowerCase();
|
|
72
|
+
return `${source}|${type}`;
|
|
73
|
+
}
|
|
74
|
+
function normalizeSummary(value) {
|
|
75
|
+
return String(value || "").trim().replace(/\s+/g, " ").toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
function buildStructureSignature(record) {
|
|
78
|
+
const entities = (record.entities || [])
|
|
79
|
+
.map(item => String(item || "").trim().toLowerCase())
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.sort();
|
|
82
|
+
const relations = (record.relations || [])
|
|
83
|
+
.map(item => {
|
|
84
|
+
const source = String(item.source || "").trim().toLowerCase();
|
|
85
|
+
const type = String(item.type || "").trim().toLowerCase();
|
|
86
|
+
const target = String(item.target || "").trim().toLowerCase();
|
|
87
|
+
const evidence = String(item.evidence_span || "").trim().toLowerCase();
|
|
88
|
+
const context = String(item.context_chunk || "").trim().toLowerCase();
|
|
89
|
+
return `${source}|${type}|${target}|${evidence}|${context}`;
|
|
90
|
+
})
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
.sort();
|
|
93
|
+
return `${entities.join("||")}#${relations.join("||")}`;
|
|
94
|
+
}
|
|
95
|
+
function buildFallbackSummary(entities, relations) {
|
|
96
|
+
const entityText = (entities || []).map(item => String(item || "").trim()).filter(Boolean).join(", ");
|
|
97
|
+
const relationText = (relations || [])
|
|
98
|
+
.slice(0, 3)
|
|
99
|
+
.map(item => `${item.source} ${item.type} ${item.target}`)
|
|
100
|
+
.join("; ");
|
|
101
|
+
return `Graph memory update covering entities [${entityText || "n/a"}] with relations [${relationText || "n/a"}].`;
|
|
102
|
+
}
|
|
103
|
+
function toConflictSummary(record) {
|
|
104
|
+
return {
|
|
105
|
+
conflict_id: record.conflict_id,
|
|
106
|
+
status: record.status,
|
|
107
|
+
created_at: record.created_at,
|
|
108
|
+
updated_at: record.updated_at,
|
|
109
|
+
source_event_id: record.source_event_id,
|
|
110
|
+
session_id: record.session_id,
|
|
111
|
+
conflict_reason: record.conflict_reason,
|
|
112
|
+
conflict_types: record.conflict_types,
|
|
113
|
+
existing_relation_keys: record.existing_relation_keys,
|
|
114
|
+
candidate_relations: (record.candidate.relations || []).map(rel => ({
|
|
115
|
+
source: rel.source,
|
|
116
|
+
type: rel.type,
|
|
117
|
+
target: rel.target,
|
|
118
|
+
})),
|
|
119
|
+
resolution: record.resolution,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function createGraphMemoryStore(options) {
|
|
123
|
+
const graphMemoryPath = path.join(options.memoryRoot, "graph", "memory.jsonl");
|
|
124
|
+
const mutationLogPath = path.join(options.memoryRoot, "graph", "mutation_log.jsonl");
|
|
125
|
+
const conflictQueuePath = path.join(options.memoryRoot, "graph", "conflict_queue.jsonl");
|
|
126
|
+
const supersededRelationPath = path.join(options.memoryRoot, "graph", "superseded_relations.jsonl");
|
|
127
|
+
const graphSchema = (0, ontology_1.loadGraphSchema)(options.projectRoot);
|
|
128
|
+
const wikiProjectionEnabled = options.wikiProjection?.enabled === true && options.wikiProjection?.mode !== "off";
|
|
129
|
+
const wikiProjectionMaxBatch = typeof options.wikiProjection?.maxBatch === "number" && Number.isFinite(options.wikiProjection.maxBatch)
|
|
130
|
+
? Math.max(1, Math.min(1000, Math.floor(options.wikiProjection.maxBatch)))
|
|
131
|
+
: 100;
|
|
132
|
+
function triggerWikiMaintenance(input) {
|
|
133
|
+
if (!wikiProjectionEnabled) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
(0, wiki_queue_1.appendWikiRebuildEvent)({
|
|
138
|
+
memoryRoot: options.memoryRoot,
|
|
139
|
+
type: input.type,
|
|
140
|
+
source_event_id: input.sourceEventId,
|
|
141
|
+
conflict_id: input.conflictId,
|
|
142
|
+
entities: input.entities,
|
|
143
|
+
relation_types: input.relationTypes,
|
|
144
|
+
});
|
|
145
|
+
(0, wiki_logger_1.appendWikiLog)({
|
|
146
|
+
memoryRoot: options.memoryRoot,
|
|
147
|
+
type: input.type,
|
|
148
|
+
source_event_id: input.sourceEventId,
|
|
149
|
+
conflict_id: input.conflictId,
|
|
150
|
+
message: input.message,
|
|
151
|
+
});
|
|
152
|
+
const graphView = exportGraphView();
|
|
153
|
+
(0, wiki_maintainer_1.maintainWikiProjection)({
|
|
154
|
+
memoryRoot: options.memoryRoot,
|
|
155
|
+
graphView,
|
|
156
|
+
maxBatch: wikiProjectionMaxBatch,
|
|
157
|
+
logger: options.logger,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
options.logger.warn(`wiki_projection_maintenance_failed reason=${String(error)}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function loadSupersededRelationEntries() {
|
|
165
|
+
if (!fs.existsSync(supersededRelationPath)) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
const lines = fs.readFileSync(supersededRelationPath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
169
|
+
const entries = [];
|
|
170
|
+
for (const line of lines) {
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(line);
|
|
173
|
+
const relationKeyRaw = typeof parsed.relation_key === "string" ? parsed.relation_key.trim().toLowerCase() : "";
|
|
174
|
+
if (!relationKeyRaw) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
entries.push({
|
|
178
|
+
relation_key: relationKeyRaw,
|
|
179
|
+
superseded_at: typeof parsed.superseded_at === "string" ? parsed.superseded_at : "",
|
|
180
|
+
conflict_id: typeof parsed.conflict_id === "string" ? parsed.conflict_id : undefined,
|
|
181
|
+
note: typeof parsed.note === "string" ? parsed.note : undefined,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
options.logger.warn(`graph_superseded_parse_error line=${line.slice(0, 120)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return entries;
|
|
189
|
+
}
|
|
190
|
+
function loadSupersededRelationKeys() {
|
|
191
|
+
const keys = new Set();
|
|
192
|
+
for (const entry of loadSupersededRelationEntries()) {
|
|
193
|
+
if (entry.relation_key) {
|
|
194
|
+
keys.add(entry.relation_key);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return keys;
|
|
198
|
+
}
|
|
199
|
+
function appendSupersededRelationEntries(entries) {
|
|
200
|
+
if (entries.length === 0) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
ensureDirForFile(supersededRelationPath);
|
|
204
|
+
const lines = entries.map(item => JSON.stringify(item));
|
|
205
|
+
fs.appendFileSync(supersededRelationPath, `${lines.join("\n")}\n`, "utf-8");
|
|
206
|
+
}
|
|
207
|
+
function filterRecordBySuperseded(record, supersededKeys) {
|
|
208
|
+
const relations = Array.isArray(record.relations)
|
|
209
|
+
? record.relations.filter(rel => !supersededKeys.has(relationKey(rel)))
|
|
210
|
+
: [];
|
|
211
|
+
if (relations.length === 0) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
...record,
|
|
216
|
+
relations,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function loadAllRaw() {
|
|
220
|
+
if (!fs.existsSync(graphMemoryPath)) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
const content = fs.readFileSync(graphMemoryPath, "utf-8");
|
|
224
|
+
const lines = content.split(/\r?\n/).filter(Boolean);
|
|
225
|
+
const records = [];
|
|
226
|
+
for (const line of lines) {
|
|
227
|
+
try {
|
|
228
|
+
const parsed = JSON.parse(line);
|
|
229
|
+
if (parsed && parsed.id && (parsed.source_event_id || parsed.archive_event_id)) {
|
|
230
|
+
if (!parsed.source_event_id && parsed.archive_event_id) {
|
|
231
|
+
parsed.source_event_id = parsed.archive_event_id;
|
|
232
|
+
parsed.source_layer = "archive_event";
|
|
233
|
+
}
|
|
234
|
+
records.push(parsed);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
options.logger.warn(`graph_memory_parse_error line=${line.slice(0, 100)}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return records;
|
|
242
|
+
}
|
|
243
|
+
function loadAllEffective() {
|
|
244
|
+
const supersededKeys = loadSupersededRelationKeys();
|
|
245
|
+
const raw = loadAllRaw();
|
|
246
|
+
const output = [];
|
|
247
|
+
for (const record of raw) {
|
|
248
|
+
const filtered = filterRecordBySuperseded(record, supersededKeys);
|
|
249
|
+
if (filtered) {
|
|
250
|
+
output.push(filtered);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return output;
|
|
254
|
+
}
|
|
255
|
+
function shouldRejectStaleSummary(record) {
|
|
256
|
+
const sameSourceRecords = loadAllEffective()
|
|
257
|
+
.filter(item => item.source_event_id === record.source_event_id)
|
|
258
|
+
.sort((a, b) => Date.parse(b.timestamp || "") - Date.parse(a.timestamp || ""));
|
|
259
|
+
if (sameSourceRecords.length === 0) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const latest = sameSourceRecords[0];
|
|
263
|
+
const previousSummary = normalizeSummary(latest.summary);
|
|
264
|
+
const nextSummary = normalizeSummary(record.summary);
|
|
265
|
+
if (!previousSummary || !nextSummary || previousSummary !== nextSummary) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
const previousSignature = buildStructureSignature(latest);
|
|
269
|
+
const nextSignature = buildStructureSignature(record);
|
|
270
|
+
return previousSignature !== nextSignature;
|
|
271
|
+
}
|
|
272
|
+
function loadConflictQueue() {
|
|
273
|
+
if (!fs.existsSync(conflictQueuePath)) {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
const lines = fs.readFileSync(conflictQueuePath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
277
|
+
const records = [];
|
|
278
|
+
for (const line of lines) {
|
|
279
|
+
try {
|
|
280
|
+
const parsed = JSON.parse(line);
|
|
281
|
+
if (parsed && typeof parsed.conflict_id === "string" && parsed.conflict_id.trim()) {
|
|
282
|
+
records.push(parsed);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
options.logger.warn(`graph_conflict_parse_error line=${line.slice(0, 120)}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return records;
|
|
290
|
+
}
|
|
291
|
+
function saveConflictQueue(records) {
|
|
292
|
+
ensureDirForFile(conflictQueuePath);
|
|
293
|
+
const lines = records.map(item => JSON.stringify(item));
|
|
294
|
+
fs.writeFileSync(conflictQueuePath, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`, "utf-8");
|
|
295
|
+
}
|
|
296
|
+
function appendGraphRecord(args) {
|
|
297
|
+
const line = JSON.stringify(args.record);
|
|
298
|
+
const qualityCheck = (0, llm_output_validator_1.validateGraphJsonlLine)(line);
|
|
299
|
+
if (!qualityCheck.valid) {
|
|
300
|
+
throw new Error(`graph_record_quality_invalid:${qualityCheck.errors.join("|")}`);
|
|
301
|
+
}
|
|
302
|
+
if (qualityCheck.warnings.length > 0) {
|
|
303
|
+
options.logger.warn(`graph_quality_warning source_event_id=${args.record.source_event_id} warnings=${qualityCheck.warnings.join("|")}`);
|
|
304
|
+
}
|
|
305
|
+
const mutationEntry = {
|
|
306
|
+
op: args.conflictId ? "insert_graph_conflict_resolved" : "insert_graph",
|
|
307
|
+
id: args.record.id,
|
|
308
|
+
source_event_id: args.record.source_event_id,
|
|
309
|
+
source_layer: args.record.source_layer,
|
|
310
|
+
archive_event_id: args.record.archive_event_id,
|
|
311
|
+
timestamp: args.record.timestamp,
|
|
312
|
+
session_id: args.record.session_id,
|
|
313
|
+
gate_source: args.record.gate_source,
|
|
314
|
+
entity_count: args.record.entities.length,
|
|
315
|
+
relation_count: args.record.relations.length,
|
|
316
|
+
conflict_id: args.conflictId,
|
|
317
|
+
note: args.note,
|
|
318
|
+
};
|
|
319
|
+
ensureDirForFile(graphMemoryPath);
|
|
320
|
+
ensureDirForFile(mutationLogPath);
|
|
321
|
+
fs.appendFileSync(graphMemoryPath, `${line}\n`, "utf-8");
|
|
322
|
+
fs.appendFileSync(mutationLogPath, `${JSON.stringify(mutationEntry)}\n`, "utf-8");
|
|
323
|
+
}
|
|
324
|
+
function appendSupersedeMutationLogs(entries, conflictId, sourceEventId) {
|
|
325
|
+
if (entries.length === 0) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
ensureDirForFile(mutationLogPath);
|
|
329
|
+
const nowIso = new Date().toISOString();
|
|
330
|
+
const lines = entries.map(item => JSON.stringify({
|
|
331
|
+
op: "supersede_relation",
|
|
332
|
+
id: `sup_${crypto.createHash("sha1").update(`${item.relation_key}|${item.superseded_at}`).digest("hex").slice(0, 12)}`,
|
|
333
|
+
source_event_id: sourceEventId || "conflict_resolution",
|
|
334
|
+
source_layer: "active_only",
|
|
335
|
+
timestamp: nowIso,
|
|
336
|
+
session_id: "system",
|
|
337
|
+
gate_source: "manual",
|
|
338
|
+
entity_count: 0,
|
|
339
|
+
relation_count: 0,
|
|
340
|
+
relation_key: item.relation_key,
|
|
341
|
+
conflict_id: conflictId,
|
|
342
|
+
note: item.note,
|
|
343
|
+
}));
|
|
344
|
+
fs.appendFileSync(mutationLogPath, `${lines.join("\n")}\n`, "utf-8");
|
|
345
|
+
}
|
|
346
|
+
function detectConflicts(candidate) {
|
|
347
|
+
const existing = loadAllEffective();
|
|
348
|
+
const candidateSingleValued = (candidate.relations || []).filter(rel => SINGLE_VALUE_RELATION_TYPES.has((rel.type || "").trim().toLowerCase()));
|
|
349
|
+
if (candidateSingleValued.length === 0) {
|
|
350
|
+
return {
|
|
351
|
+
hasConflict: false,
|
|
352
|
+
reason: "",
|
|
353
|
+
conflictTypes: [],
|
|
354
|
+
existingRelations: [],
|
|
355
|
+
existingRelationKeys: [],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const existingByBucket = new Map();
|
|
359
|
+
for (const record of existing) {
|
|
360
|
+
for (const rel of record.relations || []) {
|
|
361
|
+
const type = (rel.type || "").trim().toLowerCase();
|
|
362
|
+
if (!SINGLE_VALUE_RELATION_TYPES.has(type)) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
const key = relationKey(rel);
|
|
366
|
+
const bucket = relationBucketKey(rel);
|
|
367
|
+
if (!existingByBucket.has(bucket)) {
|
|
368
|
+
existingByBucket.set(bucket, []);
|
|
369
|
+
}
|
|
370
|
+
existingByBucket.get(bucket)?.push({ key, relation: rel });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const conflictTypes = new Set();
|
|
374
|
+
const existingRelations = [];
|
|
375
|
+
const existingRelationKeySet = new Set();
|
|
376
|
+
for (const rel of candidateSingleValued) {
|
|
377
|
+
const candidateKey = relationKey(rel);
|
|
378
|
+
const bucket = relationBucketKey(rel);
|
|
379
|
+
const sameBucket = existingByBucket.get(bucket) || [];
|
|
380
|
+
for (const existingItem of sameBucket) {
|
|
381
|
+
if (existingItem.key === candidateKey) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
existingRelationKeySet.add(existingItem.key);
|
|
385
|
+
existingRelations.push(existingItem);
|
|
386
|
+
conflictTypes.add((rel.type || "").trim().toLowerCase());
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const existingRelationKeys = [...existingRelationKeySet].sort();
|
|
390
|
+
if (existingRelationKeys.length === 0) {
|
|
391
|
+
return {
|
|
392
|
+
hasConflict: false,
|
|
393
|
+
reason: "",
|
|
394
|
+
conflictTypes: [],
|
|
395
|
+
existingRelations: [],
|
|
396
|
+
existingRelationKeys: [],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
hasConflict: true,
|
|
401
|
+
reason: `single_value_relation_conflict:${[...conflictTypes].sort().join(",")}`,
|
|
402
|
+
conflictTypes: [...conflictTypes].sort(),
|
|
403
|
+
existingRelations,
|
|
404
|
+
existingRelationKeys,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function buildConflictFingerprint(input) {
|
|
408
|
+
const payload = JSON.stringify({
|
|
409
|
+
source_event_id: input.sourceEventId,
|
|
410
|
+
conflict_types: [...input.conflictTypes].sort(),
|
|
411
|
+
existing_relation_keys: [...input.existingRelationKeys].sort(),
|
|
412
|
+
candidate_relation_keys: input.candidateRelations.map(relationKey).sort(),
|
|
413
|
+
});
|
|
414
|
+
return crypto.createHash("sha1").update(payload).digest("hex");
|
|
415
|
+
}
|
|
416
|
+
options.logger.info(`Graph memory store initialized at ${graphMemoryPath}`);
|
|
417
|
+
async function append(input) {
|
|
418
|
+
const validation = (0, ontology_1.validateGraphPayload)({
|
|
419
|
+
sourceEventId: input.sourceEventId,
|
|
420
|
+
sourceLayer: input.sourceLayer,
|
|
421
|
+
archiveEventId: input.archiveEventId,
|
|
422
|
+
sessionId: input.sessionId,
|
|
423
|
+
sourceFile: input.sourceFile,
|
|
424
|
+
source_text_nav: input.source_text_nav,
|
|
425
|
+
summary: input.summary,
|
|
426
|
+
eventType: input.eventType,
|
|
427
|
+
entities: input.entities,
|
|
428
|
+
entity_types: input.entity_types,
|
|
429
|
+
relations: input.relations,
|
|
430
|
+
gateSource: input.gateSource,
|
|
431
|
+
confidence: input.confidence,
|
|
432
|
+
schema: graphSchema,
|
|
433
|
+
sourceText: input.sourceText,
|
|
434
|
+
qualityMode: options.qualityMode || "warn",
|
|
435
|
+
});
|
|
436
|
+
if (!validation.valid) {
|
|
437
|
+
options.logger.info(`graph_skip_reason=${validation.reason} source_event_id=${input.sourceEventId}`);
|
|
438
|
+
return { success: false, reason: validation.reason };
|
|
439
|
+
}
|
|
440
|
+
if (Array.isArray(validation.warnings) && validation.warnings.length > 0) {
|
|
441
|
+
options.logger.warn(`graph_quality_warning source_event_id=${input.sourceEventId} warnings=${validation.warnings.join("|")}`);
|
|
442
|
+
}
|
|
443
|
+
const record = validation.normalized;
|
|
444
|
+
record.id = generateGraphId();
|
|
445
|
+
record.timestamp = new Date().toISOString();
|
|
446
|
+
if (shouldRejectStaleSummary(record)) {
|
|
447
|
+
options.logger.info(`graph_skip_reason=stale_summary_after_update source_event_id=${record.source_event_id}`);
|
|
448
|
+
return { success: false, reason: "stale_summary_after_update" };
|
|
449
|
+
}
|
|
450
|
+
const conflictDetection = detectConflicts(record);
|
|
451
|
+
if (conflictDetection.hasConflict) {
|
|
452
|
+
const queue = loadConflictQueue();
|
|
453
|
+
const fingerprint = buildConflictFingerprint({
|
|
454
|
+
sourceEventId: record.source_event_id,
|
|
455
|
+
conflictTypes: conflictDetection.conflictTypes,
|
|
456
|
+
existingRelationKeys: conflictDetection.existingRelationKeys,
|
|
457
|
+
candidateRelations: record.relations,
|
|
458
|
+
});
|
|
459
|
+
const existingPending = queue.find(item => item.status === "pending" && item.fingerprint === fingerprint);
|
|
460
|
+
if (existingPending) {
|
|
461
|
+
options.logger.info(`graph_conflict_pending_reuse conflict_id=${existingPending.conflict_id} source_event_id=${record.source_event_id}`);
|
|
462
|
+
return { success: false, reason: `graph_conflict_pending:${existingPending.conflict_id}` };
|
|
463
|
+
}
|
|
464
|
+
const now = new Date().toISOString();
|
|
465
|
+
const conflictRecord = {
|
|
466
|
+
conflict_id: generateConflictId(),
|
|
467
|
+
status: "pending",
|
|
468
|
+
created_at: now,
|
|
469
|
+
updated_at: now,
|
|
470
|
+
source_event_id: record.source_event_id,
|
|
471
|
+
source_layer: record.source_layer,
|
|
472
|
+
session_id: record.session_id,
|
|
473
|
+
source_file: record.source_file,
|
|
474
|
+
conflict_reason: conflictDetection.reason,
|
|
475
|
+
conflict_types: conflictDetection.conflictTypes,
|
|
476
|
+
existing_relation_keys: conflictDetection.existingRelationKeys,
|
|
477
|
+
existing_relations: conflictDetection.existingRelations,
|
|
478
|
+
candidate: {
|
|
479
|
+
summary: record.summary,
|
|
480
|
+
source_text_nav: record.source_text_nav,
|
|
481
|
+
entities: record.entities,
|
|
482
|
+
entity_types: record.entity_types,
|
|
483
|
+
relations: record.relations,
|
|
484
|
+
event_type: record.event_type,
|
|
485
|
+
gate_source: record.gate_source,
|
|
486
|
+
confidence: record.confidence,
|
|
487
|
+
source_text: input.sourceText,
|
|
488
|
+
},
|
|
489
|
+
fingerprint,
|
|
490
|
+
};
|
|
491
|
+
queue.push(conflictRecord);
|
|
492
|
+
saveConflictQueue(queue);
|
|
493
|
+
options.logger.info(`graph_conflict_pending id=${conflictRecord.conflict_id} source_event_id=${record.source_event_id} types=${conflictRecord.conflict_types.join(",")}`);
|
|
494
|
+
triggerWikiMaintenance({
|
|
495
|
+
type: "conflict_pending",
|
|
496
|
+
message: "Conflict pending and awaiting confirmation",
|
|
497
|
+
sourceEventId: record.source_event_id,
|
|
498
|
+
conflictId: conflictRecord.conflict_id,
|
|
499
|
+
entities: record.entities,
|
|
500
|
+
relationTypes: (record.relations || []).map(item => item.type),
|
|
501
|
+
});
|
|
502
|
+
return { success: false, reason: `graph_conflict_pending:${conflictRecord.conflict_id}` };
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
appendGraphRecord({ record });
|
|
506
|
+
options.logger.info(`graph_write id=${record.id} source_event_id=${record.source_event_id} source_layer=${record.source_layer} entities=${record.entities.length} relations=${record.relations.length} gate_source=${record.gate_source}`);
|
|
507
|
+
triggerWikiMaintenance({
|
|
508
|
+
type: "graph_append",
|
|
509
|
+
message: "Graph relation appended",
|
|
510
|
+
sourceEventId: record.source_event_id,
|
|
511
|
+
entities: record.entities,
|
|
512
|
+
relationTypes: (record.relations || []).map(item => item.type),
|
|
513
|
+
});
|
|
514
|
+
return { success: true, record };
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
const reason = String(error);
|
|
518
|
+
options.logger.warn(`graph_skip_reason=${reason} source_event_id=${input.sourceEventId}`);
|
|
519
|
+
return { success: false, reason };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function loadAll() {
|
|
523
|
+
return loadAllEffective();
|
|
524
|
+
}
|
|
525
|
+
function loadByArchiveEventId(archiveEventId) {
|
|
526
|
+
return loadAll().filter(record => record.archive_event_id === archiveEventId || record.source_event_id === archiveEventId);
|
|
527
|
+
}
|
|
528
|
+
function loadByEntity(entityName) {
|
|
529
|
+
const normalized = entityName.trim();
|
|
530
|
+
const queryKeySet = new Set((0, ontology_1.getEntityMatchKeys)(normalized, graphSchema));
|
|
531
|
+
if (queryKeySet.size === 0) {
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
534
|
+
return loadAll().filter(record => record.entities.some(entity => {
|
|
535
|
+
const entityKeys = (0, ontology_1.getEntityMatchKeys)(entity, graphSchema);
|
|
536
|
+
return entityKeys.some(key => queryKeySet.has(key));
|
|
537
|
+
}));
|
|
538
|
+
}
|
|
539
|
+
function getStats() {
|
|
540
|
+
const all = loadAll();
|
|
541
|
+
const entitySet = new Set();
|
|
542
|
+
let totalRelations = 0;
|
|
543
|
+
for (const record of all) {
|
|
544
|
+
for (const entity of record.entities) {
|
|
545
|
+
entitySet.add(entity.trim().toLowerCase());
|
|
546
|
+
}
|
|
547
|
+
totalRelations += record.relations.length;
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
totalRecords: all.length,
|
|
551
|
+
totalEntities: entitySet.size,
|
|
552
|
+
totalRelations,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function parseRelationKey(key) {
|
|
556
|
+
const parts = key.split("|");
|
|
557
|
+
return {
|
|
558
|
+
source: parts[0] || "",
|
|
559
|
+
type: parts[1] || "related_to",
|
|
560
|
+
target: parts.slice(2).join("|") || "",
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function exportGraphView() {
|
|
564
|
+
const rawRecords = loadAllRaw();
|
|
565
|
+
const activeRecords = loadAllEffective();
|
|
566
|
+
const conflictQueue = loadConflictQueue();
|
|
567
|
+
const supersededEntries = loadSupersededRelationEntries();
|
|
568
|
+
const nodes = new Map();
|
|
569
|
+
const edges = [];
|
|
570
|
+
const dedupe = new Set();
|
|
571
|
+
let latestTimestampMs = 0;
|
|
572
|
+
function updateLatest(iso) {
|
|
573
|
+
const ms = Date.parse(iso || "");
|
|
574
|
+
if (Number.isFinite(ms) && ms > latestTimestampMs) {
|
|
575
|
+
latestTimestampMs = ms;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function addNode(id) {
|
|
579
|
+
const trimmed = id.trim();
|
|
580
|
+
if (!trimmed) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const key = trimmed.toLowerCase();
|
|
584
|
+
if (!nodes.has(key)) {
|
|
585
|
+
nodes.set(key, { id: trimmed, type: "entity" });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function addEdge(input) {
|
|
589
|
+
const source = input.source.trim();
|
|
590
|
+
const target = input.target.trim();
|
|
591
|
+
const type = (input.type || "related_to").trim().toLowerCase();
|
|
592
|
+
if (!source || !target || !type) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const relation_key = `${source.toLowerCase()}|${type}|${target.toLowerCase()}`;
|
|
596
|
+
const dedupeKey = `${relation_key}|${input.status}|${(input.conflict_id || "").trim().toLowerCase()}`;
|
|
597
|
+
if (dedupe.has(dedupeKey)) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
dedupe.add(dedupeKey);
|
|
601
|
+
addNode(source);
|
|
602
|
+
addNode(target);
|
|
603
|
+
edges.push({
|
|
604
|
+
source,
|
|
605
|
+
target,
|
|
606
|
+
type,
|
|
607
|
+
status: input.status,
|
|
608
|
+
relation_key,
|
|
609
|
+
source_event_id: input.source_event_id,
|
|
610
|
+
evidence_span: input.evidence_span,
|
|
611
|
+
confidence: input.confidence,
|
|
612
|
+
conflict_id: input.conflict_id,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
const relationMetaByKey = new Map();
|
|
616
|
+
for (const record of rawRecords) {
|
|
617
|
+
updateLatest(record.timestamp);
|
|
618
|
+
for (const rel of record.relations || []) {
|
|
619
|
+
const key = relationKey(rel);
|
|
620
|
+
const prev = relationMetaByKey.get(key);
|
|
621
|
+
if (!prev || Date.parse(record.timestamp || "") >= Date.parse(prev.timestamp || "")) {
|
|
622
|
+
relationMetaByKey.set(key, {
|
|
623
|
+
source_event_id: record.source_event_id,
|
|
624
|
+
evidence_span: rel.evidence_span,
|
|
625
|
+
confidence: rel.confidence,
|
|
626
|
+
timestamp: record.timestamp,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
for (const record of activeRecords) {
|
|
632
|
+
updateLatest(record.timestamp);
|
|
633
|
+
for (const rel of record.relations || []) {
|
|
634
|
+
addEdge({
|
|
635
|
+
source: rel.source,
|
|
636
|
+
target: rel.target,
|
|
637
|
+
type: rel.type,
|
|
638
|
+
status: "active",
|
|
639
|
+
source_event_id: record.source_event_id,
|
|
640
|
+
evidence_span: rel.evidence_span,
|
|
641
|
+
confidence: rel.confidence,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
for (const entry of supersededEntries) {
|
|
646
|
+
updateLatest(entry.superseded_at);
|
|
647
|
+
const parsed = parseRelationKey(entry.relation_key);
|
|
648
|
+
const meta = relationMetaByKey.get(entry.relation_key);
|
|
649
|
+
addEdge({
|
|
650
|
+
source: parsed.source,
|
|
651
|
+
target: parsed.target,
|
|
652
|
+
type: parsed.type,
|
|
653
|
+
status: "superseded",
|
|
654
|
+
source_event_id: meta?.source_event_id,
|
|
655
|
+
evidence_span: meta?.evidence_span,
|
|
656
|
+
confidence: meta?.confidence,
|
|
657
|
+
conflict_id: entry.conflict_id,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
for (const item of conflictQueue) {
|
|
661
|
+
updateLatest(item.updated_at);
|
|
662
|
+
if (item.status !== "pending" && item.status !== "rejected") {
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const status = item.status === "pending" ? "pending_conflict" : "rejected";
|
|
666
|
+
for (const rel of item.candidate.relations || []) {
|
|
667
|
+
addEdge({
|
|
668
|
+
source: rel.source,
|
|
669
|
+
target: rel.target,
|
|
670
|
+
type: rel.type,
|
|
671
|
+
status,
|
|
672
|
+
source_event_id: item.source_event_id,
|
|
673
|
+
evidence_span: rel.evidence_span,
|
|
674
|
+
confidence: rel.confidence,
|
|
675
|
+
conflict_id: item.conflict_id,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
const status_counts = {
|
|
680
|
+
active: 0,
|
|
681
|
+
pending_conflict: 0,
|
|
682
|
+
superseded: 0,
|
|
683
|
+
rejected: 0,
|
|
684
|
+
};
|
|
685
|
+
for (const edge of edges) {
|
|
686
|
+
status_counts[edge.status] += 1;
|
|
687
|
+
}
|
|
688
|
+
edges.sort((a, b) => {
|
|
689
|
+
const left = `${a.status}|${a.source.toLowerCase()}|${a.type}|${a.target.toLowerCase()}|${a.conflict_id || ""}`;
|
|
690
|
+
const right = `${b.status}|${b.source.toLowerCase()}|${b.type}|${b.target.toLowerCase()}|${b.conflict_id || ""}`;
|
|
691
|
+
return left.localeCompare(right);
|
|
692
|
+
});
|
|
693
|
+
const nodeList = [...nodes.values()].sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
|
|
694
|
+
return {
|
|
695
|
+
nodes: nodeList,
|
|
696
|
+
edges,
|
|
697
|
+
status_counts,
|
|
698
|
+
updated_at: latestTimestampMs > 0 ? new Date(latestTimestampMs).toISOString() : new Date().toISOString(),
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
function listConflicts(args) {
|
|
702
|
+
const status = args?.status || "pending";
|
|
703
|
+
const limit = typeof args?.limit === "number" && Number.isFinite(args.limit) && args.limit > 0
|
|
704
|
+
? Math.min(500, Math.floor(args.limit))
|
|
705
|
+
: 50;
|
|
706
|
+
const queue = loadConflictQueue();
|
|
707
|
+
const filtered = queue
|
|
708
|
+
.filter(item => status === "all" ? true : item.status === status)
|
|
709
|
+
.sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
|
|
710
|
+
return filtered.slice(0, limit).map(toConflictSummary);
|
|
711
|
+
}
|
|
712
|
+
function getConflictStats() {
|
|
713
|
+
const queue = loadConflictQueue();
|
|
714
|
+
let pending = 0;
|
|
715
|
+
let accepted = 0;
|
|
716
|
+
let rejected = 0;
|
|
717
|
+
for (const item of queue) {
|
|
718
|
+
if (item.status === "pending")
|
|
719
|
+
pending += 1;
|
|
720
|
+
if (item.status === "accepted")
|
|
721
|
+
accepted += 1;
|
|
722
|
+
if (item.status === "rejected")
|
|
723
|
+
rejected += 1;
|
|
724
|
+
}
|
|
725
|
+
return { pending, accepted, rejected };
|
|
726
|
+
}
|
|
727
|
+
async function resolveConflict(args) {
|
|
728
|
+
const conflictId = (args.conflictId || "").trim();
|
|
729
|
+
if (!conflictId) {
|
|
730
|
+
return { success: false, reason: "conflict_id_empty" };
|
|
731
|
+
}
|
|
732
|
+
const queue = loadConflictQueue();
|
|
733
|
+
const index = queue.findIndex(item => item.conflict_id === conflictId);
|
|
734
|
+
if (index < 0) {
|
|
735
|
+
return { success: false, reason: "conflict_not_found" };
|
|
736
|
+
}
|
|
737
|
+
const current = queue[index];
|
|
738
|
+
if (current.status !== "pending") {
|
|
739
|
+
return { success: false, reason: `conflict_already_${current.status}` };
|
|
740
|
+
}
|
|
741
|
+
const now = new Date().toISOString();
|
|
742
|
+
if (args.action === "reject") {
|
|
743
|
+
current.status = "rejected";
|
|
744
|
+
current.updated_at = now;
|
|
745
|
+
current.resolution = {
|
|
746
|
+
action: "reject",
|
|
747
|
+
note: args.note,
|
|
748
|
+
resolved_at: now,
|
|
749
|
+
};
|
|
750
|
+
queue[index] = current;
|
|
751
|
+
saveConflictQueue(queue);
|
|
752
|
+
options.logger.info(`graph_conflict_rejected id=${conflictId}`);
|
|
753
|
+
triggerWikiMaintenance({
|
|
754
|
+
type: "conflict_rejected",
|
|
755
|
+
message: "Conflict rejected; canonical graph unchanged",
|
|
756
|
+
sourceEventId: current.source_event_id,
|
|
757
|
+
conflictId,
|
|
758
|
+
entities: current.candidate.entities,
|
|
759
|
+
relationTypes: (current.candidate.relations || []).map(item => item.type),
|
|
760
|
+
});
|
|
761
|
+
return { success: true };
|
|
762
|
+
}
|
|
763
|
+
const supersedeEntries = current.existing_relation_keys.map(key => ({
|
|
764
|
+
relation_key: key,
|
|
765
|
+
superseded_at: now,
|
|
766
|
+
conflict_id: conflictId,
|
|
767
|
+
note: args.note || "conflict_accept_replace",
|
|
768
|
+
}));
|
|
769
|
+
appendSupersededRelationEntries(supersedeEntries);
|
|
770
|
+
appendSupersedeMutationLogs(supersedeEntries, conflictId, current.source_event_id);
|
|
771
|
+
const record = {
|
|
772
|
+
id: generateGraphId(),
|
|
773
|
+
summary: (current.candidate.summary || "").trim() || buildFallbackSummary(current.candidate.entities, current.candidate.relations),
|
|
774
|
+
source_text_nav: current.candidate.source_text_nav || {
|
|
775
|
+
layer: current.source_layer,
|
|
776
|
+
session_id: current.session_id,
|
|
777
|
+
source_file: current.source_file || "unknown",
|
|
778
|
+
source_memory_id: current.source_event_id,
|
|
779
|
+
source_event_id: current.source_event_id,
|
|
780
|
+
},
|
|
781
|
+
source_event_id: current.source_event_id,
|
|
782
|
+
source_layer: current.source_layer,
|
|
783
|
+
archive_event_id: current.source_layer === "archive_event" ? current.source_event_id : undefined,
|
|
784
|
+
session_id: current.session_id,
|
|
785
|
+
source_file: current.source_file,
|
|
786
|
+
timestamp: now,
|
|
787
|
+
entities: current.candidate.entities,
|
|
788
|
+
entity_types: current.candidate.entity_types,
|
|
789
|
+
relations: current.candidate.relations,
|
|
790
|
+
gate_source: current.candidate.gate_source,
|
|
791
|
+
event_type: current.candidate.event_type,
|
|
792
|
+
schema_version: "1.0.0",
|
|
793
|
+
confidence: current.candidate.confidence,
|
|
794
|
+
};
|
|
795
|
+
if (shouldRejectStaleSummary(record)) {
|
|
796
|
+
return { success: false, reason: "stale_summary_after_update" };
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
appendGraphRecord({
|
|
800
|
+
record,
|
|
801
|
+
conflictId,
|
|
802
|
+
note: args.note,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
catch (error) {
|
|
806
|
+
return { success: false, reason: String(error) };
|
|
807
|
+
}
|
|
808
|
+
current.status = "accepted";
|
|
809
|
+
current.updated_at = now;
|
|
810
|
+
current.resolution = {
|
|
811
|
+
action: "accept",
|
|
812
|
+
note: args.note,
|
|
813
|
+
resolved_at: now,
|
|
814
|
+
applied_record_id: record.id,
|
|
815
|
+
};
|
|
816
|
+
queue[index] = current;
|
|
817
|
+
saveConflictQueue(queue);
|
|
818
|
+
options.logger.info(`graph_conflict_accepted id=${conflictId} record_id=${record.id}`);
|
|
819
|
+
triggerWikiMaintenance({
|
|
820
|
+
type: "conflict_accepted",
|
|
821
|
+
message: "Conflict accepted; canonical graph updated",
|
|
822
|
+
sourceEventId: current.source_event_id,
|
|
823
|
+
conflictId,
|
|
824
|
+
entities: current.candidate.entities,
|
|
825
|
+
relationTypes: (current.candidate.relations || []).map(item => item.type),
|
|
826
|
+
});
|
|
827
|
+
return { success: true, appliedRecordId: record.id };
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
append,
|
|
831
|
+
loadAll,
|
|
832
|
+
loadByArchiveEventId,
|
|
833
|
+
loadByEntity,
|
|
834
|
+
getStats,
|
|
835
|
+
exportGraphView,
|
|
836
|
+
listConflicts,
|
|
837
|
+
resolveConflict,
|
|
838
|
+
getConflictStats,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
//# sourceMappingURL=graph_memory_store.js.map
|