openclaw-cortex-memory 0.1.0-Alpha.30 → 0.1.0-Alpha.32
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 +46 -12
- package/SIGNATURE.md +7 -0
- package/SKILL.md +18 -3
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -6
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +120 -4
- package/dist/src/engine/memory_engine.d.ts +5 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +116 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +417 -102
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +17 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +23 -1
- package/dist/src/graph/ontology.d.ts.map +1 -1
- package/dist/src/graph/ontology.js +743 -70
- package/dist/src/graph/ontology.js.map +1 -1
- package/dist/src/quality/llm_output_validator.d.ts +20 -2
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -1
- package/dist/src/quality/llm_output_validator.js +296 -41
- package/dist/src/quality/llm_output_validator.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +8 -0
- package/dist/src/store/archive_store.d.ts.map +1 -1
- package/dist/src/store/archive_store.js +244 -84
- package/dist/src/store/archive_store.js.map +1 -1
- package/dist/src/store/graph_memory_store.d.ts +72 -2
- package/dist/src/store/graph_memory_store.d.ts.map +1 -1
- package/dist/src/store/graph_memory_store.js +723 -50
- package/dist/src/store/graph_memory_store.js.map +1 -1
- package/dist/src/store/read_store.d.ts +3 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1004 -209
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +1 -0
- package/dist/src/store/vector_store.d.ts.map +1 -1
- package/dist/src/store/vector_store.js +1 -0
- package/dist/src/store/vector_store.js.map +1 -1
- package/dist/src/store/write_store.d.ts +2 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +45 -3
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +20 -1
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +1810 -161
- package/dist/src/sync/session_sync.js.map +1 -1
- 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 +120 -4
- package/package.json +8 -4
- package/schema/graph.schema.yaml +188 -33
- 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
|
@@ -38,6 +38,12 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const module_1 = require("module");
|
|
40
40
|
const http_post_1 = require("../net/http_post");
|
|
41
|
+
function graphRelationKey(relation) {
|
|
42
|
+
const source = (relation.source || "").trim().toLowerCase();
|
|
43
|
+
const type = (relation.type || "related_to").trim().toLowerCase();
|
|
44
|
+
const target = (relation.target || "").trim().toLowerCase();
|
|
45
|
+
return `${source}|${type}|${target}`;
|
|
46
|
+
}
|
|
41
47
|
function buildEntityGraphSummaryDocs(graphDocs) {
|
|
42
48
|
const entityEdges = new Map();
|
|
43
49
|
const entityLatestTs = new Map();
|
|
@@ -339,8 +345,17 @@ function parseJsonlFile(filePath, sourceLabel, logger) {
|
|
|
339
345
|
try {
|
|
340
346
|
const parsed = JSON.parse(trimmed);
|
|
341
347
|
const summaryText = typeof parsed.summary === "string" ? parsed.summary.trim() : "";
|
|
348
|
+
const causeText = typeof parsed.cause === "string" ? parsed.cause.trim() : "";
|
|
349
|
+
const processText = typeof parsed.process === "string" ? parsed.process.trim() : "";
|
|
350
|
+
const resultText = typeof parsed.result === "string" ? parsed.result.trim() : "";
|
|
342
351
|
const sourceText = typeof parsed.source_text === "string" ? parsed.source_text.trim() : "";
|
|
343
|
-
const
|
|
352
|
+
const activeContent = typeof parsed.content === "string" ? parsed.content.trim() : "";
|
|
353
|
+
const text = [
|
|
354
|
+
summaryText,
|
|
355
|
+
causeText ? `cause: ${causeText}` : "",
|
|
356
|
+
processText ? `process: ${processText}` : "",
|
|
357
|
+
resultText ? `result: ${resultText}` : "",
|
|
358
|
+
].filter(Boolean).join("\n") || normalizeRecordText(parsed);
|
|
344
359
|
if (!text.trim()) {
|
|
345
360
|
continue;
|
|
346
361
|
}
|
|
@@ -350,7 +365,7 @@ function parseJsonlFile(filePath, sourceLabel, logger) {
|
|
|
350
365
|
id,
|
|
351
366
|
text,
|
|
352
367
|
summaryText: summaryText || text,
|
|
353
|
-
sourceText: sourceText || undefined,
|
|
368
|
+
sourceText: sourceText || activeContent || undefined,
|
|
354
369
|
sourceEventId: typeof parsed.source_event_id === "string" ? parsed.source_event_id : undefined,
|
|
355
370
|
sourceFile: typeof parsed.source_file === "string" ? parsed.source_file : undefined,
|
|
356
371
|
source: sourceLabel,
|
|
@@ -400,6 +415,209 @@ function parseMarkdownFile(filePath, sourceLabel) {
|
|
|
400
415
|
},
|
|
401
416
|
];
|
|
402
417
|
}
|
|
418
|
+
function normalizeFactStatus(value) {
|
|
419
|
+
const token = (value || "").trim().toLowerCase();
|
|
420
|
+
if (!token)
|
|
421
|
+
return null;
|
|
422
|
+
if (token === "active")
|
|
423
|
+
return "active";
|
|
424
|
+
if (token === "pending" || token === "pending_conflict")
|
|
425
|
+
return "pending_conflict";
|
|
426
|
+
if (token === "superseded")
|
|
427
|
+
return "superseded";
|
|
428
|
+
if (token === "rejected")
|
|
429
|
+
return "rejected";
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
function uniqueStrings(values) {
|
|
433
|
+
const output = [];
|
|
434
|
+
const seen = new Set();
|
|
435
|
+
for (const value of values) {
|
|
436
|
+
const key = (value || "").trim();
|
|
437
|
+
if (!key || seen.has(key))
|
|
438
|
+
continue;
|
|
439
|
+
seen.add(key);
|
|
440
|
+
output.push(key);
|
|
441
|
+
}
|
|
442
|
+
return output;
|
|
443
|
+
}
|
|
444
|
+
function buildGraphEvidenceIds(args) {
|
|
445
|
+
const relationKey = graphRelationKey({
|
|
446
|
+
source: args.source,
|
|
447
|
+
target: args.target,
|
|
448
|
+
type: args.type,
|
|
449
|
+
});
|
|
450
|
+
const evidenceIds = [
|
|
451
|
+
relationKey ? `graph:relation:${relationKey}` : "",
|
|
452
|
+
args.sourceEventId ? `graph:event:${args.sourceEventId}` : "",
|
|
453
|
+
args.evidenceSpan
|
|
454
|
+
? `graph:evidence:${args.sourceEventId || relationKey}`
|
|
455
|
+
: "",
|
|
456
|
+
args.wikiRef
|
|
457
|
+
? `wiki:${args.wikiRef}${args.wikiAnchor ? `#${args.wikiAnchor}` : ""}`
|
|
458
|
+
: "",
|
|
459
|
+
];
|
|
460
|
+
return uniqueStrings(evidenceIds);
|
|
461
|
+
}
|
|
462
|
+
function toMemoryRelativePath(memoryRoot, filePath) {
|
|
463
|
+
return path.relative(memoryRoot, filePath).replace(/\\/g, "/");
|
|
464
|
+
}
|
|
465
|
+
function toAnchorToken(value) {
|
|
466
|
+
const token = (value || "")
|
|
467
|
+
.trim()
|
|
468
|
+
.toLowerCase()
|
|
469
|
+
.replace(/[^a-z0-9\u4e00-\u9fa5]+/gi, "-")
|
|
470
|
+
.replace(/^-+|-+$/g, "");
|
|
471
|
+
return token || "facts";
|
|
472
|
+
}
|
|
473
|
+
function parseWikiRelationLine(line) {
|
|
474
|
+
const text = line.trim();
|
|
475
|
+
if (!text.startsWith("- "))
|
|
476
|
+
return null;
|
|
477
|
+
if (text === "- (none)")
|
|
478
|
+
return null;
|
|
479
|
+
const body = text.replace(/^-+\s*/, "");
|
|
480
|
+
const matched = body.match(/^(.+?)\s+--([^\/]+)\/([^-\s]+)-->\s+(.+?)\s*(?:\((.*)\))?$/);
|
|
481
|
+
if (!matched) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
const source = matched[1].trim();
|
|
485
|
+
const type = matched[2].trim();
|
|
486
|
+
const status = normalizeFactStatus(matched[3]);
|
|
487
|
+
const target = matched[4].trim();
|
|
488
|
+
const attrs = (matched[5] || "").trim();
|
|
489
|
+
if (!source || !target || !type) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
const attributeMap = new Map();
|
|
493
|
+
if (attrs) {
|
|
494
|
+
const parts = attrs.split(",").map(item => item.trim()).filter(Boolean);
|
|
495
|
+
for (const part of parts) {
|
|
496
|
+
const eq = part.indexOf("=");
|
|
497
|
+
if (eq <= 0)
|
|
498
|
+
continue;
|
|
499
|
+
const key = part.slice(0, eq).trim().toLowerCase();
|
|
500
|
+
const value = part.slice(eq + 1).trim();
|
|
501
|
+
if (!key)
|
|
502
|
+
continue;
|
|
503
|
+
attributeMap.set(key, value);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const evidenceSpanRaw = attributeMap.get("evidence") || "";
|
|
507
|
+
const confidenceRaw = attributeMap.get("confidence") || "";
|
|
508
|
+
const sourceEventIdRaw = attributeMap.get("source_event_id") || "";
|
|
509
|
+
const conflictIdRaw = attributeMap.get("conflict_id") || "";
|
|
510
|
+
const confidenceNum = confidenceRaw ? Number(confidenceRaw) : NaN;
|
|
511
|
+
const relationKey = graphRelationKey({ source, target, type });
|
|
512
|
+
return {
|
|
513
|
+
relation: {
|
|
514
|
+
source,
|
|
515
|
+
target,
|
|
516
|
+
type,
|
|
517
|
+
evidence_span: evidenceSpanRaw && evidenceSpanRaw.toLowerCase() !== "n/a" ? evidenceSpanRaw : undefined,
|
|
518
|
+
confidence: Number.isFinite(confidenceNum) ? confidenceNum : undefined,
|
|
519
|
+
fact_status: status || undefined,
|
|
520
|
+
source_event_id: sourceEventIdRaw && sourceEventIdRaw.toLowerCase() !== "n/a" ? sourceEventIdRaw : undefined,
|
|
521
|
+
conflict_id: conflictIdRaw && conflictIdRaw.toLowerCase() !== "n/a" ? conflictIdRaw : undefined,
|
|
522
|
+
relation_key: relationKey,
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function parseWikiProjectionDocuments(memoryRoot, logger) {
|
|
527
|
+
const wikiRoot = path.join(memoryRoot, "wiki");
|
|
528
|
+
const folders = [
|
|
529
|
+
{ dir: path.join(wikiRoot, "entities"), kind: "entity" },
|
|
530
|
+
{ dir: path.join(wikiRoot, "topics"), kind: "topic" },
|
|
531
|
+
{ dir: path.join(wikiRoot, "timelines"), kind: "timeline" },
|
|
532
|
+
];
|
|
533
|
+
const docs = [];
|
|
534
|
+
for (const { dir, kind } of folders) {
|
|
535
|
+
if (!fs.existsSync(dir))
|
|
536
|
+
continue;
|
|
537
|
+
let files = [];
|
|
538
|
+
try {
|
|
539
|
+
files = fs.readdirSync(dir)
|
|
540
|
+
.filter(file => file.toLowerCase().endsWith(".md"))
|
|
541
|
+
.sort((a, b) => a.localeCompare(b));
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
logger.debug(`Skipping wiki projection directory ${dir}: ${error}`);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
for (const file of files) {
|
|
548
|
+
const filePath = path.join(dir, file);
|
|
549
|
+
const markdown = safeReadFile(filePath);
|
|
550
|
+
if (!markdown.trim())
|
|
551
|
+
continue;
|
|
552
|
+
const relativePath = toMemoryRelativePath(memoryRoot, filePath);
|
|
553
|
+
const mtime = (() => {
|
|
554
|
+
try {
|
|
555
|
+
return fs.statSync(filePath).mtimeMs;
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
return NaN;
|
|
559
|
+
}
|
|
560
|
+
})();
|
|
561
|
+
let section = "Facts";
|
|
562
|
+
const sectionCounters = new Map();
|
|
563
|
+
const lines = markdown.split(/\r?\n/);
|
|
564
|
+
for (const rawLine of lines) {
|
|
565
|
+
const line = rawLine.trim();
|
|
566
|
+
if (!line)
|
|
567
|
+
continue;
|
|
568
|
+
if (line.startsWith("## ")) {
|
|
569
|
+
section = line.replace(/^##\s+/, "").trim() || "Facts";
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
const parsed = parseWikiRelationLine(line);
|
|
573
|
+
if (!parsed)
|
|
574
|
+
continue;
|
|
575
|
+
const sectionToken = toAnchorToken(section);
|
|
576
|
+
const index = (sectionCounters.get(sectionToken) || 0) + 1;
|
|
577
|
+
sectionCounters.set(sectionToken, index);
|
|
578
|
+
const anchor = `${sectionToken}-${index}`;
|
|
579
|
+
const relation = parsed.relation;
|
|
580
|
+
const factStatus = relation.fact_status || "active";
|
|
581
|
+
const evidenceIds = buildGraphEvidenceIds({
|
|
582
|
+
source: relation.source,
|
|
583
|
+
target: relation.target,
|
|
584
|
+
type: relation.type,
|
|
585
|
+
sourceEventId: relation.source_event_id,
|
|
586
|
+
evidenceSpan: relation.evidence_span,
|
|
587
|
+
wikiRef: relativePath,
|
|
588
|
+
wikiAnchor: anchor,
|
|
589
|
+
});
|
|
590
|
+
docs.push({
|
|
591
|
+
id: `wiki:${relativePath}#${anchor}`,
|
|
592
|
+
text: [
|
|
593
|
+
`# Wiki Projection`,
|
|
594
|
+
`wiki_kind: ${kind}`,
|
|
595
|
+
`wiki_path: ${relativePath}`,
|
|
596
|
+
`wiki_section: ${section}`,
|
|
597
|
+
`fact_status: ${factStatus}`,
|
|
598
|
+
`${relation.source} ${relation.type} ${relation.target}`,
|
|
599
|
+
line,
|
|
600
|
+
].join("\n"),
|
|
601
|
+
source: "sessions_graph_wiki",
|
|
602
|
+
timestamp: Number.isFinite(mtime) ? Math.floor(mtime) : undefined,
|
|
603
|
+
layer: "archive",
|
|
604
|
+
sourceFile: relativePath,
|
|
605
|
+
sourceMemoryId: relation.relation_key || `wiki:${relativePath}`,
|
|
606
|
+
sourceEventId: relation.source_event_id,
|
|
607
|
+
eventType: "graph_wiki_projection",
|
|
608
|
+
qualityScore: 0.95,
|
|
609
|
+
entities: uniqueStrings([relation.source, relation.target]),
|
|
610
|
+
relations: [relation],
|
|
611
|
+
factStatus,
|
|
612
|
+
wikiRef: relativePath,
|
|
613
|
+
wikiAnchor: anchor,
|
|
614
|
+
evidenceIds,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return docs;
|
|
620
|
+
}
|
|
403
621
|
function extractPrioritizedRuleLines(text, maxRules) {
|
|
404
622
|
if (!text.trim() || maxRules <= 0) {
|
|
405
623
|
return [];
|
|
@@ -693,6 +911,95 @@ function sourceWeight(source, intent) {
|
|
|
693
911
|
}
|
|
694
912
|
return 1;
|
|
695
913
|
}
|
|
914
|
+
const QUERY_PLAN_STOPWORDS = new Set([
|
|
915
|
+
"what",
|
|
916
|
+
"is",
|
|
917
|
+
"the",
|
|
918
|
+
"a",
|
|
919
|
+
"an",
|
|
920
|
+
"of",
|
|
921
|
+
"for",
|
|
922
|
+
"to",
|
|
923
|
+
"in",
|
|
924
|
+
"on",
|
|
925
|
+
"and",
|
|
926
|
+
"or",
|
|
927
|
+
"with",
|
|
928
|
+
"about",
|
|
929
|
+
"this",
|
|
930
|
+
"that",
|
|
931
|
+
"这些",
|
|
932
|
+
"那些",
|
|
933
|
+
"这个",
|
|
934
|
+
"那个",
|
|
935
|
+
"之前",
|
|
936
|
+
"提到",
|
|
937
|
+
"提过",
|
|
938
|
+
"一下",
|
|
939
|
+
"请问",
|
|
940
|
+
"关于",
|
|
941
|
+
"相关",
|
|
942
|
+
"什么",
|
|
943
|
+
"怎么",
|
|
944
|
+
"如何",
|
|
945
|
+
"是否",
|
|
946
|
+
"有没有",
|
|
947
|
+
"是什么",
|
|
948
|
+
"哪些",
|
|
949
|
+
"哪个",
|
|
950
|
+
]);
|
|
951
|
+
function planQueryKeywords(query) {
|
|
952
|
+
const normalized = (query || "").trim().replace(/\s+/g, " ");
|
|
953
|
+
if (!normalized)
|
|
954
|
+
return [];
|
|
955
|
+
const output = [];
|
|
956
|
+
const seen = new Set();
|
|
957
|
+
const push = (value) => {
|
|
958
|
+
const item = (value || "").trim();
|
|
959
|
+
if (!item)
|
|
960
|
+
return;
|
|
961
|
+
const key = item.toLowerCase();
|
|
962
|
+
if (seen.has(key))
|
|
963
|
+
return;
|
|
964
|
+
seen.add(key);
|
|
965
|
+
output.push(item);
|
|
966
|
+
};
|
|
967
|
+
push(normalized);
|
|
968
|
+
for (const match of normalized.matchAll(/["“”'‘’]([^"“”'‘’]{2,64})["“”'‘’]/g)) {
|
|
969
|
+
push(match[1]);
|
|
970
|
+
}
|
|
971
|
+
for (const match of normalized.match(/https?:\/\/[^\s]+/gi) || []) {
|
|
972
|
+
push(match);
|
|
973
|
+
}
|
|
974
|
+
for (const match of normalized.matchAll(/\b[A-Za-z0-9][A-Za-z0-9._/-]*(?:\s+[A-Za-z0-9][A-Za-z0-9._/-]*){0,3}\b/g)) {
|
|
975
|
+
const phrase = match[0].trim();
|
|
976
|
+
const words = phrase.split(/\s+/).filter(Boolean);
|
|
977
|
+
const hasStrongSignal = words.length >= 2 || /[A-Z]{2,}/.test(phrase);
|
|
978
|
+
if (hasStrongSignal && !QUERY_PLAN_STOPWORDS.has(phrase.toLowerCase())) {
|
|
979
|
+
push(phrase);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
const normalizedForSplit = normalized
|
|
983
|
+
.replace(/[(){}\[\]<>]/g, " ")
|
|
984
|
+
.replace(/[,。!?;:,.!?;:、]/g, " ")
|
|
985
|
+
.replace(/(之前提到的|之前提到|提到的|提到|请问|一下|是什么|什么|哪个|哪篇|哪里|如何|怎么|有关|关于|以及|还有|并且|然后|能否|可以|帮我|给我)/g, " ");
|
|
986
|
+
for (const raw of normalizedForSplit.split(/[\/\-\s]+/)) {
|
|
987
|
+
const token = raw.trim();
|
|
988
|
+
if (!token || token.length < 2)
|
|
989
|
+
continue;
|
|
990
|
+
if (QUERY_PLAN_STOPWORDS.has(token.toLowerCase()))
|
|
991
|
+
continue;
|
|
992
|
+
push(token);
|
|
993
|
+
}
|
|
994
|
+
return output.slice(0, 5);
|
|
995
|
+
}
|
|
996
|
+
function shouldTriggerFulltextFallback(ranked, topK) {
|
|
997
|
+
const scoped = ranked.slice(0, Math.max(1, topK));
|
|
998
|
+
if (scoped.length === 0)
|
|
999
|
+
return true;
|
|
1000
|
+
const summaryHits = scoped.filter(item => Array.isArray(item.reason_tags) && item.reason_tags.includes("summary_hit")).length;
|
|
1001
|
+
return summaryHits === 0;
|
|
1002
|
+
}
|
|
696
1003
|
function mergeKeyFromDoc(doc) {
|
|
697
1004
|
const canonical = typeof doc.sourceMemoryCanonicalId === "string" ? doc.sourceMemoryCanonicalId.trim() : "";
|
|
698
1005
|
if (canonical) {
|
|
@@ -704,6 +1011,42 @@ function mergeKeyFromDoc(doc) {
|
|
|
704
1011
|
}
|
|
705
1012
|
return `id:${doc.id}`;
|
|
706
1013
|
}
|
|
1014
|
+
function docFactStatus(doc) {
|
|
1015
|
+
const direct = normalizeFactStatus(typeof doc.factStatus === "string" ? doc.factStatus : "");
|
|
1016
|
+
if (direct)
|
|
1017
|
+
return direct;
|
|
1018
|
+
const relation = Array.isArray(doc.relations) && doc.relations.length > 0 ? doc.relations[0] : null;
|
|
1019
|
+
const relationStatus = normalizeFactStatus(typeof relation?.fact_status === "string" ? relation.fact_status : "");
|
|
1020
|
+
if (relationStatus)
|
|
1021
|
+
return relationStatus;
|
|
1022
|
+
return "active";
|
|
1023
|
+
}
|
|
1024
|
+
function docEvidenceIds(doc) {
|
|
1025
|
+
if (Array.isArray(doc.evidenceIds) && doc.evidenceIds.length > 0) {
|
|
1026
|
+
return uniqueStrings(doc.evidenceIds);
|
|
1027
|
+
}
|
|
1028
|
+
const relation = Array.isArray(doc.relations) && doc.relations.length > 0 ? doc.relations[0] : null;
|
|
1029
|
+
const source = relation?.source || "";
|
|
1030
|
+
const target = relation?.target || "";
|
|
1031
|
+
const type = relation?.type || "";
|
|
1032
|
+
const evidence = source && target && type
|
|
1033
|
+
? buildGraphEvidenceIds({
|
|
1034
|
+
source,
|
|
1035
|
+
target,
|
|
1036
|
+
type,
|
|
1037
|
+
sourceEventId: relation?.source_event_id || doc.sourceEventId,
|
|
1038
|
+
evidenceSpan: relation?.evidence_span,
|
|
1039
|
+
wikiRef: doc.wikiRef,
|
|
1040
|
+
wikiAnchor: doc.wikiAnchor,
|
|
1041
|
+
})
|
|
1042
|
+
: [];
|
|
1043
|
+
return uniqueStrings([
|
|
1044
|
+
...evidence,
|
|
1045
|
+
doc.sourceEventId ? `graph:event:${doc.sourceEventId}` : "",
|
|
1046
|
+
doc.wikiRef ? `wiki:${doc.wikiRef}${doc.wikiAnchor ? `#${doc.wikiAnchor}` : ""}` : "",
|
|
1047
|
+
`doc:${doc.id}`,
|
|
1048
|
+
]);
|
|
1049
|
+
}
|
|
707
1050
|
function customChannelWeight(source, options) {
|
|
708
1051
|
const weights = options?.channelWeights;
|
|
709
1052
|
if (!weights)
|
|
@@ -1111,6 +1454,24 @@ function createReadStore(options) {
|
|
|
1111
1454
|
return hitStatsCache;
|
|
1112
1455
|
}
|
|
1113
1456
|
}
|
|
1457
|
+
function directorySignature(dirPath, extension) {
|
|
1458
|
+
try {
|
|
1459
|
+
if (!fs.existsSync(dirPath)) {
|
|
1460
|
+
return `${dirPath}:missing`;
|
|
1461
|
+
}
|
|
1462
|
+
const files = fs.readdirSync(dirPath)
|
|
1463
|
+
.filter(file => file.toLowerCase().endsWith(extension.toLowerCase()))
|
|
1464
|
+
.sort((a, b) => a.localeCompare(b));
|
|
1465
|
+
if (files.length === 0) {
|
|
1466
|
+
return `${dirPath}:empty`;
|
|
1467
|
+
}
|
|
1468
|
+
const signatures = files.map(file => fileSignature(path.join(dirPath, file)));
|
|
1469
|
+
return `${dirPath}:${signatures.join("|")}`;
|
|
1470
|
+
}
|
|
1471
|
+
catch {
|
|
1472
|
+
return `${dirPath}:error`;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1114
1475
|
function saveHitStats(state) {
|
|
1115
1476
|
try {
|
|
1116
1477
|
const dir = path.dirname(hitStatsPath);
|
|
@@ -1169,16 +1530,26 @@ function createReadStore(options) {
|
|
|
1169
1530
|
}
|
|
1170
1531
|
function loadAllDocuments() {
|
|
1171
1532
|
const cortexRulesPath = path.join(memoryRoot, "CORTEX_RULES.md");
|
|
1172
|
-
const memoryMdPath = path.join(memoryRoot, "MEMORY.md");
|
|
1173
1533
|
const activeSessionsPath = path.join(memoryRoot, "sessions", "active", "sessions.jsonl");
|
|
1174
1534
|
const archiveSessionsPath = path.join(memoryRoot, "sessions", "archive", "sessions.jsonl");
|
|
1175
1535
|
const graphMemoryPath = path.join(memoryRoot, "graph", "memory.jsonl");
|
|
1536
|
+
const supersededRelationPath = path.join(memoryRoot, "graph", "superseded_relations.jsonl");
|
|
1537
|
+
const conflictQueuePath = path.join(memoryRoot, "graph", "conflict_queue.jsonl");
|
|
1538
|
+
const wikiEntitiesDir = path.join(memoryRoot, "wiki", "entities");
|
|
1539
|
+
const wikiTopicsDir = path.join(memoryRoot, "wiki", "topics");
|
|
1540
|
+
const wikiTimelinesDir = path.join(memoryRoot, "wiki", "timelines");
|
|
1541
|
+
const wikiProjectionIndexPath = path.join(memoryRoot, "wiki", ".projection_index.json");
|
|
1176
1542
|
const signature = [
|
|
1177
1543
|
fileSignature(cortexRulesPath),
|
|
1178
|
-
fileSignature(memoryMdPath),
|
|
1179
1544
|
fileSignature(activeSessionsPath),
|
|
1180
1545
|
fileSignature(archiveSessionsPath),
|
|
1181
1546
|
fileSignature(graphMemoryPath),
|
|
1547
|
+
fileSignature(supersededRelationPath),
|
|
1548
|
+
fileSignature(conflictQueuePath),
|
|
1549
|
+
directorySignature(wikiEntitiesDir, ".md"),
|
|
1550
|
+
directorySignature(wikiTopicsDir, ".md"),
|
|
1551
|
+
directorySignature(wikiTimelinesDir, ".md"),
|
|
1552
|
+
fileSignature(wikiProjectionIndexPath),
|
|
1182
1553
|
].join("|");
|
|
1183
1554
|
if (docsCache && docsCache.signature === signature) {
|
|
1184
1555
|
return docsCache.docs;
|
|
@@ -1202,6 +1573,25 @@ function createReadStore(options) {
|
|
|
1202
1573
|
}
|
|
1203
1574
|
}
|
|
1204
1575
|
const graphDocs = [];
|
|
1576
|
+
const supersededRelationKeys = new Set();
|
|
1577
|
+
if (fs.existsSync(supersededRelationPath)) {
|
|
1578
|
+
const supersededContent = safeReadFile(supersededRelationPath);
|
|
1579
|
+
for (const line of supersededContent.split(/\r?\n/)) {
|
|
1580
|
+
const trimmed = line.trim();
|
|
1581
|
+
if (!trimmed)
|
|
1582
|
+
continue;
|
|
1583
|
+
try {
|
|
1584
|
+
const parsed = JSON.parse(trimmed);
|
|
1585
|
+
const relationKey = typeof parsed.relation_key === "string" ? parsed.relation_key.trim().toLowerCase() : "";
|
|
1586
|
+
if (relationKey) {
|
|
1587
|
+
supersededRelationKeys.add(relationKey);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
catch (error) {
|
|
1591
|
+
options.logger.debug(`Skipping invalid superseded relation line: ${error}`);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1205
1595
|
if (fs.existsSync(graphMemoryPath)) {
|
|
1206
1596
|
const graphContent = safeReadFile(graphMemoryPath);
|
|
1207
1597
|
for (const line of graphContent.split(/\r?\n/)) {
|
|
@@ -1211,12 +1601,21 @@ function createReadStore(options) {
|
|
|
1211
1601
|
try {
|
|
1212
1602
|
const parsed = JSON.parse(trimmed);
|
|
1213
1603
|
const id = typeof parsed.id === "string" ? parsed.id : "";
|
|
1604
|
+
const summary = typeof parsed.summary === "string" ? parsed.summary.trim() : "";
|
|
1605
|
+
const sourceTextNav = typeof parsed.source_text_nav === "object" && parsed.source_text_nav !== null && !Array.isArray(parsed.source_text_nav)
|
|
1606
|
+
? parsed.source_text_nav
|
|
1607
|
+
: undefined;
|
|
1214
1608
|
const sourceEventId = typeof parsed.source_event_id === "string" ? parsed.source_event_id : "";
|
|
1215
1609
|
const archiveEventId = typeof parsed.archive_event_id === "string" ? parsed.archive_event_id : "";
|
|
1216
|
-
const
|
|
1217
|
-
const
|
|
1218
|
-
const
|
|
1219
|
-
const
|
|
1610
|
+
const navSourceEventId = typeof sourceTextNav?.source_event_id === "string" ? sourceTextNav.source_event_id.trim() : "";
|
|
1611
|
+
const navSourceMemoryId = typeof sourceTextNav?.source_memory_id === "string" ? sourceTextNav.source_memory_id.trim() : "";
|
|
1612
|
+
const navSessionId = typeof sourceTextNav?.session_id === "string" ? sourceTextNav.session_id.trim() : "";
|
|
1613
|
+
const navSourceLayer = typeof sourceTextNav?.layer === "string" ? sourceTextNav.layer.trim() : "";
|
|
1614
|
+
const navSourceFile = typeof sourceTextNav?.source_file === "string" ? sourceTextNav.source_file.trim() : "";
|
|
1615
|
+
const eventRefId = navSourceMemoryId || archiveEventId || navSourceEventId || sourceEventId;
|
|
1616
|
+
const sessionId = navSessionId || (typeof parsed.session_id === "string" ? parsed.session_id : "");
|
|
1617
|
+
const sourceLayer = navSourceLayer || (typeof parsed.source_layer === "string" ? parsed.source_layer : "");
|
|
1618
|
+
const sourceFile = navSourceFile || (typeof parsed.source_file === "string" ? parsed.source_file : "");
|
|
1220
1619
|
const timestamp = typeof parsed.timestamp === "string" ? Date.parse(parsed.timestamp) : NaN;
|
|
1221
1620
|
const entities = Array.isArray(parsed.entities)
|
|
1222
1621
|
? parsed.entities.map((item) => (typeof item === "string" ? item.trim() : "")).filter(Boolean)
|
|
@@ -1224,65 +1623,114 @@ function createReadStore(options) {
|
|
|
1224
1623
|
const entityTypes = typeof parsed.entity_types === "object" && parsed.entity_types !== null
|
|
1225
1624
|
? parsed.entity_types
|
|
1226
1625
|
: {};
|
|
1227
|
-
const relations = Array.isArray(parsed.relations)
|
|
1228
|
-
? parsed.relations
|
|
1229
|
-
.map((item) => {
|
|
1230
|
-
if (typeof item !== "object" || item === null)
|
|
1231
|
-
return null;
|
|
1232
|
-
const relation = item;
|
|
1233
|
-
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
1234
|
-
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
1235
|
-
const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
|
|
1236
|
-
if (!source || !target)
|
|
1237
|
-
return null;
|
|
1238
|
-
return { source, target, type };
|
|
1239
|
-
})
|
|
1240
|
-
.filter((item) => Boolean(item))
|
|
1241
|
-
: [];
|
|
1626
|
+
const relations = Array.isArray(parsed.relations) ? parsed.relations : [];
|
|
1242
1627
|
const eventType = (typeof parsed.event_type === "string" ? parsed.event_type : "") || archiveEventTypeById.get(eventRefId) || "";
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
: "
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1628
|
+
let relationCount = 0;
|
|
1629
|
+
for (const relationRaw of relations) {
|
|
1630
|
+
if (typeof relationRaw !== "object" || relationRaw === null)
|
|
1631
|
+
continue;
|
|
1632
|
+
const relationRecord = relationRaw;
|
|
1633
|
+
const source = typeof relationRecord.source === "string" ? relationRecord.source.trim() : "";
|
|
1634
|
+
const target = typeof relationRecord.target === "string" ? relationRecord.target.trim() : "";
|
|
1635
|
+
const type = typeof relationRecord.type === "string" ? relationRecord.type.trim() : "related_to";
|
|
1636
|
+
if (!source || !target)
|
|
1637
|
+
continue;
|
|
1638
|
+
relationCount += 1;
|
|
1639
|
+
const relationKey = graphRelationKey({ source, target, type });
|
|
1640
|
+
const factStatus = supersededRelationKeys.has(relationKey) ? "superseded" : "active";
|
|
1641
|
+
const evidenceSpan = typeof relationRecord.evidence_span === "string" && relationRecord.evidence_span.trim()
|
|
1642
|
+
? relationRecord.evidence_span.trim()
|
|
1643
|
+
: undefined;
|
|
1644
|
+
const confidenceValue = typeof relationRecord.confidence === "number" && Number.isFinite(relationRecord.confidence)
|
|
1645
|
+
? relationRecord.confidence
|
|
1646
|
+
: undefined;
|
|
1647
|
+
const relationOrigin = typeof relationRecord.relation_origin === "string" && relationRecord.relation_origin.trim()
|
|
1648
|
+
? relationRecord.relation_origin.trim()
|
|
1649
|
+
: undefined;
|
|
1650
|
+
const relationDefinition = typeof relationRecord.relation_definition === "string" && relationRecord.relation_definition.trim()
|
|
1651
|
+
? relationRecord.relation_definition.trim()
|
|
1652
|
+
: undefined;
|
|
1653
|
+
const contextChunk = typeof relationRecord.context_chunk === "string" && relationRecord.context_chunk.trim()
|
|
1654
|
+
? relationRecord.context_chunk.trim()
|
|
1655
|
+
: undefined;
|
|
1656
|
+
const relation = {
|
|
1657
|
+
source,
|
|
1658
|
+
target,
|
|
1659
|
+
type,
|
|
1660
|
+
relation_origin: relationOrigin,
|
|
1661
|
+
relation_definition: relationDefinition,
|
|
1662
|
+
evidence_span: evidenceSpan,
|
|
1663
|
+
context_chunk: contextChunk,
|
|
1664
|
+
confidence: confidenceValue,
|
|
1665
|
+
fact_status: factStatus,
|
|
1666
|
+
source_event_id: sourceEventId || archiveEventId || undefined,
|
|
1667
|
+
relation_key: relationKey,
|
|
1668
|
+
};
|
|
1669
|
+
const relationEntities = uniqueStrings([...entities, source, target]);
|
|
1670
|
+
const entityLines = relationEntities.length > 0
|
|
1671
|
+
? relationEntities.map((entity, index) => {
|
|
1672
|
+
const entityType = entityTypes[entity];
|
|
1673
|
+
return `${index + 1}. ${entity}${entityType ? ` (${entityType})` : ""}`;
|
|
1674
|
+
}).join("\n")
|
|
1675
|
+
: "none";
|
|
1676
|
+
const text = [
|
|
1677
|
+
`# Graph Relation`,
|
|
1678
|
+
`record_id: ${id}`,
|
|
1679
|
+
`relation_index: ${relationCount}`,
|
|
1680
|
+
`relation_key: ${relationKey}`,
|
|
1681
|
+
`fact_status: ${factStatus}`,
|
|
1682
|
+
`source_event_id: ${sourceEventId || archiveEventId || "unknown"}`,
|
|
1683
|
+
`source_layer: ${sourceLayer || "unknown"}`,
|
|
1684
|
+
`archive_event_id: ${archiveEventId || "n/a"}`,
|
|
1685
|
+
`event_type: ${eventType || "unknown"}`,
|
|
1686
|
+
`session_id: ${sessionId || "unknown"}`,
|
|
1687
|
+
`source_file: ${sourceFile || "unknown"}`,
|
|
1688
|
+
`evidence_span: ${evidenceSpan || "n/a"}`,
|
|
1689
|
+
`context_chunk: ${contextChunk || "n/a"}`,
|
|
1690
|
+
`relation_origin: ${relationOrigin || "n/a"}`,
|
|
1691
|
+
`relation_definition: ${relationDefinition || "n/a"}`,
|
|
1692
|
+
`confidence: ${typeof confidenceValue === "number" ? confidenceValue : "n/a"}`,
|
|
1693
|
+
``,
|
|
1694
|
+
`## Summary`,
|
|
1695
|
+
summary || "n/a",
|
|
1696
|
+
``,
|
|
1697
|
+
`## Source References`,
|
|
1698
|
+
`source_event_id: ${navSourceEventId || sourceEventId || archiveEventId || "unknown"}`,
|
|
1699
|
+
`source_memory_id: ${navSourceMemoryId || eventRefId || "unknown"}`,
|
|
1700
|
+
`source_layer: ${sourceLayer || "unknown"}`,
|
|
1701
|
+
`source_file: ${sourceFile || "unknown"}`,
|
|
1702
|
+
`session_id: ${sessionId || "unknown"}`,
|
|
1703
|
+
``,
|
|
1704
|
+
`## Entities`,
|
|
1705
|
+
entityLines,
|
|
1706
|
+
``,
|
|
1707
|
+
`## Relation`,
|
|
1708
|
+
`${source} -[${type}/${factStatus}]-> ${target}`,
|
|
1709
|
+
].join("\n");
|
|
1275
1710
|
graphDocs.push({
|
|
1276
|
-
id
|
|
1711
|
+
id: `${id || "graph"}:rel:${relationCount}`,
|
|
1277
1712
|
text,
|
|
1278
1713
|
source: "sessions_graph",
|
|
1279
1714
|
timestamp: Number.isFinite(timestamp) ? timestamp : undefined,
|
|
1280
1715
|
layer: sourceLayer === "active_only" ? "active" : "archive",
|
|
1716
|
+
summaryText: summary || undefined,
|
|
1717
|
+
sourceText: contextChunk || undefined,
|
|
1281
1718
|
sourceMemoryId: eventRefId || id,
|
|
1282
|
-
sourceEventId: sourceEventId || archiveEventId || undefined,
|
|
1719
|
+
sourceEventId: navSourceEventId || sourceEventId || archiveEventId || undefined,
|
|
1720
|
+
sourceFile: sourceFile || undefined,
|
|
1283
1721
|
sessionId,
|
|
1284
|
-
entities,
|
|
1285
|
-
relations,
|
|
1722
|
+
entities: relationEntities,
|
|
1723
|
+
relations: [relation],
|
|
1724
|
+
eventType: eventType || undefined,
|
|
1725
|
+
qualityScore: typeof parsed.confidence === "number" && Number.isFinite(parsed.confidence) ? parsed.confidence : 1,
|
|
1726
|
+
factStatus,
|
|
1727
|
+
evidenceIds: buildGraphEvidenceIds({
|
|
1728
|
+
source,
|
|
1729
|
+
target,
|
|
1730
|
+
type,
|
|
1731
|
+
sourceEventId: relation.source_event_id,
|
|
1732
|
+
evidenceSpan,
|
|
1733
|
+
}),
|
|
1286
1734
|
});
|
|
1287
1735
|
}
|
|
1288
1736
|
}
|
|
@@ -1291,14 +1739,152 @@ function createReadStore(options) {
|
|
|
1291
1739
|
}
|
|
1292
1740
|
}
|
|
1293
1741
|
}
|
|
1294
|
-
|
|
1742
|
+
if (fs.existsSync(conflictQueuePath)) {
|
|
1743
|
+
const queueContent = safeReadFile(conflictQueuePath);
|
|
1744
|
+
for (const line of queueContent.split(/\r?\n/)) {
|
|
1745
|
+
const trimmed = line.trim();
|
|
1746
|
+
if (!trimmed)
|
|
1747
|
+
continue;
|
|
1748
|
+
try {
|
|
1749
|
+
const parsed = JSON.parse(trimmed);
|
|
1750
|
+
const status = normalizeFactStatus(typeof parsed.status === "string" ? parsed.status : "");
|
|
1751
|
+
if (status !== "pending_conflict" && status !== "rejected") {
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
const conflictId = typeof parsed.conflict_id === "string" ? parsed.conflict_id.trim() : "";
|
|
1755
|
+
const sourceEventId = typeof parsed.source_event_id === "string" ? parsed.source_event_id.trim() : "";
|
|
1756
|
+
const sessionId = typeof parsed.session_id === "string" ? parsed.session_id.trim() : "";
|
|
1757
|
+
const sourceFile = typeof parsed.source_file === "string" ? parsed.source_file.trim() : "";
|
|
1758
|
+
const sourceLayer = typeof parsed.source_layer === "string" ? parsed.source_layer.trim() : "";
|
|
1759
|
+
const updatedAt = typeof parsed.updated_at === "string" ? Date.parse(parsed.updated_at) : NaN;
|
|
1760
|
+
const candidate = typeof parsed.candidate === "object" && parsed.candidate !== null
|
|
1761
|
+
? parsed.candidate
|
|
1762
|
+
: {};
|
|
1763
|
+
const candidateSummary = typeof candidate.summary === "string" ? candidate.summary.trim() : "";
|
|
1764
|
+
const candidateSourceTextNav = typeof candidate.source_text_nav === "object" && candidate.source_text_nav !== null && !Array.isArray(candidate.source_text_nav)
|
|
1765
|
+
? candidate.source_text_nav
|
|
1766
|
+
: undefined;
|
|
1767
|
+
const navSourceEventId = typeof candidateSourceTextNav?.source_event_id === "string" ? candidateSourceTextNav.source_event_id.trim() : "";
|
|
1768
|
+
const navSourceMemoryId = typeof candidateSourceTextNav?.source_memory_id === "string" ? candidateSourceTextNav.source_memory_id.trim() : "";
|
|
1769
|
+
const navSourceLayer = typeof candidateSourceTextNav?.layer === "string" ? candidateSourceTextNav.layer.trim() : "";
|
|
1770
|
+
const navSourceFile = typeof candidateSourceTextNav?.source_file === "string" ? candidateSourceTextNav.source_file.trim() : "";
|
|
1771
|
+
const navSessionId = typeof candidateSourceTextNav?.session_id === "string" ? candidateSourceTextNav.session_id.trim() : "";
|
|
1772
|
+
const candidateEventType = typeof candidate.event_type === "string" ? candidate.event_type.trim() : "";
|
|
1773
|
+
const candidateRelations = Array.isArray(candidate.relations) ? candidate.relations : [];
|
|
1774
|
+
let relationIndex = 0;
|
|
1775
|
+
for (const relationRaw of candidateRelations) {
|
|
1776
|
+
if (typeof relationRaw !== "object" || relationRaw === null)
|
|
1777
|
+
continue;
|
|
1778
|
+
const relationRecord = relationRaw;
|
|
1779
|
+
const source = typeof relationRecord.source === "string" ? relationRecord.source.trim() : "";
|
|
1780
|
+
const target = typeof relationRecord.target === "string" ? relationRecord.target.trim() : "";
|
|
1781
|
+
const type = typeof relationRecord.type === "string" ? relationRecord.type.trim() : "related_to";
|
|
1782
|
+
if (!source || !target)
|
|
1783
|
+
continue;
|
|
1784
|
+
relationIndex += 1;
|
|
1785
|
+
const relationKey = graphRelationKey({ source, target, type });
|
|
1786
|
+
const evidenceSpan = typeof relationRecord.evidence_span === "string" && relationRecord.evidence_span.trim()
|
|
1787
|
+
? relationRecord.evidence_span.trim()
|
|
1788
|
+
: undefined;
|
|
1789
|
+
const confidenceValue = typeof relationRecord.confidence === "number" && Number.isFinite(relationRecord.confidence)
|
|
1790
|
+
? relationRecord.confidence
|
|
1791
|
+
: undefined;
|
|
1792
|
+
const relationOrigin = typeof relationRecord.relation_origin === "string" && relationRecord.relation_origin.trim()
|
|
1793
|
+
? relationRecord.relation_origin.trim()
|
|
1794
|
+
: undefined;
|
|
1795
|
+
const relationDefinition = typeof relationRecord.relation_definition === "string" && relationRecord.relation_definition.trim()
|
|
1796
|
+
? relationRecord.relation_definition.trim()
|
|
1797
|
+
: undefined;
|
|
1798
|
+
const contextChunk = typeof relationRecord.context_chunk === "string" && relationRecord.context_chunk.trim()
|
|
1799
|
+
? relationRecord.context_chunk.trim()
|
|
1800
|
+
: undefined;
|
|
1801
|
+
const relation = {
|
|
1802
|
+
source,
|
|
1803
|
+
target,
|
|
1804
|
+
type,
|
|
1805
|
+
relation_origin: relationOrigin,
|
|
1806
|
+
relation_definition: relationDefinition,
|
|
1807
|
+
evidence_span: evidenceSpan,
|
|
1808
|
+
context_chunk: contextChunk,
|
|
1809
|
+
confidence: confidenceValue,
|
|
1810
|
+
fact_status: status,
|
|
1811
|
+
source_event_id: sourceEventId || undefined,
|
|
1812
|
+
conflict_id: conflictId || undefined,
|
|
1813
|
+
relation_key: relationKey,
|
|
1814
|
+
};
|
|
1815
|
+
const evidenceIds = uniqueStrings([
|
|
1816
|
+
...buildGraphEvidenceIds({
|
|
1817
|
+
source,
|
|
1818
|
+
target,
|
|
1819
|
+
type,
|
|
1820
|
+
sourceEventId: sourceEventId || undefined,
|
|
1821
|
+
evidenceSpan,
|
|
1822
|
+
}),
|
|
1823
|
+
conflictId ? `graph:conflict:${conflictId}` : "",
|
|
1824
|
+
]);
|
|
1825
|
+
const text = [
|
|
1826
|
+
`# Graph Conflict Candidate`,
|
|
1827
|
+
`conflict_id: ${conflictId || "unknown"}`,
|
|
1828
|
+
`fact_status: ${status}`,
|
|
1829
|
+
`source_event_id: ${sourceEventId || "unknown"}`,
|
|
1830
|
+
`source_layer: ${sourceLayer || "unknown"}`,
|
|
1831
|
+
`event_type: ${candidateEventType || "unknown"}`,
|
|
1832
|
+
`session_id: ${sessionId || "unknown"}`,
|
|
1833
|
+
`source_file: ${sourceFile || "unknown"}`,
|
|
1834
|
+
`relation_key: ${relationKey}`,
|
|
1835
|
+
`evidence_span: ${evidenceSpan || "n/a"}`,
|
|
1836
|
+
`context_chunk: ${contextChunk || "n/a"}`,
|
|
1837
|
+
`relation_origin: ${relationOrigin || "n/a"}`,
|
|
1838
|
+
`relation_definition: ${relationDefinition || "n/a"}`,
|
|
1839
|
+
`confidence: ${typeof confidenceValue === "number" ? confidenceValue : "n/a"}`,
|
|
1840
|
+
``,
|
|
1841
|
+
`## Summary`,
|
|
1842
|
+
candidateSummary || "n/a",
|
|
1843
|
+
``,
|
|
1844
|
+
`## Source References`,
|
|
1845
|
+
`source_event_id: ${navSourceEventId || sourceEventId || "unknown"}`,
|
|
1846
|
+
`source_memory_id: ${navSourceMemoryId || sourceEventId || "unknown"}`,
|
|
1847
|
+
`source_layer: ${navSourceLayer || sourceLayer || "unknown"}`,
|
|
1848
|
+
`source_file: ${navSourceFile || sourceFile || "unknown"}`,
|
|
1849
|
+
`session_id: ${navSessionId || sessionId || "unknown"}`,
|
|
1850
|
+
``,
|
|
1851
|
+
`${source} -[${type}/${status}]-> ${target}`,
|
|
1852
|
+
].join("\n");
|
|
1853
|
+
graphDocs.push({
|
|
1854
|
+
id: `gcf:${conflictId || "unknown"}:rel:${relationIndex}`,
|
|
1855
|
+
text,
|
|
1856
|
+
source: "sessions_graph_conflict",
|
|
1857
|
+
timestamp: Number.isFinite(updatedAt) ? updatedAt : undefined,
|
|
1858
|
+
layer: (navSourceLayer || sourceLayer) === "active_only" ? "active" : "archive",
|
|
1859
|
+
summaryText: candidateSummary || undefined,
|
|
1860
|
+
sourceText: contextChunk || undefined,
|
|
1861
|
+
sourceMemoryId: navSourceMemoryId || relationKey,
|
|
1862
|
+
sourceEventId: navSourceEventId || sourceEventId || undefined,
|
|
1863
|
+
sourceFile: navSourceFile || sourceFile || undefined,
|
|
1864
|
+
sessionId: navSessionId || sessionId || undefined,
|
|
1865
|
+
entities: uniqueStrings([source, target]),
|
|
1866
|
+
relations: [relation],
|
|
1867
|
+
eventType: candidateEventType || "graph_conflict",
|
|
1868
|
+
qualityScore: typeof confidenceValue === "number" ? confidenceValue : 0.8,
|
|
1869
|
+
factStatus: status,
|
|
1870
|
+
evidenceIds,
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
catch (error) {
|
|
1875
|
+
options.logger.debug(`Skipping invalid conflict queue line: ${error}`);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const entitySummaryDocs = buildEntityGraphSummaryDocs(graphDocs.filter(item => item.factStatus === "active"));
|
|
1880
|
+
const wikiProjectionDocs = parseWikiProjectionDocuments(memoryRoot, options.logger);
|
|
1295
1881
|
const docs = [
|
|
1296
1882
|
...parseMarkdownFile(cortexRulesPath, "CORTEX_RULES.md"),
|
|
1297
|
-
...parseMarkdownFile(memoryMdPath, "MEMORY.md"),
|
|
1298
1883
|
...parseJsonlFile(activeSessionsPath, "sessions_active", options.logger),
|
|
1299
1884
|
...parseJsonlFile(archiveSessionsPath, "sessions_archive", options.logger),
|
|
1300
1885
|
...graphDocs,
|
|
1301
1886
|
...entitySummaryDocs,
|
|
1887
|
+
...wikiProjectionDocs,
|
|
1302
1888
|
];
|
|
1303
1889
|
docsCache = { signature, docs };
|
|
1304
1890
|
return docs;
|
|
@@ -1312,24 +1898,32 @@ function createReadStore(options) {
|
|
|
1312
1898
|
vectorFallbackCache = { signature, docs };
|
|
1313
1899
|
return docs;
|
|
1314
1900
|
}
|
|
1315
|
-
function getBm25Tokens(doc, signature) {
|
|
1901
|
+
function getBm25Tokens(doc, signature, channel) {
|
|
1316
1902
|
if (bm25TokenCacheSignature !== signature) {
|
|
1317
1903
|
bm25TokenCacheSignature = signature;
|
|
1318
1904
|
bm25TokenCache = new Map();
|
|
1319
1905
|
}
|
|
1320
|
-
const
|
|
1906
|
+
const channelText = channel === "fulltext"
|
|
1907
|
+
? ((doc.sourceText || "").trim() || (doc.summaryText || doc.text || ""))
|
|
1908
|
+
: ((doc.summaryText || doc.text || "").trim());
|
|
1909
|
+
const key = `${channel}:${doc.source}|${doc.id}|${channelText.length}|${channelText.slice(0, 64)}`;
|
|
1321
1910
|
const cached = bm25TokenCache.get(key);
|
|
1322
1911
|
if (cached) {
|
|
1323
1912
|
return cached;
|
|
1324
1913
|
}
|
|
1325
|
-
const tokens = tokenize(
|
|
1914
|
+
const tokens = tokenize(channelText);
|
|
1326
1915
|
bm25TokenCache.set(key, tokens);
|
|
1327
1916
|
return tokens;
|
|
1328
1917
|
}
|
|
1329
1918
|
async function searchMemory(args) {
|
|
1330
1919
|
const query = args.query?.trim();
|
|
1331
1920
|
if (!query) {
|
|
1332
|
-
return {
|
|
1921
|
+
return {
|
|
1922
|
+
results: [],
|
|
1923
|
+
semantic_results: [],
|
|
1924
|
+
keyword_results: [],
|
|
1925
|
+
strategy: "vector_sentence_and_keyword_parallel",
|
|
1926
|
+
};
|
|
1333
1927
|
}
|
|
1334
1928
|
const mode = args.mode === "lightweight" ? "lightweight" : "default";
|
|
1335
1929
|
const lightweightMode = mode === "lightweight";
|
|
@@ -1337,6 +1931,11 @@ function createReadStore(options) {
|
|
|
1337
1931
|
const hitStats = loadHitStats();
|
|
1338
1932
|
const intent = classifyIntent(query);
|
|
1339
1933
|
const preferredTypes = preferredEventTypes(intent);
|
|
1934
|
+
const plannedQueriesRaw = lightweightMode ? [query] : planQueryKeywords(query);
|
|
1935
|
+
const plannedQueries = plannedQueriesRaw.length > 0 ? plannedQueriesRaw : [query];
|
|
1936
|
+
const summaryChannelWeight = 1;
|
|
1937
|
+
const fulltextChannelWeight = 0.35;
|
|
1938
|
+
const maxCandidatePool = Math.max(1, Math.max(args.topK, 20));
|
|
1340
1939
|
let queryEmbedding = null;
|
|
1341
1940
|
const embeddingModel = options.embedding?.model || "";
|
|
1342
1941
|
const embeddingApiKey = options.embedding?.apiKey || "";
|
|
@@ -1393,11 +1992,11 @@ function createReadStore(options) {
|
|
|
1393
1992
|
}
|
|
1394
1993
|
}
|
|
1395
1994
|
const graphDocs = docs
|
|
1396
|
-
.filter(doc => doc.source
|
|
1995
|
+
.filter(doc => doc.source.startsWith("sessions_graph"))
|
|
1397
1996
|
.map(doc => {
|
|
1398
1997
|
const graphText = [
|
|
1399
1998
|
doc.text,
|
|
1400
|
-
...(doc.relations || []).map(relation => `${relation.source} ${relation.type} ${relation.target}`),
|
|
1999
|
+
...(doc.relations || []).map(relation => `${relation.source} ${relation.type} ${relation.target} ${relation.fact_status || doc.factStatus || "active"}`),
|
|
1401
2000
|
].join(" | ");
|
|
1402
2001
|
return {
|
|
1403
2002
|
...doc,
|
|
@@ -1406,153 +2005,302 @@ function createReadStore(options) {
|
|
|
1406
2005
|
});
|
|
1407
2006
|
const rulesDocs = docs.filter(doc => doc.source === "CORTEX_RULES.md");
|
|
1408
2007
|
const archiveDocs = docs.filter(doc => doc.source === "sessions_active" || doc.source === "sessions_archive");
|
|
1409
|
-
const bm25Terms = tokenize(query);
|
|
1410
2008
|
const bm25Corpus = [...rulesDocs, ...archiveDocs, ...vectorDocs, ...graphDocs];
|
|
1411
2009
|
const bm25Signature = `${docsCache?.signature || "na"}|vector:${vectorDocs.length}:${vectorDocs.slice(0, 40).map(item => `${item.id}:${item.text.length}`).join(",")}`;
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
2010
|
+
const rankByQuery = (plannedQuery, includeFulltext) => {
|
|
2011
|
+
const bm25Terms = tokenize(plannedQuery);
|
|
2012
|
+
const bm25StatsSummary = buildBm25Stats(bm25Corpus, bm25Terms, doc => getBm25Tokens(doc, bm25Signature, "summary"));
|
|
2013
|
+
const bm25StatsFulltext = includeFulltext
|
|
2014
|
+
? buildBm25Stats(bm25Corpus, bm25Terms, doc => getBm25Tokens(doc, bm25Signature, "fulltext"))
|
|
2015
|
+
: { avgDocLen: 1, docFreq: new Map() };
|
|
2016
|
+
const channels = {
|
|
2017
|
+
rules: [],
|
|
2018
|
+
archive: [],
|
|
2019
|
+
vector: [],
|
|
2020
|
+
graph: [],
|
|
2021
|
+
};
|
|
2022
|
+
const evaluateDoc = (doc, source) => {
|
|
2023
|
+
const summaryText = (doc.summaryText || doc.text || "").trim();
|
|
2024
|
+
const fulltextText = (doc.sourceText || "").trim();
|
|
2025
|
+
const summaryBm25 = bm25Score({
|
|
2026
|
+
queryTerms: bm25Terms,
|
|
2027
|
+
docText: summaryText,
|
|
2028
|
+
docTokens: getBm25Tokens(doc, bm25Signature, "summary"),
|
|
2029
|
+
docCount: bm25Corpus.length,
|
|
2030
|
+
avgDocLen: bm25StatsSummary.avgDocLen,
|
|
2031
|
+
docFreq: bm25StatsSummary.docFreq,
|
|
2032
|
+
});
|
|
2033
|
+
const fulltextBm25 = includeFulltext
|
|
2034
|
+
? bm25Score({
|
|
2035
|
+
queryTerms: bm25Terms,
|
|
2036
|
+
docText: fulltextText,
|
|
2037
|
+
docTokens: getBm25Tokens(doc, bm25Signature, "fulltext"),
|
|
2038
|
+
docCount: bm25Corpus.length,
|
|
2039
|
+
avgDocLen: bm25StatsFulltext.avgDocLen,
|
|
2040
|
+
docFreq: bm25StatsFulltext.docFreq,
|
|
2041
|
+
})
|
|
2042
|
+
: 0;
|
|
2043
|
+
const summaryCombined = scoreText(plannedQuery, summaryText) + summaryBm25 * readTuning.scoring.bm25Scale;
|
|
2044
|
+
const fulltextCombined = includeFulltext
|
|
2045
|
+
? scoreText(plannedQuery, fulltextText) + fulltextBm25 * readTuning.scoring.bm25Scale
|
|
2046
|
+
: 0;
|
|
2047
|
+
const lexicalCombined = summaryCombined * summaryChannelWeight + fulltextCombined * fulltextChannelWeight;
|
|
2048
|
+
const semantic = plannedQuery === query && queryEmbedding && Array.isArray(doc.embedding) && doc.embedding.length > 0
|
|
2049
|
+
? Math.max(0, cosineSimilarity(queryEmbedding, doc.embedding) * 5)
|
|
2050
|
+
: 0;
|
|
2051
|
+
if (lexicalCombined <= 0 && semantic <= 0) {
|
|
2052
|
+
return null;
|
|
2053
|
+
}
|
|
2054
|
+
if (source === "graph") {
|
|
2055
|
+
const status = docFactStatus(doc);
|
|
2056
|
+
if (status === "superseded" || status === "rejected") {
|
|
2057
|
+
return null;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
const recency = recencyScore(doc.timestamp, readTuning.recency.buckets);
|
|
2061
|
+
const quality = typeof doc.qualityScore === "number" ? Math.max(0, Math.min(1, doc.qualityScore)) : 0.5;
|
|
2062
|
+
const typeMatch = preferredTypes.length > 0 && doc.eventType
|
|
2063
|
+
? (preferredTypes.includes(doc.eventType) ? 1 : 0)
|
|
2064
|
+
: 0.5;
|
|
2065
|
+
const graphMatch = source === "graph" ? 1 : 0;
|
|
2066
|
+
const sourceBaseWeight = sourceWeight(source, intent);
|
|
2067
|
+
const sourceConfigWeight = customChannelWeight(source, options.fusion);
|
|
2068
|
+
const lengthNorm = lengthNormalizeFactor(doc, options.fusion);
|
|
2069
|
+
const baseWeighted = (readTuning.scoring.lexicalWeight * lexicalCombined +
|
|
2070
|
+
readTuning.scoring.semanticWeight * (semantic * lengthNorm) +
|
|
2071
|
+
readTuning.scoring.recencyWeight * recency +
|
|
2072
|
+
readTuning.scoring.qualityWeight * quality +
|
|
2073
|
+
readTuning.scoring.typeMatchWeight * typeMatch +
|
|
2074
|
+
readTuning.scoring.graphMatchWeight * graphMatch) * sourceBaseWeight * sourceConfigWeight;
|
|
2075
|
+
const decayFactor = computeDecayFactor(doc.id, doc.eventType, doc.timestamp, options.memoryDecay, hitStats);
|
|
2076
|
+
const weighted = baseWeighted * decayFactor;
|
|
2077
|
+
return {
|
|
2078
|
+
doc,
|
|
2079
|
+
source,
|
|
2080
|
+
lexical: lexicalCombined,
|
|
2081
|
+
bm25: summaryBm25 + fulltextBm25,
|
|
2082
|
+
semantic,
|
|
2083
|
+
recency,
|
|
2084
|
+
quality,
|
|
2085
|
+
typeMatch,
|
|
2086
|
+
graphMatch,
|
|
2087
|
+
decayFactor,
|
|
2088
|
+
weighted,
|
|
2089
|
+
summaryCombined,
|
|
2090
|
+
fulltextCombined,
|
|
2091
|
+
};
|
|
2092
|
+
};
|
|
2093
|
+
for (const doc of rulesDocs) {
|
|
2094
|
+
const candidate = evaluateDoc(doc, "rules");
|
|
2095
|
+
if (candidate)
|
|
2096
|
+
channels.rules.push(candidate);
|
|
2097
|
+
}
|
|
2098
|
+
for (const doc of archiveDocs) {
|
|
2099
|
+
const candidate = evaluateDoc(doc, "archive");
|
|
2100
|
+
if (candidate)
|
|
2101
|
+
channels.archive.push(candidate);
|
|
2102
|
+
}
|
|
2103
|
+
for (const doc of vectorDocs) {
|
|
2104
|
+
const candidate = evaluateDoc(doc, "vector");
|
|
2105
|
+
if (candidate)
|
|
2106
|
+
channels.vector.push(candidate);
|
|
2107
|
+
}
|
|
2108
|
+
for (const doc of graphDocs) {
|
|
2109
|
+
const candidate = evaluateDoc(doc, "graph");
|
|
2110
|
+
if (candidate)
|
|
2111
|
+
channels.graph.push(candidate);
|
|
2112
|
+
}
|
|
2113
|
+
const rrfMap = new Map();
|
|
2114
|
+
const weightedMap = new Map();
|
|
2115
|
+
const rrfK = readTuning.rrf.k;
|
|
2116
|
+
for (const key of Object.keys(channels)) {
|
|
2117
|
+
const list = channels[key].sort((a, b) => b.weighted - a.weighted);
|
|
2118
|
+
const capped = list.slice(0, channelQuota(key, args.topK, options.fusion));
|
|
2119
|
+
for (let i = 0; i < capped.length; i += 1) {
|
|
2120
|
+
const candidate = capped[i];
|
|
2121
|
+
const rrf = 1 / (rrfK + i + 1);
|
|
2122
|
+
const mergeKey = mergeKeyFromDoc(candidate.doc);
|
|
2123
|
+
rrfMap.set(mergeKey, (rrfMap.get(mergeKey) || 0) + rrf);
|
|
2124
|
+
const current = weightedMap.get(mergeKey);
|
|
2125
|
+
if (!current || candidate.weighted > current.weighted) {
|
|
2126
|
+
weightedMap.set(mergeKey, candidate);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return [...weightedMap.entries()]
|
|
2131
|
+
.map(([mergeKey, candidate]) => ({
|
|
2132
|
+
id: candidate.doc.id,
|
|
2133
|
+
merge_key: mergeKey,
|
|
2134
|
+
source_memory_id: candidate.doc.sourceMemoryId || "",
|
|
2135
|
+
source_memory_canonical_id: candidate.doc.sourceMemoryCanonicalId || "",
|
|
2136
|
+
source_event_id: candidate.doc.sourceEventId || "",
|
|
2137
|
+
source_field: candidate.doc.sourceField || "",
|
|
2138
|
+
text: candidate.doc.summaryText || candidate.doc.text,
|
|
2139
|
+
source_text: candidate.doc.sourceText ? candidate.doc.sourceText.slice(0, 4000) : "",
|
|
2140
|
+
source_excerpt: candidate.doc.sourceText ? candidate.doc.sourceText.slice(0, 360) : "",
|
|
2141
|
+
source_file: candidate.doc.sourceFile || "",
|
|
2142
|
+
source: candidate.doc.source,
|
|
2143
|
+
layer: candidate.doc.layer || "",
|
|
2144
|
+
event_type: candidate.doc.eventType || "",
|
|
2145
|
+
fact_status: docFactStatus(candidate.doc),
|
|
2146
|
+
wiki_ref: candidate.doc.wikiRef || "",
|
|
2147
|
+
quality_score: candidate.quality,
|
|
2148
|
+
timestamp: candidate.doc.timestamp ? new Date(candidate.doc.timestamp).toISOString() : "",
|
|
2149
|
+
evidence_ids: docEvidenceIds(candidate.doc),
|
|
2150
|
+
score: candidate.weighted + (rrfMap.get(mergeKey) || 0) * readTuning.rrf.weight,
|
|
2151
|
+
score_breakdown: {
|
|
2152
|
+
lexical: Number(candidate.lexical.toFixed(4)),
|
|
2153
|
+
bm25: Number(candidate.bm25.toFixed(4)),
|
|
2154
|
+
semantic: Number(candidate.semantic.toFixed(4)),
|
|
2155
|
+
recency: Number(candidate.recency.toFixed(4)),
|
|
2156
|
+
quality: Number(candidate.quality.toFixed(4)),
|
|
2157
|
+
type: Number(candidate.typeMatch.toFixed(4)),
|
|
2158
|
+
graph: Number(candidate.graphMatch.toFixed(4)),
|
|
2159
|
+
summary: Number(candidate.summaryCombined.toFixed(4)),
|
|
2160
|
+
fulltext: Number(candidate.fulltextCombined.toFixed(4)),
|
|
2161
|
+
decay: Number(candidate.decayFactor.toFixed(4)),
|
|
2162
|
+
rrf: Number(((rrfMap.get(mergeKey) || 0) * readTuning.rrf.weight).toFixed(4)),
|
|
2163
|
+
weighted: Number(candidate.weighted.toFixed(4)),
|
|
2164
|
+
},
|
|
2165
|
+
reason_tags: [
|
|
2166
|
+
`intent:${intent.toLowerCase()}`,
|
|
2167
|
+
candidate.summaryCombined > 0 ? "summary_hit" : "",
|
|
2168
|
+
candidate.fulltextCombined > 0 ? "fulltext_hit" : "",
|
|
2169
|
+
candidate.semantic > 0 ? "vector_hit" : "",
|
|
2170
|
+
candidate.lexical > 0 ? "lexical_hit" : "",
|
|
2171
|
+
candidate.typeMatch >= 1 ? "event_type_match" : "event_type_weak",
|
|
2172
|
+
candidate.recency >= 0.8 ? "recent" : "historical",
|
|
2173
|
+
candidate.quality >= 0.7 ? "high_quality" : "normal_quality",
|
|
2174
|
+
candidate.decayFactor < 1 ? `decay:${candidate.decayFactor.toFixed(3)}` : "decay:1.000",
|
|
2175
|
+
`source:${candidate.source}`,
|
|
2176
|
+
`merge_key:${mergeKey}`,
|
|
2177
|
+
`query_term:${plannedQuery}`,
|
|
2178
|
+
].filter(Boolean),
|
|
2179
|
+
matched_keywords: [plannedQuery],
|
|
2180
|
+
}))
|
|
2181
|
+
.sort((a, b) => b.score - a.score)
|
|
2182
|
+
.slice(0, maxCandidatePool);
|
|
1419
2183
|
};
|
|
1420
|
-
const
|
|
1421
|
-
const
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
2184
|
+
const queryRuns = await Promise.all(plannedQueries.map(async (plannedQuery) => {
|
|
2185
|
+
const stage1 = rankByQuery(plannedQuery, false);
|
|
2186
|
+
const needFallback = shouldTriggerFulltextFallback(stage1, args.topK);
|
|
2187
|
+
if (!needFallback || lightweightMode) {
|
|
2188
|
+
return { plannedQuery, ranked: stage1, fulltextFallback: false };
|
|
2189
|
+
}
|
|
2190
|
+
const stage2 = rankByQuery(plannedQuery, true);
|
|
2191
|
+
const merged = new Map();
|
|
2192
|
+
for (const item of [...stage1, ...stage2]) {
|
|
2193
|
+
const mergeKey = item.merge_key || item.id || "";
|
|
2194
|
+
const existing = merged.get(mergeKey);
|
|
2195
|
+
if (!existing) {
|
|
2196
|
+
merged.set(mergeKey, { ...item });
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
const best = Number(item.score || 0) > Number(existing.score || 0)
|
|
2200
|
+
? { ...existing, ...item }
|
|
2201
|
+
: { ...item, ...existing };
|
|
2202
|
+
const mergedReasonTags = uniqueStrings([
|
|
2203
|
+
...(Array.isArray(existing.reason_tags) ? existing.reason_tags.map(v => String(v)) : []),
|
|
2204
|
+
...(Array.isArray(item.reason_tags) ? item.reason_tags.map(v => String(v)) : []),
|
|
2205
|
+
]);
|
|
2206
|
+
const mergedEvidenceIds = uniqueStrings([
|
|
2207
|
+
...(Array.isArray(existing.evidence_ids) ? existing.evidence_ids.map(v => String(v)) : []),
|
|
2208
|
+
...(Array.isArray(item.evidence_ids) ? item.evidence_ids.map(v => String(v)) : []),
|
|
2209
|
+
]);
|
|
2210
|
+
const mergedKeywords = uniqueStrings([
|
|
2211
|
+
...(Array.isArray(existing.matched_keywords) ? existing.matched_keywords.map(v => String(v)) : []),
|
|
2212
|
+
...(Array.isArray(item.matched_keywords) ? item.matched_keywords.map(v => String(v)) : []),
|
|
2213
|
+
]);
|
|
2214
|
+
merged.set(mergeKey, {
|
|
2215
|
+
...best,
|
|
2216
|
+
score: Math.max(existing.score || 0, item.score || 0),
|
|
2217
|
+
reason_tags: mergedReasonTags,
|
|
2218
|
+
evidence_ids: mergedEvidenceIds,
|
|
2219
|
+
matched_keywords: mergedKeywords,
|
|
2220
|
+
});
|
|
1436
2221
|
}
|
|
1437
|
-
const recency = recencyScore(doc.timestamp, readTuning.recency.buckets);
|
|
1438
|
-
const quality = typeof doc.qualityScore === "number" ? Math.max(0, Math.min(1, doc.qualityScore)) : 0.5;
|
|
1439
|
-
const typeMatch = preferredTypes.length > 0 && doc.eventType
|
|
1440
|
-
? (preferredTypes.includes(doc.eventType) ? 1 : 0)
|
|
1441
|
-
: 0.5;
|
|
1442
|
-
const graphMatch = source === "graph" ? 1 : 0;
|
|
1443
|
-
const sourceBaseWeight = sourceWeight(source, intent);
|
|
1444
|
-
const sourceConfigWeight = customChannelWeight(source, options.fusion);
|
|
1445
|
-
const lengthNorm = lengthNormalizeFactor(doc, options.fusion);
|
|
1446
|
-
const baseWeighted = (readTuning.scoring.lexicalWeight * lexicalCombined +
|
|
1447
|
-
readTuning.scoring.semanticWeight * (semantic * lengthNorm) +
|
|
1448
|
-
readTuning.scoring.recencyWeight * recency +
|
|
1449
|
-
readTuning.scoring.qualityWeight * quality +
|
|
1450
|
-
readTuning.scoring.typeMatchWeight * typeMatch +
|
|
1451
|
-
readTuning.scoring.graphMatchWeight * graphMatch) * sourceBaseWeight * sourceConfigWeight;
|
|
1452
|
-
const decayFactor = computeDecayFactor(doc.id, doc.eventType, doc.timestamp, options.memoryDecay, hitStats);
|
|
1453
|
-
const weighted = baseWeighted * decayFactor;
|
|
1454
2222
|
return {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
2223
|
+
plannedQuery,
|
|
2224
|
+
ranked: [...merged.values()]
|
|
2225
|
+
.sort((a, b) => Number(b.score || 0) - Number(a.score || 0))
|
|
2226
|
+
.slice(0, maxCandidatePool)
|
|
2227
|
+
.map((item) => ({
|
|
2228
|
+
...item,
|
|
2229
|
+
reason_tags: uniqueStrings([
|
|
2230
|
+
...(Array.isArray(item.reason_tags) ? item.reason_tags.map((v) => String(v)) : []),
|
|
2231
|
+
"fulltext_fallback",
|
|
2232
|
+
]),
|
|
2233
|
+
})),
|
|
2234
|
+
fulltextFallback: true,
|
|
1466
2235
|
};
|
|
1467
|
-
};
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
if (candidate)
|
|
1481
|
-
channels.vector.push(candidate);
|
|
1482
|
-
}
|
|
1483
|
-
for (const doc of graphDocs) {
|
|
1484
|
-
const candidate = evaluateDoc(doc, "graph");
|
|
1485
|
-
if (candidate)
|
|
1486
|
-
channels.graph.push(candidate);
|
|
1487
|
-
}
|
|
1488
|
-
for (const key of Object.keys(channels)) {
|
|
1489
|
-
channels[key].sort((a, b) => b.weighted - a.weighted);
|
|
1490
|
-
combinedCandidates.push(...channels[key].slice(0, channelQuota(key, args.topK, options.fusion)));
|
|
1491
|
-
}
|
|
1492
|
-
const rrfMap = new Map();
|
|
1493
|
-
const weightedMap = new Map();
|
|
1494
|
-
const rrfK = readTuning.rrf.k;
|
|
1495
|
-
for (const key of Object.keys(channels)) {
|
|
1496
|
-
const list = channels[key];
|
|
1497
|
-
for (let i = 0; i < list.length; i += 1) {
|
|
1498
|
-
const candidate = list[i];
|
|
1499
|
-
const rrf = 1 / (rrfK + i + 1);
|
|
1500
|
-
const mergeKey = mergeKeyFromDoc(candidate.doc);
|
|
1501
|
-
rrfMap.set(mergeKey, (rrfMap.get(mergeKey) || 0) + rrf);
|
|
1502
|
-
const current = weightedMap.get(mergeKey);
|
|
1503
|
-
if (!current || candidate.weighted > current.weighted) {
|
|
1504
|
-
weightedMap.set(mergeKey, candidate);
|
|
2236
|
+
}));
|
|
2237
|
+
const mergedByQuery = new Map();
|
|
2238
|
+
for (const run of queryRuns) {
|
|
2239
|
+
for (const item of run.ranked) {
|
|
2240
|
+
const mergeKey = item.merge_key || item.id || "";
|
|
2241
|
+
const existing = mergedByQuery.get(mergeKey);
|
|
2242
|
+
if (!existing) {
|
|
2243
|
+
mergedByQuery.set(mergeKey, {
|
|
2244
|
+
...item,
|
|
2245
|
+
matched_keywords: uniqueStrings([run.plannedQuery]),
|
|
2246
|
+
fulltext_fallback_used: run.fulltextFallback,
|
|
2247
|
+
});
|
|
2248
|
+
continue;
|
|
1505
2249
|
}
|
|
2250
|
+
const mergedReasonTags = uniqueStrings([
|
|
2251
|
+
...(Array.isArray(existing.reason_tags) ? existing.reason_tags.map(v => String(v)) : []),
|
|
2252
|
+
...(Array.isArray(item.reason_tags) ? item.reason_tags.map(v => String(v)) : []),
|
|
2253
|
+
]);
|
|
2254
|
+
const mergedEvidenceIds = uniqueStrings([
|
|
2255
|
+
...(Array.isArray(existing.evidence_ids) ? existing.evidence_ids.map(v => String(v)) : []),
|
|
2256
|
+
...(Array.isArray(item.evidence_ids) ? item.evidence_ids.map(v => String(v)) : []),
|
|
2257
|
+
]);
|
|
2258
|
+
const matchedKeywords = uniqueStrings([
|
|
2259
|
+
...(Array.isArray(existing.matched_keywords) ? existing.matched_keywords.map(v => String(v)) : []),
|
|
2260
|
+
run.plannedQuery,
|
|
2261
|
+
]);
|
|
2262
|
+
const preferred = Number(item.score || 0) > Number(existing.score || 0)
|
|
2263
|
+
? { ...existing, ...item }
|
|
2264
|
+
: { ...item, ...existing };
|
|
2265
|
+
mergedByQuery.set(mergeKey, {
|
|
2266
|
+
...preferred,
|
|
2267
|
+
score: Math.max(existing.score || 0, item.score || 0),
|
|
2268
|
+
reason_tags: mergedReasonTags,
|
|
2269
|
+
evidence_ids: mergedEvidenceIds,
|
|
2270
|
+
matched_keywords: matchedKeywords,
|
|
2271
|
+
fulltext_fallback_used: Boolean(existing.fulltext_fallback_used) || run.fulltextFallback,
|
|
2272
|
+
});
|
|
1506
2273
|
}
|
|
1507
2274
|
}
|
|
1508
|
-
const
|
|
1509
|
-
.map((
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
},
|
|
1538
|
-
reason_tags: [
|
|
1539
|
-
`intent:${intent.toLowerCase()}`,
|
|
1540
|
-
candidate.semantic > 0 ? "vector_hit" : "lexical_hit",
|
|
1541
|
-
candidate.typeMatch >= 1 ? "event_type_match" : "event_type_weak",
|
|
1542
|
-
candidate.recency >= 0.8 ? "recent" : "historical",
|
|
1543
|
-
candidate.quality >= 0.7 ? "high_quality" : "normal_quality",
|
|
1544
|
-
candidate.decayFactor < 1 ? `decay:${candidate.decayFactor.toFixed(3)}` : "decay:1.000",
|
|
1545
|
-
`source:${candidate.source}`,
|
|
1546
|
-
`merge_key:${mergeKey}`,
|
|
1547
|
-
],
|
|
1548
|
-
}))
|
|
1549
|
-
.sort((a, b) => b.score - a.score)
|
|
1550
|
-
.slice(0, Math.max(1, Math.max(args.topK, 20)));
|
|
1551
|
-
const lexicalRanked = preRanked
|
|
1552
|
-
.map(doc => {
|
|
1553
|
-
const boost = withRecencyBoost(doc.score, doc.timestamp ? Date.parse(doc.timestamp) : undefined, readTuning.recency.buckets);
|
|
1554
|
-
return { ...doc, score: Number(boost.toFixed(4)) };
|
|
1555
|
-
});
|
|
2275
|
+
const lexicalRanked = [...mergedByQuery.values()]
|
|
2276
|
+
.map((item) => {
|
|
2277
|
+
const matchedKeywords = Array.isArray(item.matched_keywords)
|
|
2278
|
+
? uniqueStrings(item.matched_keywords.map(v => String(v)))
|
|
2279
|
+
: [query];
|
|
2280
|
+
const keywordBonus = Math.max(0, matchedKeywords.length - 1) * 0.12;
|
|
2281
|
+
const boosted = withRecencyBoost(Number(item.score || 0) + keywordBonus, typeof item.timestamp === "string" ? Date.parse(item.timestamp) : undefined, readTuning.recency.buckets);
|
|
2282
|
+
return {
|
|
2283
|
+
...item,
|
|
2284
|
+
matched_keywords: matchedKeywords,
|
|
2285
|
+
query_plan_keywords: plannedQueries,
|
|
2286
|
+
score: Number(boosted.toFixed(4)),
|
|
2287
|
+
reason_tags: uniqueStrings([
|
|
2288
|
+
...(Array.isArray(item.reason_tags) ? item.reason_tags.map((v) => String(v)) : []),
|
|
2289
|
+
`keyword_hits:${matchedKeywords.length}`,
|
|
2290
|
+
`query_plan:${plannedQueries.length}`,
|
|
2291
|
+
Boolean(item.fulltext_fallback_used) ? "fulltext_fallback_used" : "",
|
|
2292
|
+
]),
|
|
2293
|
+
};
|
|
2294
|
+
})
|
|
2295
|
+
.sort((a, b) => Number(b.score || 0) - Number(a.score || 0))
|
|
2296
|
+
.slice(0, maxCandidatePool);
|
|
2297
|
+
const isVectorSource = (value) => value.startsWith("vector_");
|
|
2298
|
+
const semanticResults = lexicalRanked
|
|
2299
|
+
.filter(item => isVectorSource(item.source) && Array.isArray(item.reason_tags) && item.reason_tags.includes("vector_hit"))
|
|
2300
|
+
.slice(0, Math.max(1, args.topK));
|
|
2301
|
+
const keywordResults = lexicalRanked
|
|
2302
|
+
.filter(item => isVectorSource(item.source) && Array.isArray(item.reason_tags) && item.reason_tags.includes("lexical_hit"))
|
|
2303
|
+
.slice(0, Math.max(1, args.topK));
|
|
1556
2304
|
const rerankerModel = options.reranker?.model || "";
|
|
1557
2305
|
const rerankerApiKey = options.reranker?.apiKey || "";
|
|
1558
2306
|
const rerankerBaseUrl = normalizeBaseUrl(options.reranker?.baseURL || options.reranker?.baseUrl);
|
|
@@ -1604,8 +2352,11 @@ function createReadStore(options) {
|
|
|
1604
2352
|
source: item.source,
|
|
1605
2353
|
layer: hit?.layer || "",
|
|
1606
2354
|
event_type: hit?.event_type || "",
|
|
2355
|
+
fact_status: hit?.fact_status || "active",
|
|
2356
|
+
wiki_ref: hit?.wiki_ref || "",
|
|
1607
2357
|
quality_score: hit?.quality_score ?? 0,
|
|
1608
2358
|
timestamp: hit?.timestamp || "",
|
|
2359
|
+
evidence_ids: Array.isArray(hit?.evidence_ids) ? hit?.evidence_ids : [],
|
|
1609
2360
|
score: Number(item.score.toFixed(4)),
|
|
1610
2361
|
score_breakdown: hit?.score_breakdown || {},
|
|
1611
2362
|
reason_tags: Array.isArray(hit?.reason_tags) ? hit?.reason_tags : [],
|
|
@@ -1618,6 +2369,9 @@ function createReadStore(options) {
|
|
|
1618
2369
|
channel: item.source,
|
|
1619
2370
|
source_file: hit?.source_file || "",
|
|
1620
2371
|
layer: hit?.layer || "",
|
|
2372
|
+
fact_status: hit?.fact_status || "active",
|
|
2373
|
+
wiki_ref: hit?.wiki_ref || "",
|
|
2374
|
+
evidence_ids: Array.isArray(hit?.evidence_ids) ? hit?.evidence_ids : [],
|
|
1621
2375
|
score_breakdown: hit?.score_breakdown || {},
|
|
1622
2376
|
reason_tags: Array.isArray(hit?.reason_tags) ? hit?.reason_tags : [],
|
|
1623
2377
|
},
|
|
@@ -1647,8 +2401,11 @@ function createReadStore(options) {
|
|
|
1647
2401
|
source: item.source,
|
|
1648
2402
|
layer: item.layer,
|
|
1649
2403
|
event_type: item.event_type,
|
|
2404
|
+
fact_status: item.fact_status || "active",
|
|
2405
|
+
wiki_ref: item.wiki_ref || "",
|
|
1650
2406
|
quality_score: item.quality_score,
|
|
1651
2407
|
timestamp: item.timestamp,
|
|
2408
|
+
evidence_ids: Array.isArray(item.evidence_ids) ? item.evidence_ids : [],
|
|
1652
2409
|
score: Number(item.score.toFixed(4)),
|
|
1653
2410
|
score_breakdown: item.score_breakdown || {},
|
|
1654
2411
|
reason_tags: Array.isArray(item.reason_tags) ? item.reason_tags : [],
|
|
@@ -1661,6 +2418,9 @@ function createReadStore(options) {
|
|
|
1661
2418
|
channel: item.source,
|
|
1662
2419
|
source_file: item.source_file || "",
|
|
1663
2420
|
layer: item.layer,
|
|
2421
|
+
fact_status: item.fact_status || "active",
|
|
2422
|
+
wiki_ref: item.wiki_ref || "",
|
|
2423
|
+
evidence_ids: Array.isArray(item.evidence_ids) ? item.evidence_ids : [],
|
|
1664
2424
|
score_breakdown: item.score_breakdown || {},
|
|
1665
2425
|
reason_tags: Array.isArray(item.reason_tags) ? item.reason_tags : [],
|
|
1666
2426
|
},
|
|
@@ -1689,8 +2449,11 @@ function createReadStore(options) {
|
|
|
1689
2449
|
source: item.source,
|
|
1690
2450
|
layer: item.layer,
|
|
1691
2451
|
event_type: item.event_type,
|
|
2452
|
+
fact_status: item.fact_status || "active",
|
|
2453
|
+
wiki_ref: item.wiki_ref || "",
|
|
1692
2454
|
quality_score: item.quality_score,
|
|
1693
2455
|
timestamp: item.timestamp,
|
|
2456
|
+
evidence_ids: Array.isArray(item.evidence_ids) ? item.evidence_ids : [],
|
|
1694
2457
|
score: Number(item.score.toFixed(4)),
|
|
1695
2458
|
score_breakdown: item.score_breakdown || {},
|
|
1696
2459
|
reason_tags: Array.isArray(item.reason_tags) ? item.reason_tags : [],
|
|
@@ -1703,6 +2466,9 @@ function createReadStore(options) {
|
|
|
1703
2466
|
channel: item.source,
|
|
1704
2467
|
source_file: item.source_file || "",
|
|
1705
2468
|
layer: item.layer,
|
|
2469
|
+
fact_status: item.fact_status || "active",
|
|
2470
|
+
wiki_ref: item.wiki_ref || "",
|
|
2471
|
+
evidence_ids: Array.isArray(item.evidence_ids) ? item.evidence_ids : [],
|
|
1706
2472
|
score_breakdown: item.score_breakdown || {},
|
|
1707
2473
|
reason_tags: Array.isArray(item.reason_tags) ? item.reason_tags : [],
|
|
1708
2474
|
},
|
|
@@ -1742,6 +2508,18 @@ function createReadStore(options) {
|
|
|
1742
2508
|
if (!Array.isArray(fusion.evidence_ids) || fusion.evidence_ids.length === 0) {
|
|
1743
2509
|
throw new Error("fusion_missing_whitelisted_evidence");
|
|
1744
2510
|
}
|
|
2511
|
+
const fusedEvidenceIds = uniqueStrings(fusion.evidence_ids.flatMap(item => {
|
|
2512
|
+
const linked = ranked.find(candidate => candidate.id === item);
|
|
2513
|
+
if (!linked)
|
|
2514
|
+
return [item];
|
|
2515
|
+
const linkedEvidence = Array.isArray(linked.evidence_ids) ? linked.evidence_ids : [];
|
|
2516
|
+
return linkedEvidence.length > 0 ? linkedEvidence : [item];
|
|
2517
|
+
}));
|
|
2518
|
+
const wikiRefs = uniqueStrings(fusion.evidence_ids.flatMap(item => {
|
|
2519
|
+
const linked = ranked.find(candidate => candidate.id === item);
|
|
2520
|
+
const wikiRef = typeof linked?.wiki_ref === "string" ? linked.wiki_ref : "";
|
|
2521
|
+
return wikiRef ? [wikiRef] : [];
|
|
2522
|
+
}));
|
|
1745
2523
|
const fulltextFetchHints = (Array.isArray(fusion.need_fulltext_event_ids) ? fusion.need_fulltext_event_ids : [])
|
|
1746
2524
|
.map(eventId => {
|
|
1747
2525
|
const linked = ranked.find(item => item.source_memory_id === eventId ||
|
|
@@ -1777,21 +2555,33 @@ function createReadStore(options) {
|
|
|
1777
2555
|
fused_risks: fusion.risks || [],
|
|
1778
2556
|
fused_action_items: fusion.action_items || [],
|
|
1779
2557
|
fused_conflicts: fusion.conflicts,
|
|
1780
|
-
|
|
2558
|
+
evidence_ids: fusedEvidenceIds,
|
|
2559
|
+
wiki_refs: wikiRefs,
|
|
2560
|
+
fused_evidence_ids: fusedEvidenceIds,
|
|
1781
2561
|
fused_need_fulltext_event_ids: fusion.need_fulltext_event_ids || [],
|
|
1782
2562
|
fulltext_fetch_hints: fulltextFetchHints,
|
|
1783
2563
|
};
|
|
1784
2564
|
const authoritative = options.fusion?.authoritative !== false;
|
|
1785
2565
|
if (authoritative) {
|
|
1786
2566
|
markHit(Array.isArray(fusion.evidence_ids) ? fusion.evidence_ids : []);
|
|
1787
|
-
return {
|
|
2567
|
+
return {
|
|
2568
|
+
results: [fusedItem],
|
|
2569
|
+
semantic_results: semanticResults,
|
|
2570
|
+
keyword_results: keywordResults,
|
|
2571
|
+
strategy: "vector_sentence_and_keyword_parallel",
|
|
2572
|
+
};
|
|
1788
2573
|
}
|
|
1789
2574
|
const merged = [fusedItem, ...ranked];
|
|
1790
2575
|
markHit([
|
|
1791
2576
|
...(Array.isArray(fusion.evidence_ids) ? fusion.evidence_ids : []),
|
|
1792
2577
|
...ranked.map(item => item.id),
|
|
1793
2578
|
]);
|
|
1794
|
-
return {
|
|
2579
|
+
return {
|
|
2580
|
+
results: merged.slice(0, Math.max(1, args.topK)),
|
|
2581
|
+
semantic_results: semanticResults,
|
|
2582
|
+
keyword_results: keywordResults,
|
|
2583
|
+
strategy: "vector_sentence_and_keyword_parallel",
|
|
2584
|
+
};
|
|
1795
2585
|
}
|
|
1796
2586
|
}
|
|
1797
2587
|
catch (error) {
|
|
@@ -1800,7 +2590,12 @@ function createReadStore(options) {
|
|
|
1800
2590
|
}
|
|
1801
2591
|
const finalRanked = ranked.slice(0, Math.max(1, args.topK));
|
|
1802
2592
|
markHit(finalRanked.map(item => item.id));
|
|
1803
|
-
return {
|
|
2593
|
+
return {
|
|
2594
|
+
results: finalRanked,
|
|
2595
|
+
semantic_results: semanticResults,
|
|
2596
|
+
keyword_results: keywordResults,
|
|
2597
|
+
strategy: "vector_sentence_and_keyword_parallel",
|
|
2598
|
+
};
|
|
1804
2599
|
}
|
|
1805
2600
|
async function getHotContext(args) {
|
|
1806
2601
|
const limit = Math.max(1, args.limit);
|