openclaw-cortex-memory 0.1.0-Alpha.30 → 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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -6
  3. package/SIGNATURE.md +7 -0
  4. package/SKILL.md +18 -3
  5. package/dist/index.d.ts +8 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +148 -6
  8. package/dist/index.js.map +1 -1
  9. package/dist/openclaw.plugin.json +120 -4
  10. package/dist/src/engine/memory_engine.d.ts +5 -1
  11. package/dist/src/engine/memory_engine.d.ts.map +1 -1
  12. package/dist/src/engine/ts_engine.d.ts +116 -0
  13. package/dist/src/engine/ts_engine.d.ts.map +1 -1
  14. package/dist/src/engine/ts_engine.js +417 -102
  15. package/dist/src/engine/ts_engine.js.map +1 -1
  16. package/dist/src/engine/types.d.ts +17 -0
  17. package/dist/src/engine/types.d.ts.map +1 -1
  18. package/dist/src/graph/ontology.d.ts +23 -1
  19. package/dist/src/graph/ontology.d.ts.map +1 -1
  20. package/dist/src/graph/ontology.js +743 -70
  21. package/dist/src/graph/ontology.js.map +1 -1
  22. package/dist/src/quality/llm_output_validator.d.ts +20 -2
  23. package/dist/src/quality/llm_output_validator.d.ts.map +1 -1
  24. package/dist/src/quality/llm_output_validator.js +296 -41
  25. package/dist/src/quality/llm_output_validator.js.map +1 -1
  26. package/dist/src/store/archive_store.d.ts +8 -0
  27. package/dist/src/store/archive_store.d.ts.map +1 -1
  28. package/dist/src/store/archive_store.js +244 -84
  29. package/dist/src/store/archive_store.js.map +1 -1
  30. package/dist/src/store/graph_memory_store.d.ts +72 -2
  31. package/dist/src/store/graph_memory_store.d.ts.map +1 -1
  32. package/dist/src/store/graph_memory_store.js +723 -50
  33. package/dist/src/store/graph_memory_store.js.map +1 -1
  34. package/dist/src/store/read_store.d.ts +3 -0
  35. package/dist/src/store/read_store.d.ts.map +1 -1
  36. package/dist/src/store/read_store.js +1004 -209
  37. package/dist/src/store/read_store.js.map +1 -1
  38. package/dist/src/store/vector_store.d.ts +1 -0
  39. package/dist/src/store/vector_store.d.ts.map +1 -1
  40. package/dist/src/store/vector_store.js +1 -0
  41. package/dist/src/store/vector_store.js.map +1 -1
  42. package/dist/src/store/write_store.d.ts +2 -0
  43. package/dist/src/store/write_store.d.ts.map +1 -1
  44. package/dist/src/store/write_store.js +45 -3
  45. package/dist/src/store/write_store.js.map +1 -1
  46. package/dist/src/sync/session_sync.d.ts +20 -1
  47. package/dist/src/sync/session_sync.d.ts.map +1 -1
  48. package/dist/src/sync/session_sync.js +1810 -161
  49. package/dist/src/sync/session_sync.js.map +1 -1
  50. package/dist/src/wiki/wiki_linter.d.ts +25 -0
  51. package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
  52. package/dist/src/wiki/wiki_linter.js +268 -0
  53. package/dist/src/wiki/wiki_linter.js.map +1 -0
  54. package/dist/src/wiki/wiki_logger.d.ts +10 -0
  55. package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
  56. package/dist/src/wiki/wiki_logger.js +78 -0
  57. package/dist/src/wiki/wiki_logger.js.map +1 -0
  58. package/dist/src/wiki/wiki_maintainer.d.ts +36 -0
  59. package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
  60. package/dist/src/wiki/wiki_maintainer.js +38 -0
  61. package/dist/src/wiki/wiki_maintainer.js.map +1 -0
  62. package/dist/src/wiki/wiki_projector.d.ts +33 -0
  63. package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
  64. package/dist/src/wiki/wiki_projector.js +633 -0
  65. package/dist/src/wiki/wiki_projector.js.map +1 -0
  66. package/dist/src/wiki/wiki_queue.d.ts +29 -0
  67. package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
  68. package/dist/src/wiki/wiki_queue.js +137 -0
  69. package/dist/src/wiki/wiki_queue.js.map +1 -0
  70. package/openclaw.plugin.json +120 -4
  71. package/package.json +7 -2
  72. package/schema/graph.schema.yaml +188 -33
  73. package/scripts/install.js +27 -0
  74. package/skills/cortex-memory/SKILL.md +49 -0
  75. package/skills/cortex-memory/references/agent-manual.md +115 -0
  76. package/skills/cortex-memory/references/configuration.md +92 -0
  77. package/skills/cortex-memory/references/publish-checklist.md +46 -0
  78. package/skills/cortex-memory/references/system-prompt-template.md +27 -0
  79. package/skills/cortex-memory/references/tools.md +181 -0
  80. package/skills/cortex-memory/scripts/smoke-check.ps1 +56 -0
