openclaw-cortex-memory 0.1.0-Alpha.9 → 0.1.0

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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +347 -290
  3. package/SIGNATURE.md +7 -0
  4. package/SKILL.md +96 -345
  5. package/dist/index.d.ts +69 -23
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1130 -1330
  8. package/dist/index.js.map +1 -1
  9. package/dist/openclaw.plugin.json +377 -18
  10. package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -1
  11. package/dist/src/dedup/three_stage_deduplicator.js +13 -3
  12. package/dist/src/dedup/three_stage_deduplicator.js.map +1 -1
  13. package/dist/src/engine/memory_engine.d.ts +5 -1
  14. package/dist/src/engine/memory_engine.d.ts.map +1 -1
  15. package/dist/src/engine/ts_engine.d.ts +149 -0
  16. package/dist/src/engine/ts_engine.d.ts.map +1 -1
  17. package/dist/src/engine/ts_engine.js +863 -203
  18. package/dist/src/engine/ts_engine.js.map +1 -1
  19. package/dist/src/engine/types.d.ts +20 -0
  20. package/dist/src/engine/types.d.ts.map +1 -1
  21. package/dist/src/graph/ontology.d.ts +87 -15
  22. package/dist/src/graph/ontology.d.ts.map +1 -1
  23. package/dist/src/graph/ontology.js +999 -12
  24. package/dist/src/graph/ontology.js.map +1 -1
  25. package/dist/src/net/http_post.d.ts +17 -0
  26. package/dist/src/net/http_post.d.ts.map +1 -0
  27. package/dist/src/net/http_post.js +56 -0
  28. package/dist/src/net/http_post.js.map +1 -0
  29. package/dist/src/quality/llm_output_validator.d.ts +65 -0
  30. package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
  31. package/dist/src/quality/llm_output_validator.js +635 -0
  32. package/dist/src/quality/llm_output_validator.js.map +1 -0
  33. package/dist/src/reflect/reflector.d.ts.map +1 -1
  34. package/dist/src/reflect/reflector.js +296 -26
  35. package/dist/src/reflect/reflector.js.map +1 -1
  36. package/dist/src/rules/rule_store.d.ts.map +1 -1
  37. package/dist/src/rules/rule_store.js +75 -16
  38. package/dist/src/rules/rule_store.js.map +1 -1
  39. package/dist/src/session/session_end.d.ts +20 -42
  40. package/dist/src/session/session_end.d.ts.map +1 -1
  41. package/dist/src/session/session_end.js +21 -218
  42. package/dist/src/session/session_end.js.map +1 -1
  43. package/dist/src/store/archive_store.d.ts +28 -7
  44. package/dist/src/store/archive_store.d.ts.map +1 -1
  45. package/dist/src/store/archive_store.js +367 -130
  46. package/dist/src/store/archive_store.js.map +1 -1
  47. package/dist/src/store/graph_memory_store.d.ts +115 -0
  48. package/dist/src/store/graph_memory_store.d.ts.map +1 -0
  49. package/dist/src/store/graph_memory_store.js +1061 -0
  50. package/dist/src/store/graph_memory_store.js.map +1 -0
  51. package/dist/src/store/read_store.d.ts +75 -0
  52. package/dist/src/store/read_store.d.ts.map +1 -1
  53. package/dist/src/store/read_store.js +1837 -312
  54. package/dist/src/store/read_store.js.map +1 -1
  55. package/dist/src/store/vector_store.d.ts +2 -0
  56. package/dist/src/store/vector_store.d.ts.map +1 -1
  57. package/dist/src/store/vector_store.js +19 -3
  58. package/dist/src/store/vector_store.js.map +1 -1
  59. package/dist/src/store/write_store.d.ts +11 -0
  60. package/dist/src/store/write_store.d.ts.map +1 -1
  61. package/dist/src/store/write_store.js +242 -42
  62. package/dist/src/store/write_store.js.map +1 -1
  63. package/dist/src/sync/session_sync.d.ts +72 -1
  64. package/dist/src/sync/session_sync.d.ts.map +1 -1
  65. package/dist/src/sync/session_sync.js +2246 -126
  66. package/dist/src/sync/session_sync.js.map +1 -1
  67. package/dist/src/wiki/wiki_linter.d.ts +26 -0
  68. package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
  69. package/dist/src/wiki/wiki_linter.js +339 -0
  70. package/dist/src/wiki/wiki_linter.js.map +1 -0
  71. package/dist/src/wiki/wiki_logger.d.ts +10 -0
  72. package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
  73. package/dist/src/wiki/wiki_logger.js +78 -0
  74. package/dist/src/wiki/wiki_logger.js.map +1 -0
  75. package/dist/src/wiki/wiki_maintainer.d.ts +39 -0
  76. package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
  77. package/dist/src/wiki/wiki_maintainer.js +38 -0
  78. package/dist/src/wiki/wiki_maintainer.js.map +1 -0
  79. package/dist/src/wiki/wiki_projector.d.ts +35 -0
  80. package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
  81. package/dist/src/wiki/wiki_projector.js +1151 -0
  82. package/dist/src/wiki/wiki_projector.js.map +1 -0
  83. package/dist/src/wiki/wiki_queue.d.ts +29 -0
  84. package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
  85. package/dist/src/wiki/wiki_queue.js +137 -0
  86. package/dist/src/wiki/wiki_queue.js.map +1 -0
  87. package/openclaw.plugin.json +377 -18
  88. package/package.json +52 -6
  89. package/schema/graph.schema.yaml +330 -0
  90. package/scripts/cli.js +67 -13
  91. package/scripts/repair-memory.js +321 -0
  92. package/skills/cortex-memory/SKILL.md +83 -0
  93. package/skills/cortex-memory/references/agent-manual.md +127 -0
  94. package/skills/cortex-memory/references/configuration.md +109 -0
  95. package/skills/cortex-memory/references/publish-checklist.md +45 -0
  96. package/skills/cortex-memory/references/system-prompt-template.md +27 -0
  97. package/skills/cortex-memory/references/tools.md +191 -0
@@ -0,0 +1,1061 @@
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
+ "owned_by",
51
+ "assigned_to",
52
+ "reviewed_by",
53
+ "approved_by",
54
+ "rejected_by",
55
+ "planned_for",
56
+ "scheduled_for",
57
+ "configured_in",
58
+ "migrates_to",
59
+ "replaced_by",
60
+ "supersedes",
61
+ ]);
62
+ const TARGET_TYPE_SCOPED_SINGLE_VALUE_RELATION_TYPES = new Set([
63
+ "prefers",
64
+ ]);
65
+ const OPPOSING_RELATION_TYPES = {
66
+ approved_by: ["rejected_by"],
67
+ rejected_by: ["approved_by"],
68
+ blocks: ["supports", "unblocks"],
69
+ supports: ["blocks", "conflicts_with"],
70
+ unblocks: ["blocks"],
71
+ conflicts_with: ["supports"],
72
+ duplicates: ["supersedes", "replaced_by"],
73
+ supersedes: ["duplicates"],
74
+ replaced_by: ["duplicates"],
75
+ };
76
+ const CURRENT_STATE_RELATION_TYPES = new Set([
77
+ ...SINGLE_VALUE_RELATION_TYPES,
78
+ ...TARGET_TYPE_SCOPED_SINGLE_VALUE_RELATION_TYPES,
79
+ ]);
80
+ function ensureDirForFile(filePath) {
81
+ const dir = path.dirname(filePath);
82
+ if (!fs.existsSync(dir)) {
83
+ fs.mkdirSync(dir, { recursive: true });
84
+ }
85
+ }
86
+ function generateGraphId() {
87
+ return `gph_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
88
+ }
89
+ function generateConflictId() {
90
+ return `gcf_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
91
+ }
92
+ function relationKey(relation) {
93
+ const source = (relation.source || "").trim().toLowerCase();
94
+ const type = (relation.type || "related_to").trim().toLowerCase();
95
+ const target = (relation.target || "").trim().toLowerCase();
96
+ return `${source}|${type}|${target}`;
97
+ }
98
+ function relationBucketKey(relation, entityTypes) {
99
+ const source = (relation.source || "").trim().toLowerCase();
100
+ const type = (relation.type || "related_to").trim().toLowerCase();
101
+ if (TARGET_TYPE_SCOPED_SINGLE_VALUE_RELATION_TYPES.has(type)) {
102
+ const target = (relation.target || "").trim();
103
+ const targetType = target && entityTypes ? (entityTypes[target] || entityTypes[target.toLowerCase()] || "") : "";
104
+ return `${source}|${type}|target_type:${String(targetType || "unknown").trim().toLowerCase()}`;
105
+ }
106
+ return `${source}|${type}`;
107
+ }
108
+ function opposingRelationBucketKey(relation) {
109
+ const source = (relation.source || "").trim().toLowerCase();
110
+ const target = (relation.target || "").trim().toLowerCase();
111
+ return `${source}|${target}`;
112
+ }
113
+ function getOpposingTypes(type) {
114
+ return new Set((OPPOSING_RELATION_TYPES[type] || []).map(item => item.trim().toLowerCase()).filter(Boolean));
115
+ }
116
+ function normalizeSummary(value) {
117
+ return String(value || "").trim().replace(/\s+/g, " ").toLowerCase();
118
+ }
119
+ function buildStructureSignature(record) {
120
+ const entities = (record.entities || [])
121
+ .map(item => String(item || "").trim().toLowerCase())
122
+ .filter(Boolean)
123
+ .sort();
124
+ const relations = (record.relations || [])
125
+ .map(item => {
126
+ const source = String(item.source || "").trim().toLowerCase();
127
+ const type = String(item.type || "").trim().toLowerCase();
128
+ const target = String(item.target || "").trim().toLowerCase();
129
+ const evidence = String(item.evidence_span || "").trim().toLowerCase();
130
+ const context = String(item.context_chunk || "").trim().toLowerCase();
131
+ return `${source}|${type}|${target}|${evidence}|${context}`;
132
+ })
133
+ .filter(Boolean)
134
+ .sort();
135
+ return `${entities.join("||")}#${relations.join("||")}`;
136
+ }
137
+ function buildFallbackSummary(entities, relations) {
138
+ const entityText = (entities || []).map(item => String(item || "").trim()).filter(Boolean).join(", ");
139
+ const relationText = (relations || [])
140
+ .slice(0, 3)
141
+ .map(item => `${item.source} ${item.type} ${item.target}`)
142
+ .join("; ");
143
+ return `Graph memory update covering entities [${entityText || "n/a"}] with relations [${relationText || "n/a"}].`;
144
+ }
145
+ function toConflictSummary(record) {
146
+ return {
147
+ conflict_id: record.conflict_id,
148
+ status: record.status,
149
+ created_at: record.created_at,
150
+ updated_at: record.updated_at,
151
+ source_event_id: record.source_event_id,
152
+ session_id: record.session_id,
153
+ conflict_reason: record.conflict_reason,
154
+ conflict_types: record.conflict_types,
155
+ existing_relation_keys: record.existing_relation_keys,
156
+ candidate_relations: (record.candidate.relations || []).map(rel => ({
157
+ source: rel.source,
158
+ type: rel.type,
159
+ target: rel.target,
160
+ })),
161
+ resolution: record.resolution,
162
+ };
163
+ }
164
+ function createGraphMemoryStore(options) {
165
+ const graphMemoryPath = path.join(options.memoryRoot, "graph", "memory.jsonl");
166
+ const mutationLogPath = path.join(options.memoryRoot, "graph", "mutation_log.jsonl");
167
+ const graphMemoryMarkdownPath = path.join(options.memoryRoot, "graph", "memory.md");
168
+ const mutationLogMarkdownPath = path.join(options.memoryRoot, "graph", "mutation_log.md");
169
+ const conflictQueuePath = path.join(options.memoryRoot, "graph", "conflict_queue.jsonl");
170
+ const supersededRelationPath = path.join(options.memoryRoot, "graph", "superseded_relations.jsonl");
171
+ const graphSchema = (0, ontology_1.loadGraphSchema)(options.projectRoot);
172
+ const wikiProjectionEnabled = options.wikiProjection?.enabled === true && options.wikiProjection?.mode !== "off";
173
+ const wikiProjectionMaxBatch = typeof options.wikiProjection?.maxBatch === "number" && Number.isFinite(options.wikiProjection.maxBatch)
174
+ ? Math.max(1, Math.min(1000, Math.floor(options.wikiProjection.maxBatch)))
175
+ : 100;
176
+ function sanitizeMarkdownInline(value) {
177
+ return String(value ?? "").replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
178
+ }
179
+ function loadMutationLogEntries() {
180
+ if (!fs.existsSync(mutationLogPath)) {
181
+ return [];
182
+ }
183
+ const lines = fs.readFileSync(mutationLogPath, "utf-8").split(/\r?\n/).filter(Boolean);
184
+ const output = [];
185
+ for (const line of lines) {
186
+ try {
187
+ const parsed = JSON.parse(line);
188
+ if (parsed && typeof parsed.id === "string" && parsed.id.trim()) {
189
+ output.push(parsed);
190
+ }
191
+ }
192
+ catch {
193
+ options.logger.warn(`graph_mutation_parse_error line=${line.slice(0, 120)}`);
194
+ }
195
+ }
196
+ return output;
197
+ }
198
+ function renderGraphMemoryMarkdown(records) {
199
+ const lines = [
200
+ "# Graph Memory",
201
+ "",
202
+ "> Human-readable mirror of graph/memory.jsonl",
203
+ "",
204
+ `Generated at: ${new Date().toISOString()}`,
205
+ `Total records: ${records.length}`,
206
+ "",
207
+ ];
208
+ if (records.length === 0) {
209
+ lines.push("## Records", "", "- (none)", "");
210
+ return `${lines.join("\n")}\n`;
211
+ }
212
+ lines.push("## Records", "");
213
+ for (const record of records) {
214
+ lines.push(`### ${sanitizeMarkdownInline(record.id)}`);
215
+ lines.push(`- timestamp: ${sanitizeMarkdownInline(record.timestamp)}`);
216
+ lines.push(`- source_event_id: ${sanitizeMarkdownInline(record.source_event_id)}`);
217
+ lines.push(`- source_layer: ${sanitizeMarkdownInline(record.source_layer)}`);
218
+ lines.push(`- session_id: ${sanitizeMarkdownInline(record.session_id)}`);
219
+ if (record.source_file) {
220
+ lines.push(`- source_file: ${sanitizeMarkdownInline(record.source_file)}`);
221
+ }
222
+ lines.push(`- entities: ${(record.entities || []).map(item => sanitizeMarkdownInline(item)).join(", ") || "(none)"}`);
223
+ lines.push(`- summary: ${sanitizeMarkdownInline(record.summary) || "(none)"}`);
224
+ lines.push("- relations:");
225
+ if (!Array.isArray(record.relations) || record.relations.length === 0) {
226
+ lines.push(" - (none)");
227
+ }
228
+ else {
229
+ for (const relation of record.relations) {
230
+ lines.push(` - ${sanitizeMarkdownInline(relation.source)} --${sanitizeMarkdownInline(relation.type)}--> ${sanitizeMarkdownInline(relation.target)} | confidence=${typeof relation.confidence === "number" ? relation.confidence : "n/a"} | evidence=${sanitizeMarkdownInline(relation.evidence_span) || "n/a"}`);
231
+ }
232
+ }
233
+ lines.push("");
234
+ }
235
+ return `${lines.join("\n")}\n`;
236
+ }
237
+ function renderMutationLogMarkdown(entries) {
238
+ const lines = [
239
+ "# Graph Mutation Log",
240
+ "",
241
+ "> Human-readable mirror of graph/mutation_log.jsonl",
242
+ "",
243
+ `Generated at: ${new Date().toISOString()}`,
244
+ `Total entries: ${entries.length}`,
245
+ "",
246
+ "## Entries",
247
+ "",
248
+ ];
249
+ if (entries.length === 0) {
250
+ lines.push("- (none)", "");
251
+ return `${lines.join("\n")}\n`;
252
+ }
253
+ for (const entry of entries) {
254
+ lines.push(`### ${sanitizeMarkdownInline(entry.id)}`);
255
+ lines.push(`- op: ${sanitizeMarkdownInline(entry.op)}`);
256
+ lines.push(`- timestamp: ${sanitizeMarkdownInline(entry.timestamp)}`);
257
+ lines.push(`- source_event_id: ${sanitizeMarkdownInline(entry.source_event_id)}`);
258
+ lines.push(`- source_layer: ${sanitizeMarkdownInline(entry.source_layer)}`);
259
+ lines.push(`- session_id: ${sanitizeMarkdownInline(entry.session_id)}`);
260
+ lines.push(`- gate_source: ${sanitizeMarkdownInline(entry.gate_source)}`);
261
+ lines.push(`- entity_count: ${Number.isFinite(entry.entity_count) ? entry.entity_count : 0}`);
262
+ lines.push(`- relation_count: ${Number.isFinite(entry.relation_count) ? entry.relation_count : 0}`);
263
+ if (entry.relation_key) {
264
+ lines.push(`- relation_key: ${sanitizeMarkdownInline(entry.relation_key)}`);
265
+ }
266
+ if (entry.conflict_id) {
267
+ lines.push(`- conflict_id: ${sanitizeMarkdownInline(entry.conflict_id)}`);
268
+ }
269
+ if (entry.note) {
270
+ lines.push(`- note: ${sanitizeMarkdownInline(entry.note)}`);
271
+ }
272
+ lines.push("");
273
+ }
274
+ return `${lines.join("\n")}\n`;
275
+ }
276
+ function refreshMarkdownArtifacts(reason) {
277
+ try {
278
+ const records = loadAllRaw().sort((a, b) => Date.parse(b.timestamp || "") - Date.parse(a.timestamp || ""));
279
+ const mutations = loadMutationLogEntries().sort((a, b) => Date.parse(b.timestamp || "") - Date.parse(a.timestamp || ""));
280
+ ensureDirForFile(graphMemoryMarkdownPath);
281
+ ensureDirForFile(mutationLogMarkdownPath);
282
+ fs.writeFileSync(graphMemoryMarkdownPath, renderGraphMemoryMarkdown(records), "utf-8");
283
+ fs.writeFileSync(mutationLogMarkdownPath, renderMutationLogMarkdown(mutations), "utf-8");
284
+ }
285
+ catch (error) {
286
+ options.logger.warn(`graph_markdown_refresh_failed reason=${reason} error=${String(error)}`);
287
+ }
288
+ }
289
+ function triggerWikiMaintenance(input) {
290
+ if (!wikiProjectionEnabled) {
291
+ return;
292
+ }
293
+ try {
294
+ (0, wiki_queue_1.appendWikiRebuildEvent)({
295
+ memoryRoot: options.memoryRoot,
296
+ type: input.type,
297
+ source_event_id: input.sourceEventId,
298
+ conflict_id: input.conflictId,
299
+ entities: input.entities,
300
+ relation_types: input.relationTypes,
301
+ });
302
+ (0, wiki_logger_1.appendWikiLog)({
303
+ memoryRoot: options.memoryRoot,
304
+ type: input.type,
305
+ source_event_id: input.sourceEventId,
306
+ conflict_id: input.conflictId,
307
+ message: input.message,
308
+ });
309
+ const graphView = exportGraphView();
310
+ (0, wiki_maintainer_1.maintainWikiProjection)({
311
+ memoryRoot: options.memoryRoot,
312
+ graphView,
313
+ maxBatch: wikiProjectionMaxBatch,
314
+ logger: options.logger,
315
+ });
316
+ }
317
+ catch (error) {
318
+ options.logger.warn(`wiki_projection_maintenance_failed reason=${String(error)}`);
319
+ }
320
+ }
321
+ function rebuildWikiProjectionFromCurrentGraph() {
322
+ if (!wikiProjectionEnabled) {
323
+ return;
324
+ }
325
+ const wikiRoot = path.join(options.memoryRoot, "wiki");
326
+ const wikiIndexPath = path.join(wikiRoot, "index.md");
327
+ const wikiProjectionIndexPath = path.join(wikiRoot, ".projection_index.json");
328
+ if (fs.existsSync(wikiIndexPath) && fs.existsSync(wikiProjectionIndexPath)) {
329
+ return;
330
+ }
331
+ try {
332
+ const graphView = exportGraphView();
333
+ if ((graphView.edges || []).length === 0) {
334
+ return;
335
+ }
336
+ (0, wiki_maintainer_1.maintainWikiProjection)({
337
+ memoryRoot: options.memoryRoot,
338
+ graphView,
339
+ maxBatch: wikiProjectionMaxBatch,
340
+ logger: options.logger,
341
+ force: true,
342
+ });
343
+ }
344
+ catch (error) {
345
+ options.logger.warn(`wiki_projection_bootstrap_failed reason=${String(error)}`);
346
+ }
347
+ }
348
+ function loadSupersededRelationEntries() {
349
+ if (!fs.existsSync(supersededRelationPath)) {
350
+ return [];
351
+ }
352
+ const lines = fs.readFileSync(supersededRelationPath, "utf-8").split(/\r?\n/).filter(Boolean);
353
+ const entries = [];
354
+ for (const line of lines) {
355
+ try {
356
+ const parsed = JSON.parse(line);
357
+ const relationKeyRaw = typeof parsed.relation_key === "string" ? parsed.relation_key.trim().toLowerCase() : "";
358
+ if (!relationKeyRaw) {
359
+ continue;
360
+ }
361
+ entries.push({
362
+ relation_key: relationKeyRaw,
363
+ superseded_at: typeof parsed.superseded_at === "string" ? parsed.superseded_at : "",
364
+ conflict_id: typeof parsed.conflict_id === "string" ? parsed.conflict_id : undefined,
365
+ note: typeof parsed.note === "string" ? parsed.note : undefined,
366
+ });
367
+ }
368
+ catch {
369
+ options.logger.warn(`graph_superseded_parse_error line=${line.slice(0, 120)}`);
370
+ }
371
+ }
372
+ return entries;
373
+ }
374
+ function loadSupersededRelationKeys() {
375
+ const keys = new Set();
376
+ for (const entry of loadSupersededRelationEntries()) {
377
+ if (entry.relation_key) {
378
+ keys.add(entry.relation_key);
379
+ }
380
+ }
381
+ return keys;
382
+ }
383
+ function appendSupersededRelationEntries(entries) {
384
+ if (entries.length === 0) {
385
+ return;
386
+ }
387
+ ensureDirForFile(supersededRelationPath);
388
+ const lines = entries.map(item => JSON.stringify(item));
389
+ fs.appendFileSync(supersededRelationPath, `${lines.join("\n")}\n`, "utf-8");
390
+ }
391
+ function filterRecordBySuperseded(record, supersededKeys) {
392
+ const relations = Array.isArray(record.relations)
393
+ ? record.relations.filter(rel => !supersededKeys.has(relationKey(rel)))
394
+ : [];
395
+ if (relations.length === 0) {
396
+ return null;
397
+ }
398
+ return {
399
+ ...record,
400
+ relations,
401
+ };
402
+ }
403
+ function loadAllRaw() {
404
+ if (!fs.existsSync(graphMemoryPath)) {
405
+ return [];
406
+ }
407
+ const content = fs.readFileSync(graphMemoryPath, "utf-8");
408
+ const lines = content.split(/\r?\n/).filter(Boolean);
409
+ const records = [];
410
+ for (const line of lines) {
411
+ try {
412
+ const parsed = JSON.parse(line);
413
+ if (parsed && parsed.id && (parsed.source_event_id || parsed.archive_event_id)) {
414
+ if (!parsed.source_event_id && parsed.archive_event_id) {
415
+ parsed.source_event_id = parsed.archive_event_id;
416
+ parsed.source_layer = "archive_event";
417
+ }
418
+ records.push(parsed);
419
+ }
420
+ }
421
+ catch {
422
+ options.logger.warn(`graph_memory_parse_error line=${line.slice(0, 100)}`);
423
+ }
424
+ }
425
+ return records;
426
+ }
427
+ function loadAllEffective() {
428
+ const supersededKeys = loadSupersededRelationKeys();
429
+ const raw = loadAllRaw();
430
+ const output = [];
431
+ for (const record of raw) {
432
+ const filtered = filterRecordBySuperseded(record, supersededKeys);
433
+ if (filtered) {
434
+ output.push(filtered);
435
+ }
436
+ }
437
+ return output;
438
+ }
439
+ function shouldRejectStaleSummary(record) {
440
+ const sameSourceRecords = loadAllEffective()
441
+ .filter(item => item.source_event_id === record.source_event_id)
442
+ .sort((a, b) => Date.parse(b.timestamp || "") - Date.parse(a.timestamp || ""));
443
+ if (sameSourceRecords.length === 0) {
444
+ return false;
445
+ }
446
+ const latest = sameSourceRecords[0];
447
+ const previousSummary = normalizeSummary(latest.summary);
448
+ const nextSummary = normalizeSummary(record.summary);
449
+ if (!previousSummary || !nextSummary || previousSummary !== nextSummary) {
450
+ return false;
451
+ }
452
+ const previousSignature = buildStructureSignature(latest);
453
+ const nextSignature = buildStructureSignature(record);
454
+ return previousSignature !== nextSignature;
455
+ }
456
+ function loadConflictQueue() {
457
+ if (!fs.existsSync(conflictQueuePath)) {
458
+ return [];
459
+ }
460
+ const lines = fs.readFileSync(conflictQueuePath, "utf-8").split(/\r?\n/).filter(Boolean);
461
+ const records = [];
462
+ for (const line of lines) {
463
+ try {
464
+ const parsed = JSON.parse(line);
465
+ if (parsed && typeof parsed.conflict_id === "string" && parsed.conflict_id.trim()) {
466
+ records.push(parsed);
467
+ }
468
+ }
469
+ catch {
470
+ options.logger.warn(`graph_conflict_parse_error line=${line.slice(0, 120)}`);
471
+ }
472
+ }
473
+ return records;
474
+ }
475
+ function saveConflictQueue(records) {
476
+ ensureDirForFile(conflictQueuePath);
477
+ const lines = records.map(item => JSON.stringify(item));
478
+ fs.writeFileSync(conflictQueuePath, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`, "utf-8");
479
+ }
480
+ function appendGraphRecord(args) {
481
+ const line = JSON.stringify(args.record);
482
+ const qualityCheck = (0, llm_output_validator_1.validateGraphJsonlLine)(line);
483
+ if (!qualityCheck.valid) {
484
+ throw new Error(`graph_record_quality_invalid:${qualityCheck.errors.join("|")}`);
485
+ }
486
+ if (qualityCheck.warnings.length > 0) {
487
+ options.logger.warn(`graph_quality_warning source_event_id=${args.record.source_event_id} warnings=${qualityCheck.warnings.join("|")}`);
488
+ }
489
+ const mutationEntry = {
490
+ op: args.conflictId ? "insert_graph_conflict_resolved" : "insert_graph",
491
+ id: args.record.id,
492
+ source_event_id: args.record.source_event_id,
493
+ source_layer: args.record.source_layer,
494
+ archive_event_id: args.record.archive_event_id,
495
+ timestamp: args.record.timestamp,
496
+ session_id: args.record.session_id,
497
+ gate_source: args.record.gate_source,
498
+ entity_count: args.record.entities.length,
499
+ relation_count: args.record.relations.length,
500
+ conflict_id: args.conflictId,
501
+ note: args.note,
502
+ };
503
+ ensureDirForFile(graphMemoryPath);
504
+ ensureDirForFile(mutationLogPath);
505
+ fs.appendFileSync(graphMemoryPath, `${line}\n`, "utf-8");
506
+ fs.appendFileSync(mutationLogPath, `${JSON.stringify(mutationEntry)}\n`, "utf-8");
507
+ refreshMarkdownArtifacts("append_graph_record");
508
+ }
509
+ function appendSupersedeMutationLogs(entries, conflictId, sourceEventId) {
510
+ if (entries.length === 0) {
511
+ return;
512
+ }
513
+ ensureDirForFile(mutationLogPath);
514
+ const nowIso = new Date().toISOString();
515
+ const lines = entries.map(item => JSON.stringify({
516
+ op: "supersede_relation",
517
+ id: `sup_${crypto.createHash("sha1").update(`${item.relation_key}|${item.superseded_at}`).digest("hex").slice(0, 12)}`,
518
+ source_event_id: sourceEventId || "conflict_resolution",
519
+ source_layer: "active_only",
520
+ timestamp: nowIso,
521
+ session_id: "system",
522
+ gate_source: "manual",
523
+ entity_count: 0,
524
+ relation_count: 0,
525
+ relation_key: item.relation_key,
526
+ conflict_id: conflictId,
527
+ note: item.note,
528
+ }));
529
+ fs.appendFileSync(mutationLogPath, `${lines.join("\n")}\n`, "utf-8");
530
+ refreshMarkdownArtifacts("append_supersede_mutation_logs");
531
+ }
532
+ function detectConflicts(candidate) {
533
+ const existing = loadAllEffective();
534
+ const candidateCurrentState = (candidate.relations || []).filter(rel => {
535
+ const type = (rel.type || "").trim().toLowerCase();
536
+ return CURRENT_STATE_RELATION_TYPES.has(type) || getOpposingTypes(type).size > 0;
537
+ });
538
+ if (candidateCurrentState.length === 0) {
539
+ return {
540
+ hasConflict: false,
541
+ reason: "",
542
+ conflictTypes: [],
543
+ existingRelations: [],
544
+ existingRelationKeys: [],
545
+ };
546
+ }
547
+ const existingByBucket = new Map();
548
+ const existingByOpposingBucket = new Map();
549
+ for (const record of existing) {
550
+ for (const rel of record.relations || []) {
551
+ const type = (rel.type || "").trim().toLowerCase();
552
+ if (CURRENT_STATE_RELATION_TYPES.has(type)) {
553
+ const key = relationKey(rel);
554
+ const bucket = relationBucketKey(rel, record.entity_types);
555
+ if (!existingByBucket.has(bucket)) {
556
+ existingByBucket.set(bucket, []);
557
+ }
558
+ existingByBucket.get(bucket)?.push({ key, relation: rel });
559
+ }
560
+ if (getOpposingTypes(type).size > 0) {
561
+ const key = relationKey(rel);
562
+ const bucket = opposingRelationBucketKey(rel);
563
+ if (!existingByOpposingBucket.has(bucket)) {
564
+ existingByOpposingBucket.set(bucket, []);
565
+ }
566
+ existingByOpposingBucket.get(bucket)?.push({ key, relation: rel });
567
+ }
568
+ }
569
+ }
570
+ const conflictTypes = new Set();
571
+ const existingRelations = [];
572
+ const existingRelationKeySet = new Set();
573
+ const addConflict = (type, existingItem) => {
574
+ if (existingRelationKeySet.has(existingItem.key)) {
575
+ return;
576
+ }
577
+ existingRelationKeySet.add(existingItem.key);
578
+ existingRelations.push(existingItem);
579
+ conflictTypes.add(type);
580
+ };
581
+ for (const rel of candidateCurrentState) {
582
+ const type = (rel.type || "").trim().toLowerCase();
583
+ const candidateKey = relationKey(rel);
584
+ if (CURRENT_STATE_RELATION_TYPES.has(type)) {
585
+ const bucket = relationBucketKey(rel, candidate.entity_types);
586
+ const sameBucket = existingByBucket.get(bucket) || [];
587
+ for (const existingItem of sameBucket) {
588
+ if (existingItem.key === candidateKey) {
589
+ continue;
590
+ }
591
+ addConflict(type, existingItem);
592
+ }
593
+ }
594
+ const opposingTypes = getOpposingTypes(type);
595
+ if (opposingTypes.size > 0) {
596
+ const opposingBucket = opposingRelationBucketKey(rel);
597
+ const sameEndpoints = existingByOpposingBucket.get(opposingBucket) || [];
598
+ for (const existingItem of sameEndpoints) {
599
+ const existingType = (existingItem.relation.type || "").trim().toLowerCase();
600
+ if (existingItem.key === candidateKey || !opposingTypes.has(existingType)) {
601
+ continue;
602
+ }
603
+ addConflict(`${existingType}<->${type}`, existingItem);
604
+ }
605
+ }
606
+ }
607
+ const existingRelationKeys = [...existingRelationKeySet].sort();
608
+ if (existingRelationKeys.length === 0) {
609
+ return {
610
+ hasConflict: false,
611
+ reason: "",
612
+ conflictTypes: [],
613
+ existingRelations: [],
614
+ existingRelationKeys: [],
615
+ };
616
+ }
617
+ return {
618
+ hasConflict: true,
619
+ reason: `current_state_relation_conflict:${[...conflictTypes].sort().join(",")}`,
620
+ conflictTypes: [...conflictTypes].sort(),
621
+ existingRelations,
622
+ existingRelationKeys,
623
+ };
624
+ }
625
+ function buildConflictFingerprint(input) {
626
+ const payload = JSON.stringify({
627
+ source_event_id: input.sourceEventId,
628
+ conflict_types: [...input.conflictTypes].sort(),
629
+ existing_relation_keys: [...input.existingRelationKeys].sort(),
630
+ candidate_relation_keys: input.candidateRelations.map(relationKey).sort(),
631
+ });
632
+ return crypto.createHash("sha1").update(payload).digest("hex");
633
+ }
634
+ options.logger.info(`Graph memory store initialized at ${graphMemoryPath}`);
635
+ refreshMarkdownArtifacts("startup_init");
636
+ rebuildWikiProjectionFromCurrentGraph();
637
+ async function append(input) {
638
+ const validation = (0, ontology_1.validateGraphPayload)({
639
+ sourceEventId: input.sourceEventId,
640
+ sourceLayer: input.sourceLayer,
641
+ archiveEventId: input.archiveEventId,
642
+ sessionId: input.sessionId,
643
+ sourceFile: input.sourceFile,
644
+ source_text_nav: input.source_text_nav,
645
+ summary: input.summary,
646
+ eventType: input.eventType,
647
+ entities: input.entities,
648
+ entity_types: input.entity_types,
649
+ relations: input.relations,
650
+ gateSource: input.gateSource,
651
+ confidence: input.confidence,
652
+ schema: graphSchema,
653
+ sourceText: input.sourceText,
654
+ qualityMode: input.qualityMode || options.qualityMode || "warn",
655
+ });
656
+ if (!validation.valid) {
657
+ options.logger.info(`graph_skip_reason=${validation.reason} source_event_id=${input.sourceEventId}`);
658
+ return { success: false, reason: validation.reason };
659
+ }
660
+ if (Array.isArray(validation.warnings) && validation.warnings.length > 0) {
661
+ options.logger.warn(`graph_quality_warning source_event_id=${input.sourceEventId} warnings=${validation.warnings.join("|")}`);
662
+ }
663
+ const record = validation.normalized;
664
+ record.id = generateGraphId();
665
+ record.timestamp = new Date().toISOString();
666
+ if (shouldRejectStaleSummary(record)) {
667
+ options.logger.info(`graph_skip_reason=stale_summary_after_update source_event_id=${record.source_event_id}`);
668
+ return { success: false, reason: "stale_summary_after_update" };
669
+ }
670
+ const conflictDetection = detectConflicts(record);
671
+ if (conflictDetection.hasConflict) {
672
+ const queue = loadConflictQueue();
673
+ const fingerprint = buildConflictFingerprint({
674
+ sourceEventId: record.source_event_id,
675
+ conflictTypes: conflictDetection.conflictTypes,
676
+ existingRelationKeys: conflictDetection.existingRelationKeys,
677
+ candidateRelations: record.relations,
678
+ });
679
+ const existingPending = queue.find(item => item.status === "pending" && item.fingerprint === fingerprint);
680
+ if (existingPending) {
681
+ options.logger.info(`graph_conflict_pending_reuse conflict_id=${existingPending.conflict_id} source_event_id=${record.source_event_id}`);
682
+ return { success: false, reason: `graph_conflict_pending:${existingPending.conflict_id}` };
683
+ }
684
+ const now = new Date().toISOString();
685
+ const conflictRecord = {
686
+ conflict_id: generateConflictId(),
687
+ status: "pending",
688
+ created_at: now,
689
+ updated_at: now,
690
+ source_event_id: record.source_event_id,
691
+ source_layer: record.source_layer,
692
+ session_id: record.session_id,
693
+ source_file: record.source_file,
694
+ conflict_reason: conflictDetection.reason,
695
+ conflict_types: conflictDetection.conflictTypes,
696
+ existing_relation_keys: conflictDetection.existingRelationKeys,
697
+ existing_relations: conflictDetection.existingRelations,
698
+ candidate: {
699
+ summary: record.summary,
700
+ source_text_nav: record.source_text_nav,
701
+ entities: record.entities,
702
+ entity_types: record.entity_types,
703
+ relations: record.relations,
704
+ event_type: record.event_type,
705
+ gate_source: record.gate_source,
706
+ confidence: record.confidence,
707
+ source_text: input.sourceText,
708
+ },
709
+ fingerprint,
710
+ };
711
+ queue.push(conflictRecord);
712
+ saveConflictQueue(queue);
713
+ options.logger.info(`graph_conflict_pending id=${conflictRecord.conflict_id} source_event_id=${record.source_event_id} types=${conflictRecord.conflict_types.join(",")}`);
714
+ triggerWikiMaintenance({
715
+ type: "conflict_pending",
716
+ message: "Conflict pending and awaiting confirmation",
717
+ sourceEventId: record.source_event_id,
718
+ conflictId: conflictRecord.conflict_id,
719
+ entities: record.entities,
720
+ relationTypes: (record.relations || []).map(item => item.type),
721
+ });
722
+ return { success: false, reason: `graph_conflict_pending:${conflictRecord.conflict_id}` };
723
+ }
724
+ try {
725
+ appendGraphRecord({ record });
726
+ 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}`);
727
+ triggerWikiMaintenance({
728
+ type: "graph_append",
729
+ message: "Graph relation appended",
730
+ sourceEventId: record.source_event_id,
731
+ entities: record.entities,
732
+ relationTypes: (record.relations || []).map(item => item.type),
733
+ });
734
+ return { success: true, record };
735
+ }
736
+ catch (error) {
737
+ const reason = String(error);
738
+ options.logger.warn(`graph_skip_reason=${reason} source_event_id=${input.sourceEventId}`);
739
+ return { success: false, reason };
740
+ }
741
+ }
742
+ function loadAll() {
743
+ return loadAllEffective();
744
+ }
745
+ function loadByArchiveEventId(archiveEventId) {
746
+ return loadAll().filter(record => record.archive_event_id === archiveEventId || record.source_event_id === archiveEventId);
747
+ }
748
+ function loadByEntity(entityName) {
749
+ const normalized = entityName.trim();
750
+ const queryKeySet = new Set((0, ontology_1.getEntityMatchKeys)(normalized, graphSchema));
751
+ if (queryKeySet.size === 0) {
752
+ return [];
753
+ }
754
+ return loadAll().filter(record => record.entities.some(entity => {
755
+ const entityKeys = (0, ontology_1.getEntityMatchKeys)(entity, graphSchema);
756
+ return entityKeys.some(key => queryKeySet.has(key));
757
+ }));
758
+ }
759
+ function getStats() {
760
+ const all = loadAll();
761
+ const entitySet = new Set();
762
+ let totalRelations = 0;
763
+ for (const record of all) {
764
+ for (const entity of record.entities) {
765
+ entitySet.add(entity.trim().toLowerCase());
766
+ }
767
+ totalRelations += record.relations.length;
768
+ }
769
+ return {
770
+ totalRecords: all.length,
771
+ totalEntities: entitySet.size,
772
+ totalRelations,
773
+ };
774
+ }
775
+ function parseRelationKey(key) {
776
+ const parts = key.split("|");
777
+ return {
778
+ source: parts[0] || "",
779
+ type: parts[1] || "related_to",
780
+ target: parts.slice(2).join("|") || "",
781
+ };
782
+ }
783
+ function exportGraphView() {
784
+ const rawRecords = loadAllRaw();
785
+ const activeRecords = loadAllEffective();
786
+ const conflictQueue = loadConflictQueue();
787
+ const supersededEntries = loadSupersededRelationEntries();
788
+ const nodes = new Map();
789
+ const edges = [];
790
+ const dedupe = new Set();
791
+ let latestTimestampMs = 0;
792
+ function updateLatest(iso) {
793
+ const ms = Date.parse(iso || "");
794
+ if (Number.isFinite(ms) && ms > latestTimestampMs) {
795
+ latestTimestampMs = ms;
796
+ }
797
+ }
798
+ function addNode(id) {
799
+ const trimmed = id.trim();
800
+ if (!trimmed) {
801
+ return;
802
+ }
803
+ const key = trimmed.toLowerCase();
804
+ if (!nodes.has(key)) {
805
+ nodes.set(key, { id: trimmed, type: "entity" });
806
+ }
807
+ }
808
+ function addEdge(input) {
809
+ const source = input.source.trim();
810
+ const target = input.target.trim();
811
+ const type = (input.type || "related_to").trim().toLowerCase();
812
+ if (!source || !target || !type) {
813
+ return;
814
+ }
815
+ const relation_key = `${source.toLowerCase()}|${type}|${target.toLowerCase()}`;
816
+ const dedupeKey = `${relation_key}|${input.status}|${(input.conflict_id || "").trim().toLowerCase()}`;
817
+ if (dedupe.has(dedupeKey)) {
818
+ return;
819
+ }
820
+ dedupe.add(dedupeKey);
821
+ addNode(source);
822
+ addNode(target);
823
+ edges.push({
824
+ source,
825
+ target,
826
+ type,
827
+ status: input.status,
828
+ relation_key,
829
+ source_event_id: input.source_event_id,
830
+ evidence_span: input.evidence_span,
831
+ confidence: input.confidence,
832
+ conflict_id: input.conflict_id,
833
+ });
834
+ }
835
+ const relationMetaByKey = new Map();
836
+ for (const record of rawRecords) {
837
+ updateLatest(record.timestamp);
838
+ for (const rel of record.relations || []) {
839
+ const key = relationKey(rel);
840
+ const prev = relationMetaByKey.get(key);
841
+ if (!prev || Date.parse(record.timestamp || "") >= Date.parse(prev.timestamp || "")) {
842
+ relationMetaByKey.set(key, {
843
+ source_event_id: record.source_event_id,
844
+ evidence_span: rel.evidence_span,
845
+ confidence: rel.confidence,
846
+ timestamp: record.timestamp,
847
+ });
848
+ }
849
+ }
850
+ }
851
+ for (const record of activeRecords) {
852
+ updateLatest(record.timestamp);
853
+ for (const rel of record.relations || []) {
854
+ addEdge({
855
+ source: rel.source,
856
+ target: rel.target,
857
+ type: rel.type,
858
+ status: "active",
859
+ source_event_id: record.source_event_id,
860
+ evidence_span: rel.evidence_span,
861
+ confidence: rel.confidence,
862
+ });
863
+ }
864
+ }
865
+ for (const entry of supersededEntries) {
866
+ updateLatest(entry.superseded_at);
867
+ const parsed = parseRelationKey(entry.relation_key);
868
+ const meta = relationMetaByKey.get(entry.relation_key);
869
+ addEdge({
870
+ source: parsed.source,
871
+ target: parsed.target,
872
+ type: parsed.type,
873
+ status: "superseded",
874
+ source_event_id: meta?.source_event_id,
875
+ evidence_span: meta?.evidence_span,
876
+ confidence: meta?.confidence,
877
+ conflict_id: entry.conflict_id,
878
+ });
879
+ }
880
+ for (const item of conflictQueue) {
881
+ updateLatest(item.updated_at);
882
+ if (item.status !== "pending" && item.status !== "rejected") {
883
+ continue;
884
+ }
885
+ const status = item.status === "pending" ? "pending_conflict" : "rejected";
886
+ for (const rel of item.candidate.relations || []) {
887
+ addEdge({
888
+ source: rel.source,
889
+ target: rel.target,
890
+ type: rel.type,
891
+ status,
892
+ source_event_id: item.source_event_id,
893
+ evidence_span: rel.evidence_span,
894
+ confidence: rel.confidence,
895
+ conflict_id: item.conflict_id,
896
+ });
897
+ }
898
+ }
899
+ const status_counts = {
900
+ active: 0,
901
+ pending_conflict: 0,
902
+ superseded: 0,
903
+ rejected: 0,
904
+ };
905
+ for (const edge of edges) {
906
+ status_counts[edge.status] += 1;
907
+ }
908
+ edges.sort((a, b) => {
909
+ const left = `${a.status}|${a.source.toLowerCase()}|${a.type}|${a.target.toLowerCase()}|${a.conflict_id || ""}`;
910
+ const right = `${b.status}|${b.source.toLowerCase()}|${b.type}|${b.target.toLowerCase()}|${b.conflict_id || ""}`;
911
+ return left.localeCompare(right);
912
+ });
913
+ const nodeList = [...nodes.values()].sort((a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase()));
914
+ return {
915
+ nodes: nodeList,
916
+ edges,
917
+ status_counts,
918
+ updated_at: latestTimestampMs > 0 ? new Date(latestTimestampMs).toISOString() : new Date().toISOString(),
919
+ };
920
+ }
921
+ function listConflicts(args) {
922
+ const status = args?.status || "pending";
923
+ const limit = typeof args?.limit === "number" && Number.isFinite(args.limit) && args.limit > 0
924
+ ? Math.min(500, Math.floor(args.limit))
925
+ : 50;
926
+ const queue = loadConflictQueue();
927
+ const filtered = queue
928
+ .filter(item => status === "all" ? true : item.status === status)
929
+ .sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
930
+ return filtered.slice(0, limit).map(toConflictSummary);
931
+ }
932
+ function getConflictStats() {
933
+ const queue = loadConflictQueue();
934
+ let pending = 0;
935
+ let accepted = 0;
936
+ let rejected = 0;
937
+ for (const item of queue) {
938
+ if (item.status === "pending")
939
+ pending += 1;
940
+ if (item.status === "accepted")
941
+ accepted += 1;
942
+ if (item.status === "rejected")
943
+ rejected += 1;
944
+ }
945
+ return { pending, accepted, rejected };
946
+ }
947
+ async function resolveConflict(args) {
948
+ const conflictId = (args.conflictId || "").trim();
949
+ if (!conflictId) {
950
+ return { success: false, reason: "conflict_id_empty" };
951
+ }
952
+ const queue = loadConflictQueue();
953
+ const index = queue.findIndex(item => item.conflict_id === conflictId);
954
+ if (index < 0) {
955
+ return { success: false, reason: "conflict_not_found" };
956
+ }
957
+ const current = queue[index];
958
+ if (current.status !== "pending") {
959
+ return { success: false, reason: `conflict_already_${current.status}` };
960
+ }
961
+ const now = new Date().toISOString();
962
+ if (args.action === "reject") {
963
+ current.status = "rejected";
964
+ current.updated_at = now;
965
+ current.resolution = {
966
+ action: "reject",
967
+ note: args.note,
968
+ resolved_at: now,
969
+ };
970
+ queue[index] = current;
971
+ saveConflictQueue(queue);
972
+ options.logger.info(`graph_conflict_rejected id=${conflictId}`);
973
+ triggerWikiMaintenance({
974
+ type: "conflict_rejected",
975
+ message: "Conflict rejected; canonical graph unchanged",
976
+ sourceEventId: current.source_event_id,
977
+ conflictId,
978
+ entities: current.candidate.entities,
979
+ relationTypes: (current.candidate.relations || []).map(item => item.type),
980
+ });
981
+ return { success: true };
982
+ }
983
+ const supersedeEntries = current.existing_relation_keys.map(key => ({
984
+ relation_key: key,
985
+ superseded_at: now,
986
+ conflict_id: conflictId,
987
+ note: args.note || "conflict_accept_replace",
988
+ }));
989
+ appendSupersededRelationEntries(supersedeEntries);
990
+ appendSupersedeMutationLogs(supersedeEntries, conflictId, current.source_event_id);
991
+ const record = {
992
+ id: generateGraphId(),
993
+ summary: (current.candidate.summary || "").trim() || buildFallbackSummary(current.candidate.entities, current.candidate.relations),
994
+ source_text_nav: current.candidate.source_text_nav || {
995
+ layer: current.source_layer,
996
+ session_id: current.session_id,
997
+ source_file: current.source_file || "unknown",
998
+ source_memory_id: current.source_event_id,
999
+ source_event_id: current.source_event_id,
1000
+ },
1001
+ source_event_id: current.source_event_id,
1002
+ source_layer: current.source_layer,
1003
+ archive_event_id: current.source_layer === "archive_event" ? current.source_event_id : undefined,
1004
+ session_id: current.session_id,
1005
+ source_file: current.source_file,
1006
+ timestamp: now,
1007
+ entities: current.candidate.entities,
1008
+ entity_types: current.candidate.entity_types,
1009
+ relations: current.candidate.relations,
1010
+ gate_source: current.candidate.gate_source,
1011
+ event_type: current.candidate.event_type,
1012
+ schema_version: "1.0.0",
1013
+ confidence: current.candidate.confidence,
1014
+ };
1015
+ if (shouldRejectStaleSummary(record)) {
1016
+ return { success: false, reason: "stale_summary_after_update" };
1017
+ }
1018
+ try {
1019
+ appendGraphRecord({
1020
+ record,
1021
+ conflictId,
1022
+ note: args.note,
1023
+ });
1024
+ }
1025
+ catch (error) {
1026
+ return { success: false, reason: String(error) };
1027
+ }
1028
+ current.status = "accepted";
1029
+ current.updated_at = now;
1030
+ current.resolution = {
1031
+ action: "accept",
1032
+ note: args.note,
1033
+ resolved_at: now,
1034
+ applied_record_id: record.id,
1035
+ };
1036
+ queue[index] = current;
1037
+ saveConflictQueue(queue);
1038
+ options.logger.info(`graph_conflict_accepted id=${conflictId} record_id=${record.id}`);
1039
+ triggerWikiMaintenance({
1040
+ type: "conflict_accepted",
1041
+ message: "Conflict accepted; canonical graph updated",
1042
+ sourceEventId: current.source_event_id,
1043
+ conflictId,
1044
+ entities: current.candidate.entities,
1045
+ relationTypes: (current.candidate.relations || []).map(item => item.type),
1046
+ });
1047
+ return { success: true, appliedRecordId: record.id };
1048
+ }
1049
+ return {
1050
+ append,
1051
+ loadAll,
1052
+ loadByArchiveEventId,
1053
+ loadByEntity,
1054
+ getStats,
1055
+ exportGraphView,
1056
+ listConflicts,
1057
+ resolveConflict,
1058
+ getConflictStats,
1059
+ };
1060
+ }
1061
+ //# sourceMappingURL=graph_memory_store.js.map