@@ -39,8 +39,10 @@ const path = __importStar(require("path"));
39
39
  const ontology_1 = require("../graph/ontology");
40
40
  const http_post_1 = require("../net/http_post");
41
41
  const llm_output_validator_1 = require("../quality/llm_output_validator");
42
+ const wiki_projector_1 = require("../wiki/wiki_projector");
43
+ const wiki_linter_1 = require("../wiki/wiki_linter");
42
44
  const PROMPT_VERSIONS = {
43
- write_gate: "write-gate.v1.2.0",
45
+ write_gate: "write-gate.v1.7.9",
44
46
  session_end_write: "session-end-write.v1.2.0",
45
47
  read_fusion: "read-fusion.v1.2.0",
46
48
  };
@@ -160,39 +162,12 @@ function createTsEngine(deps) {
160
162
  : (typeof record.text === "string" ? record.text.trim() : "");
161
163
  return content;
162
164
  }
165
+ const sourceText = typeof record.source_text === "string" ? record.source_text.trim() : "";
166
+ if (sourceText) {
167
+ return sourceText;
168
+ }
163
169
  const summary = typeof record.summary === "string" ? record.summary.trim() : "";
164
- const eventType = typeof record.event_type === "string" ? record.event_type.trim() : "insight";
165
- const outcome = typeof record.outcome === "string" ? record.outcome.trim() : "";
166
- const sourceFile = typeof record.source_file === "string" ? record.source_file.trim() : "";
167
- const actor = typeof record.actor === "string" ? record.actor.trim() : "";
168
- const entities = Array.isArray(record.entities)
169
- ? record.entities.filter(v => typeof v === "string").map(v => String(v).trim()).filter(Boolean)
170
- : [];
171
- const relations = Array.isArray(record.relations)
172
- ? record.relations
173
- .map(v => {
174
- if (!v || typeof v !== "object")
175
- return "";
176
- const relation = v;
177
- const source = typeof relation.source === "string" ? relation.source.trim() : "";
178
- const target = typeof relation.target === "string" ? relation.target.trim() : "";
179
- const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
180
- if (!source || !target)
181
- return "";
182
- return `${source} -[${type}]-> ${target}`;
183
- })
184
- .filter(Boolean)
185
- : [];
186
- const lines = [
187
- `event_type: ${eventType}`,
188
- `summary: ${summary}`,
189
- `outcome: ${outcome}`,
190
- `entities: ${entities.join(", ")}`,
191
- `source_file: ${sourceFile}`,
192
- `actor: ${actor}`,
193
- relations.length > 0 ? `relations: ${relations.join(" ; ")}` : "",
194
- ].filter(Boolean);
195
- return lines.join("\n").trim();
170
+ return summary;
196
171
  }
197
172
  function splitTextChunks(text, chunkSize, chunkOverlap) {
198
173
  const normalizedSize = Number.isFinite(chunkSize) && chunkSize >= 200 ? Math.floor(chunkSize) : 600;
@@ -245,6 +220,32 @@ function createTsEngine(deps) {
245
220
  }
246
221
  return output;
247
222
  }
223
+ function pickEvidenceChunks(chunks, maxCount) {
224
+ if (!chunks.length || maxCount <= 0)
225
+ return [];
226
+ if (chunks.length <= maxCount)
227
+ return chunks;
228
+ const picked = new Map();
229
+ picked.set(chunks[0].index, chunks[0]);
230
+ if (maxCount >= 2) {
231
+ const mid = chunks[Math.floor(chunks.length / 2)];
232
+ picked.set(mid.index, mid);
233
+ }
234
+ if (maxCount >= 3) {
235
+ const last = chunks[chunks.length - 1];
236
+ picked.set(last.index, last);
237
+ }
238
+ if (picked.size < maxCount) {
239
+ for (const chunk of chunks) {
240
+ if (!picked.has(chunk.index)) {
241
+ picked.set(chunk.index, chunk);
242
+ }
243
+ if (picked.size >= maxCount)
244
+ break;
245
+ }
246
+ }
247
+ return [...picked.values()].sort((a, b) => a.index - b.index).slice(0, maxCount);
248
+ }
248
249
  function upsertJsonFile(filePath, patch) {
249
250
  const current = parseJsonFile(filePath) || {};
250
251
  const next = { ...current, ...patch };
@@ -468,6 +469,27 @@ function createTsEngine(deps) {
468
469
  : typeof rawArgs.event?.outcome === "string"
469
470
  ? String(rawArgs.event.outcome)
470
471
  : "";
472
+ const causeValue = typeof rawArgs.cause === "string"
473
+ ? rawArgs.cause
474
+ : typeof rawArgs.input?.cause === "string"
475
+ ? String(rawArgs.input.cause)
476
+ : typeof rawArgs.event?.cause === "string"
477
+ ? String(rawArgs.event.cause)
478
+ : "";
479
+ const processValue = typeof rawArgs.process === "string"
480
+ ? rawArgs.process
481
+ : typeof rawArgs.input?.process === "string"
482
+ ? String(rawArgs.input.process)
483
+ : typeof rawArgs.event?.process === "string"
484
+ ? String(rawArgs.event.process)
485
+ : "";
486
+ const resultValue = typeof rawArgs.result === "string"
487
+ ? rawArgs.result
488
+ : typeof rawArgs.input?.result === "string"
489
+ ? String(rawArgs.input.result)
490
+ : typeof rawArgs.event?.result === "string"
491
+ ? String(rawArgs.event.result)
492
+ : "";
471
493
  const entityTypesInput = typeof rawArgs.entity_types === "object" && rawArgs.entity_types !== null
472
494
  ? rawArgs.entity_types
473
495
  : typeof rawArgs.input?.entity_types === "object"
@@ -490,6 +512,9 @@ function createTsEngine(deps) {
490
512
  {
491
513
  event_type: "manual_event",
492
514
  summary: normalizedSummary,
515
+ cause: causeValue.trim() || normalizedSummary,
516
+ process: processValue.trim() || normalizedSummary,
517
+ result: resultValue.trim() || outcomeValue.trim() || normalizedSummary,
493
518
  entities,
494
519
  relations,
495
520
  entity_types: entityTypes,
@@ -506,12 +531,23 @@ function createTsEngine(deps) {
506
531
  }
507
532
  const storedId = result.stored[0].id;
508
533
  if (deps.graphMemoryStore && entities.length > 0 && Object.keys(entityTypes).length > 0 && relations.length > 0) {
534
+ const graphSummary = normalizedSummary.toLowerCase().includes("entities:")
535
+ ? normalizedSummary
536
+ : `${normalizedSummary} Entities: ${entities.join(", ")}.`;
509
537
  const graphResult = await deps.graphMemoryStore.append({
510
538
  sourceEventId: storedId,
511
539
  sourceLayer: "archive_event",
512
540
  archiveEventId: storedId,
513
541
  sessionId: "manual",
514
542
  sourceFile: "ts_store_event",
543
+ summary: graphSummary,
544
+ source_text_nav: {
545
+ layer: "archive_event",
546
+ session_id: "manual",
547
+ source_file: "ts_store_event",
548
+ source_memory_id: storedId,
549
+ source_event_id: storedId,
550
+ },
515
551
  eventType: "manual_event",
516
552
  entities,
517
553
  entity_types: entityTypes,
@@ -544,30 +580,19 @@ function createTsEngine(deps) {
544
580
  const pathTo = typeof args.path_to === "string" && args.path_to.trim() ? args.path_to.trim() : "";
545
581
  const maxDepth = Math.max(2, Math.min(4, typeof args.max_depth === "number" ? Math.floor(args.max_depth) : 3));
546
582
  const graphMemoryPath = path.join(deps.memoryRoot, "graph", "memory.jsonl");
583
+ const projectionIndexPath = path.join(deps.memoryRoot, "wiki", ".projection_index.json");
547
584
  const nodes = new Map();
548
585
  const edges = [];
549
- const adjacency = new Map();
550
586
  const pathAdjacency = new Map();
551
587
  const relationTypeDistribution = new Map();
552
588
  const edgeKeySet = new Set();
553
- function pushEdge(source, target, type) {
554
- const key = `${source}|${type}|${target}`;
555
- if (edgeKeySet.has(key)) {
556
- return;
557
- }
558
- edgeKeySet.add(key);
559
- edges.push({ source, target, type });
560
- relationTypeDistribution.set(type, (relationTypeDistribution.get(type) || 0) + 1);
561
- if (!adjacency.has(source)) {
562
- adjacency.set(source, []);
563
- }
564
- adjacency.get(source)?.push({ next: target, edge: { source, target, type } });
565
- if (!adjacency.has(target)) {
566
- adjacency.set(target, []);
567
- }
568
- adjacency.get(target)?.push({ next: source, edge: { source, target, type } });
589
+ const allEdges = [];
590
+ function relationKeyOf(source, type, target) {
591
+ return `${source.trim().toLowerCase()}|${type.trim().toLowerCase()}|${target.trim().toLowerCase()}`;
569
592
  }
570
- function pushPathEdge(source, target, type) {
593
+ function pushPathEdge(edge) {
594
+ const source = edge.source;
595
+ const target = edge.target;
571
596
  if (!pathAdjacency.has(source)) {
572
597
  pathAdjacency.set(source, []);
573
598
  }
@@ -575,69 +600,170 @@ function createTsEngine(deps) {
575
600
  pathAdjacency.set(target, []);
576
601
  }
577
602
  if (direction === "incoming") {
578
- pathAdjacency.get(target)?.push({ next: source, edge: { source, target, type } });
603
+ pathAdjacency.get(target)?.push({ next: source, edge });
579
604
  }
580
605
  else if (direction === "outgoing") {
581
- pathAdjacency.get(source)?.push({ next: target, edge: { source, target, type } });
606
+ pathAdjacency.get(source)?.push({ next: target, edge });
582
607
  }
583
608
  else {
584
- pathAdjacency.get(source)?.push({ next: target, edge: { source, target, type } });
585
- pathAdjacency.get(target)?.push({ next: source, edge: { source, target, type } });
609
+ pathAdjacency.get(source)?.push({ next: target, edge });
610
+ pathAdjacency.get(target)?.push({ next: source, edge });
586
611
  }
587
612
  }
588
- const graphRecords = fs.existsSync(graphMemoryPath) ? readJsonl(graphMemoryPath) : [];
589
- for (const record of graphRecords) {
590
- const entities = Array.isArray(record.entities) ? record.entities : [];
591
- const named = entities.map((e) => (typeof e === "string" ? e.trim() : "")).filter(Boolean);
592
- const relations = Array.isArray(record.relations) ? record.relations : [];
593
- let explicitMatched = false;
594
- for (const relationRaw of relations) {
595
- if (typeof relationRaw !== "object" || relationRaw === null) {
596
- continue;
597
- }
598
- const relation = relationRaw;
599
- const source = typeof relation.source === "string" ? relation.source.trim() : "";
600
- const target = typeof relation.target === "string" ? relation.target.trim() : "";
601
- const type = (0, ontology_1.normalizeRelationType)(typeof relation.type === "string" && relation.type.trim() ? relation.type.trim() : "related_to", graphSchema);
602
- if (!source || !target) {
603
- continue;
604
- }
605
- if (relFilter && type !== relFilter) {
613
+ function normalizeStatus(value) {
614
+ const token = (value || "").trim().toLowerCase();
615
+ if (token === "pending" || token === "pending_conflict")
616
+ return "pending_conflict";
617
+ if (token === "superseded")
618
+ return "superseded";
619
+ if (token === "rejected")
620
+ return "rejected";
621
+ return "active";
622
+ }
623
+ const viewData = deps.graphMemoryStore?.exportGraphView();
624
+ if (viewData && Array.isArray(viewData.edges)) {
625
+ for (const edge of viewData.edges) {
626
+ const source = typeof edge.source === "string" ? edge.source.trim() : "";
627
+ const target = typeof edge.target === "string" ? edge.target.trim() : "";
628
+ const type = (0, ontology_1.normalizeRelationType)(typeof edge.type === "string" ? edge.type : "related_to", graphSchema);
629
+ if (!source || !target)
606
630
  continue;
631
+ const relationKey = typeof edge.relation_key === "string" && edge.relation_key.trim()
632
+ ? edge.relation_key.trim().toLowerCase()
633
+ : relationKeyOf(source, type, target);
634
+ allEdges.push({
635
+ source,
636
+ target,
637
+ type,
638
+ fact_status: normalizeStatus(typeof edge.status === "string" ? edge.status : "active"),
639
+ relation_key: relationKey,
640
+ source_event_id: typeof edge.source_event_id === "string" ? edge.source_event_id : undefined,
641
+ evidence_span: typeof edge.evidence_span === "string" ? edge.evidence_span : undefined,
642
+ confidence: typeof edge.confidence === "number" ? edge.confidence : undefined,
643
+ conflict_id: typeof edge.conflict_id === "string" ? edge.conflict_id : undefined,
644
+ evidence_ids: [],
645
+ });
646
+ }
647
+ }
648
+ if (allEdges.length === 0) {
649
+ const graphRecords = deps.graphMemoryStore
650
+ ? deps.graphMemoryStore.loadAll()
651
+ : (fs.existsSync(graphMemoryPath) ? readJsonl(graphMemoryPath) : []);
652
+ for (const record of graphRecords) {
653
+ const sourceEventId = typeof record.source_event_id === "string" ? record.source_event_id.trim() : "";
654
+ const relations = Array.isArray(record.relations) ? record.relations : [];
655
+ for (const relationRaw of relations) {
656
+ if (typeof relationRaw !== "object" || relationRaw === null)
657
+ continue;
658
+ const relation = relationRaw;
659
+ const source = typeof relation.source === "string" ? relation.source.trim() : "";
660
+ const target = typeof relation.target === "string" ? relation.target.trim() : "";
661
+ const type = (0, ontology_1.normalizeRelationType)(typeof relation.type === "string" && relation.type.trim() ? relation.type.trim() : "related_to", graphSchema);
662
+ if (!source || !target)
663
+ continue;
664
+ allEdges.push({
665
+ source,
666
+ target,
667
+ type,
668
+ fact_status: "active",
669
+ relation_key: relationKeyOf(source, type, target),
670
+ source_event_id: sourceEventId || undefined,
671
+ evidence_span: typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : undefined,
672
+ confidence: typeof relation.confidence === "number" ? relation.confidence : undefined,
673
+ evidence_ids: [],
674
+ });
607
675
  }
608
- pushPathEdge(source, target, type);
609
- const outgoingMatch = source === entity;
610
- const incomingMatch = target === entity;
611
- const directionMatched = direction === "both" ? (outgoingMatch || incomingMatch)
612
- : direction === "outgoing" ? outgoingMatch
613
- : incomingMatch;
614
- if (!directionMatched) {
615
- continue;
676
+ }
677
+ }
678
+ const projectionIndex = parseJsonFile(projectionIndexPath);
679
+ const entityWikiPathByName = new Map();
680
+ const topicWikiPathByType = new Map();
681
+ const projectionEntities = Array.isArray(projectionIndex?.entities) ? projectionIndex.entities : [];
682
+ const projectionTopics = Array.isArray(projectionIndex?.topics) ? projectionIndex.topics : [];
683
+ for (const item of projectionEntities) {
684
+ if (!item || typeof item !== "object")
685
+ continue;
686
+ const row = item;
687
+ const name = typeof row.name === "string" ? row.name.trim() : "";
688
+ const wikiPath = typeof row.path === "string" ? row.path.trim() : "";
689
+ if (!name || !wikiPath)
690
+ continue;
691
+ entityWikiPathByName.set(name.toLowerCase(), wikiPath);
692
+ }
693
+ for (const item of projectionTopics) {
694
+ if (!item || typeof item !== "object")
695
+ continue;
696
+ const row = item;
697
+ const topic = typeof row.type === "string" ? row.type.trim() : "";
698
+ const wikiPath = typeof row.path === "string" ? row.path.trim() : "";
699
+ if (!topic || !wikiPath)
700
+ continue;
701
+ topicWikiPathByType.set(topic.toLowerCase(), wikiPath);
702
+ }
703
+ function statusAnchor(status) {
704
+ if (status === "active")
705
+ return "current-facts";
706
+ if (status === "pending_conflict")
707
+ return "disputed-facts";
708
+ return "history";
709
+ }
710
+ function buildEvidenceIdsForEdge(edge) {
711
+ const ids = [
712
+ `graph:relation:${edge.relation_key}`,
713
+ edge.source_event_id ? `graph:event:${edge.source_event_id}` : "",
714
+ edge.evidence_span ? `graph:evidence:${edge.source_event_id || edge.relation_key}` : "",
715
+ edge.conflict_id ? `graph:conflict:${edge.conflict_id}` : "",
716
+ ];
717
+ const anchor = statusAnchor(edge.fact_status);
718
+ for (const name of [edge.source, edge.target]) {
719
+ const wikiPath = entityWikiPathByName.get(name.toLowerCase());
720
+ if (wikiPath) {
721
+ ids.push(`wiki:${wikiPath}#${anchor}`);
616
722
  }
617
- explicitMatched = true;
618
- if (!nodes.has(source))
619
- nodes.set(source, { id: source, type: "entity" });
620
- if (!nodes.has(target))
621
- nodes.set(target, { id: target, type: "entity" });
622
- pushEdge(source, target, type);
623
723
  }
624
- if (explicitMatched) {
724
+ const topicPath = topicWikiPathByType.get(edge.type.toLowerCase());
725
+ if (topicPath) {
726
+ ids.push(`wiki:${topicPath}#relations`);
727
+ }
728
+ return [...new Set(ids.filter(Boolean))];
729
+ }
730
+ let explicitMatched = false;
731
+ for (const edge of allEdges) {
732
+ if (relFilter && edge.type !== relFilter) {
625
733
  continue;
626
734
  }
627
- if (!named.includes(entity)) {
735
+ const pathEligible = edge.fact_status === "active" || edge.fact_status === "pending_conflict";
736
+ if (pathEligible) {
737
+ pushPathEdge(edge);
738
+ }
739
+ const outgoingMatch = edge.source === entity;
740
+ const incomingMatch = edge.target === entity;
741
+ const directionMatched = direction === "both" ? (outgoingMatch || incomingMatch)
742
+ : direction === "outgoing" ? outgoingMatch
743
+ : incomingMatch;
744
+ if (!directionMatched) {
628
745
  continue;
629
746
  }
630
- for (const name of named) {
631
- if (!nodes.has(name)) {
632
- nodes.set(name, { id: name, type: "entity" });
633
- }
747
+ explicitMatched = true;
748
+ const edgeKey = `${edge.relation_key}|${edge.fact_status}|${edge.conflict_id || ""}`;
749
+ if (edgeKeySet.has(edgeKey)) {
750
+ continue;
634
751
  }
635
- for (const name of named) {
636
- if (name !== entity) {
637
- if (!relFilter || relFilter === "co_occurrence") {
638
- pushEdge(entity, name, "co_occurrence");
639
- }
640
- }
752
+ edgeKeySet.add(edgeKey);
753
+ const enrichedEdge = {
754
+ ...edge,
755
+ evidence_ids: buildEvidenceIdsForEdge(edge),
756
+ };
757
+ edges.push(enrichedEdge);
758
+ relationTypeDistribution.set(enrichedEdge.type, (relationTypeDistribution.get(enrichedEdge.type) || 0) + 1);
759
+ if (!nodes.has(enrichedEdge.source))
760
+ nodes.set(enrichedEdge.source, { id: enrichedEdge.source, type: "entity" });
761
+ if (!nodes.has(enrichedEdge.target))
762
+ nodes.set(enrichedEdge.target, { id: enrichedEdge.target, type: "entity" });
763
+ }
764
+ if (!explicitMatched) {
765
+ if (!nodes.has(entity)) {
766
+ nodes.set(entity, { id: entity, type: "entity" });
641
767
  }
642
768
  }
643
769
  let graphPath = [];
@@ -671,6 +797,34 @@ function createTsEngine(deps) {
671
797
  }
672
798
  }
673
799
  }
800
+ const statusCounts = {
801
+ active: 0,
802
+ pending_conflict: 0,
803
+ superseded: 0,
804
+ rejected: 0,
805
+ };
806
+ const wikiRefSet = new Set();
807
+ if (entityWikiPathByName.has(entity.toLowerCase())) {
808
+ wikiRefSet.add(`wiki/${entityWikiPathByName.get(entity.toLowerCase())}`);
809
+ }
810
+ for (const edge of edges) {
811
+ statusCounts[edge.fact_status] += 1;
812
+ for (const name of [edge.source, edge.target]) {
813
+ const wikiPath = entityWikiPathByName.get(name.toLowerCase());
814
+ if (wikiPath) {
815
+ wikiRefSet.add(`wiki/${wikiPath}`);
816
+ }
817
+ }
818
+ const topicPath = topicWikiPathByType.get(edge.type.toLowerCase());
819
+ if (topicPath) {
820
+ wikiRefSet.add(`wiki/${topicPath}`);
821
+ }
822
+ }
823
+ const evidenceIds = [...new Set(edges.flatMap(edge => edge.evidence_ids).filter(Boolean))];
824
+ const conflictEdges = edges.filter(edge => edge.fact_status === "pending_conflict" || edge.fact_status === "rejected");
825
+ const pendingCount = conflictEdges.filter(edge => edge.fact_status === "pending_conflict").length;
826
+ const rejectedCount = conflictEdges.filter(edge => edge.fact_status === "rejected").length;
827
+ const conflictIds = [...new Set(conflictEdges.map(edge => edge.conflict_id).filter((item) => !!item))];
674
828
  return {
675
829
  success: true,
676
830
  data: {
@@ -679,13 +833,61 @@ function createTsEngine(deps) {
679
833
  dir: direction,
680
834
  nodes: [...nodes.values()],
681
835
  edges,
836
+ status_counts: statusCounts,
682
837
  path_to: pathTo || "",
683
838
  max_depth: maxDepth,
684
839
  path: graphPath,
840
+ wiki_refs: [...wikiRefSet],
841
+ evidence_ids: evidenceIds,
842
+ conflict_hint: conflictEdges.length > 0
843
+ ? {
844
+ pending_count: pendingCount,
845
+ rejected_count: rejectedCount,
846
+ conflict_ids: conflictIds,
847
+ suggestion: pendingCount > 0
848
+ ? "Pending graph conflicts found. Call list_graph_conflicts first, then resolve_graph_conflict(accept/reject)."
849
+ : "Rejected candidate facts exist. Submit stronger evidence if you want to update the graph.",
850
+ }
851
+ : undefined,
685
852
  relation_type_distribution: [...relationTypeDistribution.entries()].map(([type, count]) => ({ type, count })),
686
853
  },
687
854
  };
688
855
  }
856
+ async function exportGraphView(args, _context) {
857
+ if (!deps.graphMemoryStore) {
858
+ return { success: false, error: "Graph memory store is not available." };
859
+ }
860
+ const writeSnapshot = args.write_snapshot !== false;
861
+ const view = deps.graphMemoryStore.exportGraphView();
862
+ const projection = writeSnapshot
863
+ ? (0, wiki_projector_1.writeGraphViewProjection)({
864
+ memoryRoot: deps.memoryRoot,
865
+ view,
866
+ })
867
+ : null;
868
+ return {
869
+ success: true,
870
+ data: {
871
+ ...view,
872
+ snapshot_written: writeSnapshot,
873
+ projection,
874
+ },
875
+ };
876
+ }
877
+ async function lintMemoryWiki(_args, _context) {
878
+ if (!deps.graphMemoryStore) {
879
+ return { success: false, error: "Graph memory store is not available." };
880
+ }
881
+ const graphView = deps.graphMemoryStore.exportGraphView();
882
+ const report = (0, wiki_linter_1.lintMemoryWiki)({
883
+ memoryRoot: deps.memoryRoot,
884
+ graphView,
885
+ });
886
+ return {
887
+ success: true,
888
+ data: report,
889
+ };
890
+ }
689
891
  async function deleteMemory(args, _context) {
690
892
  const targetId = args.memory_id?.trim();
691
893
  if (!targetId) {
@@ -814,6 +1016,16 @@ function createTsEngine(deps) {
814
1016
  if (layer === "all" || layer === "archive") {
815
1017
  targetFiles.push({ layer: "archive", filePath: archivePath });
816
1018
  }
1019
+ const vectorJsonlPath = path.join(deps.memoryRoot, "vector", "lancedb_events.jsonl");
1020
+ const vectorJsonlRecords = readJsonl(vectorJsonlPath);
1021
+ const vectorSourceIndex = new Set();
1022
+ for (const row of vectorJsonlRecords) {
1023
+ const rowLayer = row.layer === "active" || row.layer === "archive" ? row.layer : "";
1024
+ const sourceMemoryId = typeof row.source_memory_id === "string" ? row.source_memory_id.trim() : "";
1025
+ if (!rowLayer || !sourceMemoryId)
1026
+ continue;
1027
+ vectorSourceIndex.add(`${rowLayer}|${sourceMemoryId}`);
1028
+ }
817
1029
  const queue = [];
818
1030
  const recordsByFile = new Map();
819
1031
  for (const target of targetFiles) {
@@ -827,6 +1039,7 @@ function createTsEngine(deps) {
827
1039
  }
828
1040
  const status = typeof record.embedding_status === "string" ? record.embedding_status.trim() : "";
829
1041
  const hasEmbedding = Array.isArray(record.embedding) && record.embedding.length > 0;
1042
+ const hasVectorRows = vectorSourceIndex.has(`${target.layer}|${id}`);
830
1043
  if (forceRebuild) {
831
1044
  queue.push({ layer: target.layer, filePath: target.filePath, index: i });
832
1045
  continue;
@@ -836,7 +1049,7 @@ function createTsEngine(deps) {
836
1049
  continue;
837
1050
  }
838
1051
  }
839
- else if (status === "ok" || hasEmbedding) {
1052
+ else if ((status === "ok" || hasEmbedding) && hasVectorRows) {
840
1053
  continue;
841
1054
  }
842
1055
  const failCountRaw = failureCountState[id];
@@ -882,7 +1095,48 @@ function createTsEngine(deps) {
882
1095
  }
883
1096
  const chunkSize = deps.vectorChunking?.chunkSize ?? 600;
884
1097
  const chunkOverlap = deps.vectorChunking?.chunkOverlap ?? 100;
885
- const chunks = splitTextChunks(text, chunkSize, chunkOverlap);
1098
+ const evidenceMaxChunks = typeof deps.vectorChunking?.evidenceMaxChunks === "number"
1099
+ ? Math.max(0, Math.min(8, Math.floor(deps.vectorChunking.evidenceMaxChunks)))
1100
+ : 2;
1101
+ let chunks = [];
1102
+ if (item.layer === "archive") {
1103
+ const summaryText = typeof record.summary === "string" ? record.summary.trim() : "";
1104
+ const sourceText = typeof record.source_text === "string" ? record.source_text.trim() : "";
1105
+ const summaryChunk = summaryText
1106
+ ? [{
1107
+ index: 0,
1108
+ start: 0,
1109
+ end: summaryText.length,
1110
+ text: summaryText,
1111
+ source_field: "summary",
1112
+ }]
1113
+ : [];
1114
+ const evidenceChunks = sourceText
1115
+ ? pickEvidenceChunks(splitTextChunks(sourceText, chunkSize, chunkOverlap), evidenceMaxChunks)
1116
+ : [];
1117
+ chunks = [
1118
+ ...summaryChunk,
1119
+ ...evidenceChunks.map((chunk, idx) => ({
1120
+ index: idx + summaryChunk.length,
1121
+ start: chunk.start,
1122
+ end: chunk.end,
1123
+ text: chunk.text,
1124
+ source_field: "evidence",
1125
+ })),
1126
+ ];
1127
+ if (chunks.length === 0 && text) {
1128
+ chunks = splitTextChunks(text, chunkSize, chunkOverlap).map(chunk => ({
1129
+ ...chunk,
1130
+ source_field: "summary",
1131
+ }));
1132
+ }
1133
+ }
1134
+ else {
1135
+ chunks = splitTextChunks(text, chunkSize, chunkOverlap).map(chunk => ({
1136
+ ...chunk,
1137
+ source_field: "summary",
1138
+ }));
1139
+ }
886
1140
  if (chunks.length === 0) {
887
1141
  record.embedding_status = "failed";
888
1142
  failed += 1;
@@ -920,6 +1174,8 @@ function createTsEngine(deps) {
920
1174
  layer: item.layer,
921
1175
  source_memory_id: id,
922
1176
  source_memory_canonical_id: typeof record.canonical_id === "string" ? record.canonical_id : id,
1177
+ source_event_id: typeof record.source_event_id === "string" ? record.source_event_id : id,
1178
+ source_field: chunk.source_field,
923
1179
  outcome: typeof record.outcome === "string" ? record.outcome : "",
924
1180
  entities: Array.isArray(record.entities) ? record.entities.filter(v => typeof v === "string") : [],
925
1181
  relations: Array.isArray(record.relations)
@@ -1222,6 +1478,9 @@ function createTsEngine(deps) {
1222
1478
  const refId = stringField(record, "source_event_id") || stringField(record, "archive_event_id");
1223
1479
  return sourceLayer === "archive_event" && !!refId && archiveIdSet.has(refId);
1224
1480
  }).length;
1481
+ const graphConflictStats = deps.graphMemoryStore
1482
+ ? deps.graphMemoryStore.getConflictStats()
1483
+ : { pending: 0, accepted: 0, rejected: 0 };
1225
1484
  const schemaIssueTotal = Object.values(activeFieldIssues).reduce((sum, n) => sum + n, 0)
1226
1485
  + Object.values(archiveFieldIssues).reduce((sum, n) => sum + n, 0)
1227
1486
  + Object.values(vectorFieldIssues).reduce((sum, n) => sum + n, 0)
@@ -1273,6 +1532,7 @@ function createTsEngine(deps) {
1273
1532
  },
1274
1533
  graph_rules: {
1275
1534
  graph_mutation_log_exists: fs.existsSync(path.join(deps.memoryRoot, "graph", "mutation_log.jsonl")),
1535
+ graph_conflicts: graphConflictStats,
1276
1536
  rules_exists: fs.existsSync(path.join(deps.memoryRoot, "CORTEX_RULES.md")),
1277
1537
  },
1278
1538
  },
@@ -1347,7 +1607,15 @@ function createTsEngine(deps) {
1347
1607
  query,
1348
1608
  topK: typeof topKRaw === "number" && topKRaw > 0 ? Math.floor(topKRaw) : 3,
1349
1609
  });
1350
- return { success: true, data: result.results };
1610
+ return {
1611
+ success: true,
1612
+ data: {
1613
+ results: result.results,
1614
+ vector_semantic_results: result.semantic_results,
1615
+ vector_keyword_results: result.keyword_results,
1616
+ vector_search_strategy: result.strategy,
1617
+ },
1618
+ };
1351
1619
  }
1352
1620
  async function getHotContext(args, _context) {
1353
1621
  const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 20;
@@ -1382,6 +1650,49 @@ function createTsEngine(deps) {
1382
1650
  }
1383
1651
  return { success: true, data: result };
1384
1652
  }
1653
+ async function listGraphConflicts(args, _context) {
1654
+ if (!deps.graphMemoryStore) {
1655
+ return { success: false, error: "Graph memory store is not available." };
1656
+ }
1657
+ const status = args.status === "pending" || args.status === "accepted" || args.status === "rejected" || args.status === "all"
1658
+ ? args.status
1659
+ : "pending";
1660
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit) && args.limit > 0
1661
+ ? Math.min(500, Math.floor(args.limit))
1662
+ : 50;
1663
+ const items = deps.graphMemoryStore.listConflicts({ status, limit });
1664
+ return { success: true, data: { status, count: items.length, items } };
1665
+ }
1666
+ async function resolveGraphConflict(args, _context) {
1667
+ if (!deps.graphMemoryStore) {
1668
+ return { success: false, error: "Graph memory store is not available." };
1669
+ }
1670
+ const conflictId = (args.conflict_id || "").trim();
1671
+ const action = args.action === "accept" || args.action === "reject" ? args.action : null;
1672
+ if (!conflictId) {
1673
+ return { success: false, error: "Invalid input provided. Missing 'conflict_id' parameter." };
1674
+ }
1675
+ if (!action) {
1676
+ return { success: false, error: "Invalid input provided. 'action' must be accept or reject." };
1677
+ }
1678
+ const note = typeof args.note === "string" ? args.note.trim() : undefined;
1679
+ const result = await deps.graphMemoryStore.resolveConflict({
1680
+ conflictId,
1681
+ action,
1682
+ note,
1683
+ });
1684
+ if (!result.success) {
1685
+ return { success: false, error: result.reason || "resolve_graph_conflict_failed" };
1686
+ }
1687
+ return {
1688
+ success: true,
1689
+ data: {
1690
+ conflict_id: conflictId,
1691
+ action,
1692
+ applied_record_id: result.appliedRecordId,
1693
+ },
1694
+ };
1695
+ }
1385
1696
  async function syncMemory(_args, _context) {
1386
1697
  try {
1387
1698
  const result = await deps.sessionSync.syncMemory();
@@ -1475,6 +1786,10 @@ function createTsEngine(deps) {
1475
1786
  getAutoContext,
1476
1787
  storeEvent,
1477
1788
  queryGraph,
1789
+ exportGraphView,
1790
+ lintMemoryWiki,
1791
+ listGraphConflicts,
1792
+ resolveGraphConflict,
1478
1793
  reflectMemory,
1479
1794
  syncMemory,
1480
1795
  promoteMemory,