openclaw-cortex-memory 0.1.0-Alpha.20 → 0.1.0-Alpha.21
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/README.md +163 -228
- package/SKILL.md +71 -332
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -6
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +208 -12
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -1
- package/dist/src/dedup/three_stage_deduplicator.js +1 -2
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +31 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +262 -14
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +1 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +65 -15
- package/dist/src/graph/ontology.d.ts.map +1 -1
- package/dist/src/graph/ontology.js +316 -4
- package/dist/src/graph/ontology.js.map +1 -1
- package/dist/src/quality/llm_output_validator.d.ts +48 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +404 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +284 -8
- package/dist/src/reflect/reflector.js.map +1 -1
- package/dist/src/rules/rule_store.d.ts.map +1 -1
- package/dist/src/rules/rule_store.js +75 -16
- package/dist/src/rules/rule_store.js.map +1 -1
- package/dist/src/session/session_end.d.ts +20 -43
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +21 -233
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +20 -7
- package/dist/src/store/archive_store.d.ts.map +1 -1
- package/dist/src/store/archive_store.js +96 -61
- package/dist/src/store/archive_store.js.map +1 -1
- package/dist/src/store/graph_memory_store.d.ts +44 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +168 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +27 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +653 -94
- 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 +7 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +15 -3
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +48 -0
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +277 -78
- package/dist/src/sync/session_sync.js.map +1 -1
- package/openclaw.plugin.json +208 -12
- package/package.json +6 -4
|
@@ -37,6 +37,177 @@ exports.createReadStore = createReadStore;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const module_1 = require("module");
|
|
40
|
+
function buildEntityGraphSummaryDocs(graphDocs) {
|
|
41
|
+
const entityEdges = new Map();
|
|
42
|
+
const entityLatestTs = new Map();
|
|
43
|
+
const entitySession = new Map();
|
|
44
|
+
for (const doc of graphDocs) {
|
|
45
|
+
const ts = typeof doc.timestamp === "number" ? doc.timestamp : 0;
|
|
46
|
+
const sessionId = typeof doc.sessionId === "string" ? doc.sessionId : "";
|
|
47
|
+
const relations = Array.isArray(doc.relations) ? doc.relations : [];
|
|
48
|
+
for (const relation of relations) {
|
|
49
|
+
const source = (relation.source || "").trim();
|
|
50
|
+
const target = (relation.target || "").trim();
|
|
51
|
+
const type = (relation.type || "").trim();
|
|
52
|
+
if (!source || !target || !type)
|
|
53
|
+
continue;
|
|
54
|
+
if (!entityEdges.has(source))
|
|
55
|
+
entityEdges.set(source, []);
|
|
56
|
+
if (!entityEdges.has(target))
|
|
57
|
+
entityEdges.set(target, []);
|
|
58
|
+
entityEdges.get(source)?.push({ source, target, type });
|
|
59
|
+
entityEdges.get(target)?.push({ source, target, type });
|
|
60
|
+
entityLatestTs.set(source, Math.max(entityLatestTs.get(source) || 0, ts));
|
|
61
|
+
entityLatestTs.set(target, Math.max(entityLatestTs.get(target) || 0, ts));
|
|
62
|
+
if (sessionId) {
|
|
63
|
+
if (!entitySession.has(source))
|
|
64
|
+
entitySession.set(source, sessionId);
|
|
65
|
+
if (!entitySession.has(target))
|
|
66
|
+
entitySession.set(target, sessionId);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const output = [];
|
|
71
|
+
for (const [entity, edges] of entityEdges.entries()) {
|
|
72
|
+
if (!edges.length)
|
|
73
|
+
continue;
|
|
74
|
+
const outgoing = edges.filter(edge => edge.source === entity);
|
|
75
|
+
const incoming = edges.filter(edge => edge.target === entity);
|
|
76
|
+
const typeCounter = new Map();
|
|
77
|
+
for (const edge of edges) {
|
|
78
|
+
typeCounter.set(edge.type, (typeCounter.get(edge.type) || 0) + 1);
|
|
79
|
+
}
|
|
80
|
+
const typeSummary = [...typeCounter.entries()]
|
|
81
|
+
.sort((a, b) => b[1] - a[1])
|
|
82
|
+
.map(([type, count]) => `${type}:${count}`)
|
|
83
|
+
.join(", ");
|
|
84
|
+
const sortedOutgoing = [...outgoing].sort((a, b) => a.type.localeCompare(b.type));
|
|
85
|
+
const sortedIncoming = [...incoming].sort((a, b) => a.type.localeCompare(b.type));
|
|
86
|
+
const cappedOutgoing = sortedOutgoing.slice(0, 20);
|
|
87
|
+
const cappedIncoming = sortedIncoming.slice(0, 20);
|
|
88
|
+
const relationFacts = edges
|
|
89
|
+
.slice(0, 40)
|
|
90
|
+
.map(edge => `${edge.source} ${edge.type} ${edge.target}`)
|
|
91
|
+
.join(" | ");
|
|
92
|
+
const outgoingBlock = cappedOutgoing.length > 0
|
|
93
|
+
? cappedOutgoing.map((edge, index) => `${index + 1}. ${edge.source} -[${edge.type}]-> ${edge.target}`).join("\n")
|
|
94
|
+
: "none";
|
|
95
|
+
const incomingBlock = cappedIncoming.length > 0
|
|
96
|
+
? cappedIncoming.map((edge, index) => `${index + 1}. ${edge.source} -[${edge.type}]-> ${edge.target}`).join("\n")
|
|
97
|
+
: "none";
|
|
98
|
+
const summaryText = [
|
|
99
|
+
`# Graph Entity Summary`,
|
|
100
|
+
`entity: ${entity}`,
|
|
101
|
+
``,
|
|
102
|
+
`## Stats`,
|
|
103
|
+
`relation_total: ${edges.length}`,
|
|
104
|
+
`outgoing_total: ${outgoing.length}`,
|
|
105
|
+
`incoming_total: ${incoming.length}`,
|
|
106
|
+
typeSummary ? `relation_type_distribution: ${typeSummary}` : "relation_type_distribution: none",
|
|
107
|
+
``,
|
|
108
|
+
`## Outgoing Relations`,
|
|
109
|
+
outgoingBlock,
|
|
110
|
+
outgoing.length > cappedOutgoing.length ? `...truncated_outgoing: ${outgoing.length - cappedOutgoing.length}` : "",
|
|
111
|
+
``,
|
|
112
|
+
`## Incoming Relations`,
|
|
113
|
+
incomingBlock,
|
|
114
|
+
incoming.length > cappedIncoming.length ? `...truncated_incoming: ${incoming.length - cappedIncoming.length}` : "",
|
|
115
|
+
``,
|
|
116
|
+
`## Relation Facts`,
|
|
117
|
+
relationFacts || "none",
|
|
118
|
+
].filter(Boolean).join("\n");
|
|
119
|
+
output.push({
|
|
120
|
+
id: `gph_entity_${entity.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/gi, "_")}`,
|
|
121
|
+
text: summaryText,
|
|
122
|
+
source: "sessions_graph_entity",
|
|
123
|
+
timestamp: (entityLatestTs.get(entity) || 0) > 0 ? entityLatestTs.get(entity) : undefined,
|
|
124
|
+
layer: "archive",
|
|
125
|
+
sourceMemoryId: entity,
|
|
126
|
+
sessionId: entitySession.get(entity) || undefined,
|
|
127
|
+
entities: [entity],
|
|
128
|
+
relations: edges,
|
|
129
|
+
eventType: "graph_summary",
|
|
130
|
+
qualityScore: 1,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return output;
|
|
134
|
+
}
|
|
135
|
+
const DEFAULT_READ_TUNING = {
|
|
136
|
+
scoring: {
|
|
137
|
+
lexicalWeight: 0.2,
|
|
138
|
+
bm25Scale: 2,
|
|
139
|
+
semanticWeight: 0.3,
|
|
140
|
+
recencyWeight: 0.1,
|
|
141
|
+
qualityWeight: 0.15,
|
|
142
|
+
typeMatchWeight: 0.15,
|
|
143
|
+
graphMatchWeight: 0.1,
|
|
144
|
+
},
|
|
145
|
+
rrf: {
|
|
146
|
+
k: 60,
|
|
147
|
+
weight: 1.5,
|
|
148
|
+
},
|
|
149
|
+
recency: {
|
|
150
|
+
buckets: [
|
|
151
|
+
{ maxAgeHours: 12, score: 1, bonus: 0.6 },
|
|
152
|
+
{ maxAgeHours: 24, score: 0.8, bonus: 0.6 },
|
|
153
|
+
{ maxAgeHours: 72, score: 0.6, bonus: 0.3 },
|
|
154
|
+
{ maxAgeHours: 168, score: 0.4, bonus: 0.3 },
|
|
155
|
+
{ maxAgeHours: 720, score: 0.2, bonus: 0 },
|
|
156
|
+
{ maxAgeHours: Number.POSITIVE_INFINITY, score: 0.05, bonus: 0 },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
autoContext: {
|
|
160
|
+
queryMaxChars: 80,
|
|
161
|
+
lightweightSearch: true,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
function resolveReadTuning(options) {
|
|
165
|
+
const configuredBuckets = Array.isArray(options?.recency?.buckets)
|
|
166
|
+
? options?.recency?.buckets
|
|
167
|
+
.filter(item => item &&
|
|
168
|
+
Number.isFinite(item.maxAgeHours) &&
|
|
169
|
+
item.maxAgeHours > 0 &&
|
|
170
|
+
Number.isFinite(item.score) &&
|
|
171
|
+
item.score >= 0 &&
|
|
172
|
+
Number.isFinite(item.bonus))
|
|
173
|
+
.map(item => ({
|
|
174
|
+
maxAgeHours: item.maxAgeHours,
|
|
175
|
+
score: Math.max(0, item.score),
|
|
176
|
+
bonus: Math.max(0, item.bonus),
|
|
177
|
+
}))
|
|
178
|
+
: [];
|
|
179
|
+
const sortedBuckets = configuredBuckets
|
|
180
|
+
.sort((a, b) => a.maxAgeHours - b.maxAgeHours);
|
|
181
|
+
const buckets = sortedBuckets.length > 0 ? sortedBuckets : DEFAULT_READ_TUNING.recency.buckets;
|
|
182
|
+
const numberOr = (value, fallback, min) => {
|
|
183
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < min) {
|
|
184
|
+
return fallback;
|
|
185
|
+
}
|
|
186
|
+
return value;
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
scoring: {
|
|
190
|
+
lexicalWeight: numberOr(options?.scoring?.lexicalWeight, DEFAULT_READ_TUNING.scoring.lexicalWeight, 0),
|
|
191
|
+
bm25Scale: numberOr(options?.scoring?.bm25Scale, DEFAULT_READ_TUNING.scoring.bm25Scale, 0),
|
|
192
|
+
semanticWeight: numberOr(options?.scoring?.semanticWeight, DEFAULT_READ_TUNING.scoring.semanticWeight, 0),
|
|
193
|
+
recencyWeight: numberOr(options?.scoring?.recencyWeight, DEFAULT_READ_TUNING.scoring.recencyWeight, 0),
|
|
194
|
+
qualityWeight: numberOr(options?.scoring?.qualityWeight, DEFAULT_READ_TUNING.scoring.qualityWeight, 0),
|
|
195
|
+
typeMatchWeight: numberOr(options?.scoring?.typeMatchWeight, DEFAULT_READ_TUNING.scoring.typeMatchWeight, 0),
|
|
196
|
+
graphMatchWeight: numberOr(options?.scoring?.graphMatchWeight, DEFAULT_READ_TUNING.scoring.graphMatchWeight, 0),
|
|
197
|
+
},
|
|
198
|
+
rrf: {
|
|
199
|
+
k: Math.floor(numberOr(options?.rrf?.k, DEFAULT_READ_TUNING.rrf.k, 1)),
|
|
200
|
+
weight: numberOr(options?.rrf?.weight, DEFAULT_READ_TUNING.rrf.weight, 0),
|
|
201
|
+
},
|
|
202
|
+
recency: {
|
|
203
|
+
buckets,
|
|
204
|
+
},
|
|
205
|
+
autoContext: {
|
|
206
|
+
queryMaxChars: Math.floor(numberOr(options?.autoContext?.queryMaxChars, DEFAULT_READ_TUNING.autoContext.queryMaxChars, 20)),
|
|
207
|
+
lightweightSearch: options?.autoContext?.lightweightSearch !== false,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
40
211
|
function safeReadFile(filePath) {
|
|
41
212
|
try {
|
|
42
213
|
if (!fs.existsSync(filePath)) {
|
|
@@ -116,6 +287,14 @@ function bm25Score(args) {
|
|
|
116
287
|
return score;
|
|
117
288
|
}
|
|
118
289
|
function normalizeRecordText(record) {
|
|
290
|
+
const summary = typeof record.summary === "string" ? record.summary.trim() : "";
|
|
291
|
+
const sourceText = typeof record.source_text === "string" ? record.source_text.trim() : "";
|
|
292
|
+
if (summary && sourceText) {
|
|
293
|
+
return [
|
|
294
|
+
`summary: ${summary}`,
|
|
295
|
+
`source_text: ${sourceText}`,
|
|
296
|
+
].join("\n");
|
|
297
|
+
}
|
|
119
298
|
const direct = [record.content, record.summary, record.text, record.message]
|
|
120
299
|
.find(v => typeof v === "string" && v.trim());
|
|
121
300
|
if (direct) {
|
|
@@ -158,33 +337,21 @@ function parseJsonlFile(filePath, sourceLabel, logger) {
|
|
|
158
337
|
}
|
|
159
338
|
try {
|
|
160
339
|
const parsed = JSON.parse(trimmed);
|
|
161
|
-
const
|
|
340
|
+
const summaryText = typeof parsed.summary === "string" ? parsed.summary.trim() : "";
|
|
341
|
+
const sourceText = typeof parsed.source_text === "string" ? parsed.source_text.trim() : "";
|
|
342
|
+
const text = summaryText || normalizeRecordText(parsed);
|
|
162
343
|
if (!text.trim()) {
|
|
163
344
|
continue;
|
|
164
345
|
}
|
|
165
346
|
const id = typeof parsed.id === "string" ? parsed.id : `${sourceLabel}:${docs.length + 1}`;
|
|
166
347
|
const timestampValue = typeof parsed.timestamp === "string" ? Date.parse(parsed.timestamp) : NaN;
|
|
167
|
-
const entities = Array.isArray(parsed.entities)
|
|
168
|
-
? parsed.entities.map(item => (typeof item === "string" ? item.trim() : "")).filter(Boolean)
|
|
169
|
-
: [];
|
|
170
|
-
const relations = Array.isArray(parsed.relations)
|
|
171
|
-
? parsed.relations
|
|
172
|
-
.map(item => {
|
|
173
|
-
if (typeof item !== "object" || item === null)
|
|
174
|
-
return null;
|
|
175
|
-
const relation = item;
|
|
176
|
-
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
177
|
-
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
178
|
-
const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
|
|
179
|
-
if (!source || !target)
|
|
180
|
-
return null;
|
|
181
|
-
return { source, target, type };
|
|
182
|
-
})
|
|
183
|
-
.filter((item) => Boolean(item))
|
|
184
|
-
: [];
|
|
185
348
|
docs.push({
|
|
186
349
|
id,
|
|
187
350
|
text,
|
|
351
|
+
summaryText: summaryText || text,
|
|
352
|
+
sourceText: sourceText || undefined,
|
|
353
|
+
sourceEventId: typeof parsed.source_event_id === "string" ? parsed.source_event_id : undefined,
|
|
354
|
+
sourceFile: typeof parsed.source_file === "string" ? parsed.source_file : undefined,
|
|
188
355
|
source: sourceLabel,
|
|
189
356
|
timestamp: Number.isFinite(timestampValue) ? timestampValue : undefined,
|
|
190
357
|
layer: parsed.layer === "active" || parsed.layer === "archive"
|
|
@@ -202,8 +369,8 @@ function parseJsonlFile(filePath, sourceLabel, logger) {
|
|
|
202
369
|
charCount: typeof parsed.char_count === "number" ? parsed.char_count : undefined,
|
|
203
370
|
tokenCount: typeof parsed.token_count === "number" ? parsed.token_count : undefined,
|
|
204
371
|
sessionId: typeof parsed.session_id === "string" ? parsed.session_id : undefined,
|
|
205
|
-
entities,
|
|
206
|
-
relations,
|
|
372
|
+
entities: [],
|
|
373
|
+
relations: [],
|
|
207
374
|
});
|
|
208
375
|
}
|
|
209
376
|
catch (error) {
|
|
@@ -232,35 +399,83 @@ function parseMarkdownFile(filePath, sourceLabel) {
|
|
|
232
399
|
},
|
|
233
400
|
];
|
|
234
401
|
}
|
|
235
|
-
function
|
|
402
|
+
function extractPrioritizedRuleLines(text, maxRules) {
|
|
403
|
+
if (!text.trim() || maxRules <= 0) {
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
const lines = text
|
|
407
|
+
.split(/\r?\n/)
|
|
408
|
+
.map(line => line.trim())
|
|
409
|
+
.filter(Boolean)
|
|
410
|
+
.filter(line => !/^core rules and knowledge extracted/i.test(line))
|
|
411
|
+
.filter(line => !/^core rules\b/i.test(line));
|
|
412
|
+
if (lines.length === 0) {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
const dedupedFromTail = [];
|
|
416
|
+
const seen = new Set();
|
|
417
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
418
|
+
const line = lines[i];
|
|
419
|
+
const key = line.toLowerCase().replace(/\s+/g, " ").trim();
|
|
420
|
+
if (!key || seen.has(key)) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
seen.add(key);
|
|
424
|
+
dedupedFromTail.push(line);
|
|
425
|
+
}
|
|
426
|
+
const scored = dedupedFromTail.map((line, indexFromTail) => {
|
|
427
|
+
let score = 0;
|
|
428
|
+
if (/(must|should|ensure|avoid|prefer|always|never|fallback|verify|validate|retry|sanitize)/i.test(line)) {
|
|
429
|
+
score += 3;
|
|
430
|
+
}
|
|
431
|
+
if (/(fix|resolved|success|stable|deploy|release|incident|rollback|constraint|decision)/i.test(line)) {
|
|
432
|
+
score += 2;
|
|
433
|
+
}
|
|
434
|
+
if (/(确保|避免|优先|必须|建议|回退|重试|校验|稳定|发布|决策)/.test(line)) {
|
|
435
|
+
score += 2;
|
|
436
|
+
}
|
|
437
|
+
if (line.length >= 30 && line.length <= 220) {
|
|
438
|
+
score += 2;
|
|
439
|
+
}
|
|
440
|
+
else if (line.length > 220) {
|
|
441
|
+
score -= 1;
|
|
442
|
+
}
|
|
443
|
+
if (/[.!?]$/.test(line)) {
|
|
444
|
+
score += 1;
|
|
445
|
+
}
|
|
446
|
+
score += Math.max(0, 2 - indexFromTail * 0.08);
|
|
447
|
+
return { line, score, indexFromTail };
|
|
448
|
+
});
|
|
449
|
+
const selected = scored
|
|
450
|
+
.sort((a, b) => (b.score - a.score) || (a.indexFromTail - b.indexFromTail))
|
|
451
|
+
.slice(0, maxRules)
|
|
452
|
+
.sort((a, b) => a.indexFromTail - b.indexFromTail)
|
|
453
|
+
.map(item => item.line);
|
|
454
|
+
return selected;
|
|
455
|
+
}
|
|
456
|
+
function withRecencyBoost(score, timestamp, buckets) {
|
|
236
457
|
if (!timestamp) {
|
|
237
458
|
return score;
|
|
238
459
|
}
|
|
239
460
|
const ageHours = (Date.now() - timestamp) / (1000 * 60 * 60);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return score + 0.3;
|
|
461
|
+
for (const bucket of buckets) {
|
|
462
|
+
if (ageHours <= bucket.maxAgeHours) {
|
|
463
|
+
return score + bucket.bonus;
|
|
464
|
+
}
|
|
245
465
|
}
|
|
246
466
|
return score;
|
|
247
467
|
}
|
|
248
|
-
function recencyScore(timestamp) {
|
|
468
|
+
function recencyScore(timestamp, buckets) {
|
|
249
469
|
if (!timestamp) {
|
|
250
470
|
return 0;
|
|
251
471
|
}
|
|
252
472
|
const ageHours = (Date.now() - timestamp) / (1000 * 60 * 60);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (ageHours < 168)
|
|
260
|
-
return 0.4;
|
|
261
|
-
if (ageHours < 720)
|
|
262
|
-
return 0.2;
|
|
263
|
-
return 0.05;
|
|
473
|
+
for (const bucket of buckets) {
|
|
474
|
+
if (ageHours <= bucket.maxAgeHours) {
|
|
475
|
+
return bucket.score;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return 0;
|
|
264
479
|
}
|
|
265
480
|
function eventTypeHalfLifeDays(eventType, options) {
|
|
266
481
|
const fallback = typeof options?.defaultHalfLifeDays === "number" && options.defaultHalfLifeDays > 0
|
|
@@ -322,10 +537,11 @@ function normalizeBaseUrl(value) {
|
|
|
322
537
|
return "";
|
|
323
538
|
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
324
539
|
}
|
|
325
|
-
const READ_FUSION_PROMPT_VERSION = "read-fusion.v1.
|
|
540
|
+
const READ_FUSION_PROMPT_VERSION = "read-fusion.v1.2.0";
|
|
326
541
|
const READ_FUSION_REGRESSION_SAMPLES = [
|
|
327
|
-
"
|
|
328
|
-
"
|
|
542
|
+
"Example A: if archive and vector refer to the same source_memory_id, keep one main conclusion and keep the rest as supporting evidence.",
|
|
543
|
+
"Example B: if conclusions conflict, write conflicts and explain prioritization in canonical_answer (time, quality, explicitness).",
|
|
544
|
+
"Example C: if summary/excerpt is insufficient, return event ids in need_fulltext_event_ids for full-text lookup.",
|
|
329
545
|
];
|
|
330
546
|
function cosineSimilarity(left, right) {
|
|
331
547
|
if (left.length === 0 || right.length === 0) {
|
|
@@ -450,19 +666,19 @@ async function requestRerank(args) {
|
|
|
450
666
|
}
|
|
451
667
|
function classifyIntent(query) {
|
|
452
668
|
const text = query.toLowerCase();
|
|
453
|
-
const relationHints = /(
|
|
669
|
+
const relationHints = /(关系|依赖|关联|上下游|图谱|拓扑|graph|relation|entity|dependency)/i;
|
|
454
670
|
if (relationHints.test(text))
|
|
455
671
|
return "RELATION_DISCOVERY";
|
|
456
|
-
const troubleHints = /(
|
|
672
|
+
const troubleHints = /(报错|错误|异常|失败|超时|无法|崩溃|故障|修复|bug|error|failed|timeout|fix)/i;
|
|
457
673
|
if (troubleHints.test(text))
|
|
458
674
|
return "TROUBLESHOOTING";
|
|
459
675
|
const preferenceHints = /(偏好|习惯|口味|喜欢|不喜欢|偏向|preference)/i;
|
|
460
676
|
if (preferenceHints.test(text))
|
|
461
677
|
return "PREFERENCE_PROFILE";
|
|
462
|
-
const timelineHints = /(
|
|
678
|
+
const timelineHints = /(最近|上次|之前|时间线|历史|timeline|history)/i;
|
|
463
679
|
if (timelineHints.test(text))
|
|
464
680
|
return "TIMELINE_REVIEW";
|
|
465
|
-
const decisionHints = /(
|
|
681
|
+
const decisionHints = /(方案|决策|选择|建议|取舍|权衡|tradeoff|plan)/i;
|
|
466
682
|
if (decisionHints.test(text))
|
|
467
683
|
return "DECISION_SUPPORT";
|
|
468
684
|
return "FACT_LOOKUP";
|
|
@@ -546,6 +762,50 @@ function channelQuota(source, topK, options) {
|
|
|
546
762
|
return Math.max(8, topK * 3);
|
|
547
763
|
return Math.max(12, topK * 4);
|
|
548
764
|
}
|
|
765
|
+
function parseJsonStringArray(value) {
|
|
766
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
767
|
+
return [];
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
const parsed = JSON.parse(value);
|
|
771
|
+
if (!Array.isArray(parsed)) {
|
|
772
|
+
return [];
|
|
773
|
+
}
|
|
774
|
+
return parsed
|
|
775
|
+
.map(item => (typeof item === "string" ? item.trim() : ""))
|
|
776
|
+
.filter(Boolean);
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
return [];
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function parseJsonRelations(value) {
|
|
783
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
784
|
+
return [];
|
|
785
|
+
}
|
|
786
|
+
try {
|
|
787
|
+
const parsed = JSON.parse(value);
|
|
788
|
+
if (!Array.isArray(parsed)) {
|
|
789
|
+
return [];
|
|
790
|
+
}
|
|
791
|
+
return parsed
|
|
792
|
+
.map(item => {
|
|
793
|
+
if (typeof item !== "object" || item === null)
|
|
794
|
+
return null;
|
|
795
|
+
const relation = item;
|
|
796
|
+
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
797
|
+
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
798
|
+
const type = typeof relation.type === "string" && relation.type.trim() ? relation.type.trim() : "related_to";
|
|
799
|
+
if (!source || !target)
|
|
800
|
+
return null;
|
|
801
|
+
return { source, target, type };
|
|
802
|
+
})
|
|
803
|
+
.filter((item) => Boolean(item));
|
|
804
|
+
}
|
|
805
|
+
catch {
|
|
806
|
+
return [];
|
|
807
|
+
}
|
|
808
|
+
}
|
|
549
809
|
async function searchLanceDb(args) {
|
|
550
810
|
try {
|
|
551
811
|
const require = (0, module_1.createRequire)(__filename);
|
|
@@ -585,12 +845,8 @@ async function searchLanceDb(args) {
|
|
|
585
845
|
if (!id || !summary)
|
|
586
846
|
continue;
|
|
587
847
|
const ts = typeof record.timestamp === "string" ? Date.parse(record.timestamp) : NaN;
|
|
588
|
-
const entities =
|
|
589
|
-
|
|
590
|
-
: [];
|
|
591
|
-
const relations = typeof record.relations_json === "string"
|
|
592
|
-
? JSON.parse(record.relations_json)
|
|
593
|
-
: [];
|
|
848
|
+
const entities = parseJsonStringArray(record.entities_json);
|
|
849
|
+
const relations = parseJsonRelations(record.relations_json);
|
|
594
850
|
docs.push({
|
|
595
851
|
id,
|
|
596
852
|
text: summary,
|
|
@@ -599,6 +855,10 @@ async function searchLanceDb(args) {
|
|
|
599
855
|
layer: record.layer === "active" || record.layer === "archive" ? record.layer : undefined,
|
|
600
856
|
sourceMemoryId: typeof record.source_memory_id === "string" ? record.source_memory_id : undefined,
|
|
601
857
|
sourceMemoryCanonicalId: typeof record.source_memory_canonical_id === "string" ? record.source_memory_canonical_id : undefined,
|
|
858
|
+
sourceEventId: typeof record.source_event_id === "string" ? record.source_event_id : undefined,
|
|
859
|
+
sourceField: record.source_field === "summary" || record.source_field === "evidence"
|
|
860
|
+
? record.source_field
|
|
861
|
+
: undefined,
|
|
602
862
|
embedding: Array.isArray(record.vector) ? record.vector.filter(item => Number.isFinite(item)) : undefined,
|
|
603
863
|
eventType: typeof record.event_type === "string" ? record.event_type : undefined,
|
|
604
864
|
qualityScore: typeof record.quality_score === "number" ? record.quality_score : undefined,
|
|
@@ -659,6 +919,10 @@ function parseVectorFallback(filePath, logger) {
|
|
|
659
919
|
layer: parsed.layer === "active" || parsed.layer === "archive" ? parsed.layer : undefined,
|
|
660
920
|
sourceMemoryId: typeof parsed.source_memory_id === "string" ? parsed.source_memory_id : undefined,
|
|
661
921
|
sourceMemoryCanonicalId: typeof parsed.source_memory_canonical_id === "string" ? parsed.source_memory_canonical_id : undefined,
|
|
922
|
+
sourceEventId: typeof parsed.source_event_id === "string" ? parsed.source_event_id : undefined,
|
|
923
|
+
sourceField: parsed.source_field === "summary" || parsed.source_field === "evidence"
|
|
924
|
+
? parsed.source_field
|
|
925
|
+
: undefined,
|
|
662
926
|
embedding: Array.isArray(parsed.embedding) ? parsed.embedding.filter(item => Number.isFinite(item)) : undefined,
|
|
663
927
|
eventType: typeof parsed.event_type === "string" ? parsed.event_type.trim() : undefined,
|
|
664
928
|
qualityScore: typeof parsed.quality_score === "number" ? parsed.quality_score : undefined,
|
|
@@ -676,34 +940,75 @@ function parseVectorFallback(filePath, logger) {
|
|
|
676
940
|
return docs;
|
|
677
941
|
}
|
|
678
942
|
async function requestFusion(args) {
|
|
943
|
+
const candidateIdSet = new Set(args.candidates.map(item => item.id));
|
|
679
944
|
const endpoint = args.llm.baseUrl.endsWith("/chat/completions")
|
|
680
945
|
? args.llm.baseUrl
|
|
681
946
|
: `${args.llm.baseUrl}/chat/completions`;
|
|
682
947
|
const evidenceText = args.candidates
|
|
683
|
-
.map((item, index) =>
|
|
948
|
+
.map((item, index) => {
|
|
949
|
+
const excerpt = (item.source_excerpt || "").trim();
|
|
950
|
+
const sourceFile = (item.source_file || "").trim();
|
|
951
|
+
const sourceMemoryId = (item.source_memory_id || "").trim();
|
|
952
|
+
const sourceMemoryCanonicalId = (item.source_memory_canonical_id || "").trim();
|
|
953
|
+
const sourceLayer = (item.source_layer || "").trim();
|
|
954
|
+
const sourceEventId = (item.source_event_id || "").trim();
|
|
955
|
+
const sourceField = (item.source_field || "").trim();
|
|
956
|
+
const extraParts = [];
|
|
957
|
+
if (sourceMemoryId)
|
|
958
|
+
extraParts.push(`source_memory_id=${sourceMemoryId}`);
|
|
959
|
+
if (sourceMemoryCanonicalId)
|
|
960
|
+
extraParts.push(`source_memory_canonical_id=${sourceMemoryCanonicalId}`);
|
|
961
|
+
if (sourceLayer)
|
|
962
|
+
extraParts.push(`source_layer=${sourceLayer}`);
|
|
963
|
+
if (sourceEventId)
|
|
964
|
+
extraParts.push(`source_event_id=${sourceEventId}`);
|
|
965
|
+
if (sourceField)
|
|
966
|
+
extraParts.push(`source_field=${sourceField}`);
|
|
967
|
+
if (sourceFile)
|
|
968
|
+
extraParts.push(`source_file=${sourceFile}`);
|
|
969
|
+
if (excerpt)
|
|
970
|
+
extraParts.push(`source_excerpt=${excerpt}`);
|
|
971
|
+
const extra = extraParts.length > 0 ? `\n ${extraParts.join("\n ")}` : "";
|
|
972
|
+
return `${index + 1}. [${item.id}] (${item.source}, score=${item.score.toFixed(4)}) ${item.text}${extra}`;
|
|
973
|
+
})
|
|
684
974
|
.join("\n")
|
|
685
975
|
.slice(0, 18000);
|
|
686
976
|
const prompt = [
|
|
687
977
|
`prompt_version=${READ_FUSION_PROMPT_VERSION}`,
|
|
688
|
-
"
|
|
689
|
-
"
|
|
690
|
-
"
|
|
691
|
-
"
|
|
692
|
-
"
|
|
693
|
-
"
|
|
694
|
-
"
|
|
695
|
-
"
|
|
696
|
-
"
|
|
697
|
-
"
|
|
698
|
-
"
|
|
978
|
+
"You are a memory retrieval fusion engine. Fuse multi-channel evidence into a structured answer package for the agent.",
|
|
979
|
+
"Core values and principles:",
|
|
980
|
+
"A) Truthfulness first: do not fabricate; do not infer beyond evidence.",
|
|
981
|
+
"B) Evidence first: every key conclusion must be traceable via evidence_ids.",
|
|
982
|
+
"C) Make conflicts explicit: write conflicts instead of silently overriding.",
|
|
983
|
+
"D) Be transparent about uncertainty: put uncertain parts in coverage_note.",
|
|
984
|
+
"E) Summary-first: prefer summary evidence for conclusions; source_excerpt is supporting evidence.",
|
|
985
|
+
"F) Same-source dedup: merge duplicate evidence from the same source_memory_id/source_memory_canonical_id.",
|
|
986
|
+
"G) Full-text recall: if summary/excerpt is insufficient, return event ids in need_fulltext_event_ids.",
|
|
987
|
+
"Source channel semantics:",
|
|
988
|
+
"- rules: policy/constraints; use for what should be done.",
|
|
989
|
+
"- archive: event-level stable facts (summary-first).",
|
|
990
|
+
"- vector: semantic neighbors for recall; source_field=summary/evidence indicates chunk role.",
|
|
991
|
+
"- graph: entity-relation structure; prefer for dependency/relationship questions.",
|
|
992
|
+
"Query alignment:",
|
|
993
|
+
"- answer the user query first; ignore evidence unrelated to the query.",
|
|
994
|
+
"- when evidence conflicts, prioritize recency + quality + explicitness and record the conflict.",
|
|
995
|
+
"Return strict JSON only:",
|
|
996
|
+
"{\"canonical_answer\": string, \"coverage_note\": string, \"facts\": [{\"text\": string, \"evidence_ids\": string[]}], \"timeline\": [{\"when\": string, \"event\": string, \"evidence_ids\": string[]}], \"entities\": [{\"name\": string, \"role\": string}], \"decisions\": [{\"decision\": string, \"rationale\": string, \"evidence_ids\": string[]}], \"fixes\": [{\"issue\": string, \"fix\": string, \"evidence_ids\": string[]}], \"preferences\": [{\"subject\": string, \"preference\": string, \"evidence_ids\": string[]}], \"risks\": [{\"risk\": string, \"mitigation\": string, \"evidence_ids\": string[]}], \"action_items\": [{\"item\": string, \"owner\": string, \"status\": string, \"evidence_ids\": string[]}], \"conflicts\": [{\"topic\": string, \"details\": string}], \"evidence_ids\": string[], \"need_fulltext_event_ids\": string[], \"confidence\": number}",
|
|
997
|
+
"Output constraints:",
|
|
998
|
+
"1) canonical_answer must be directly usable.",
|
|
999
|
+
"2) facts: usually 3-12 items; prefer high-quality evidence.",
|
|
1000
|
+
"3) evidence_ids must come from input candidate ids.",
|
|
1001
|
+
"4) conflicts must be [] when no conflict exists.",
|
|
1002
|
+
"5) confidence must be within [0, 1].",
|
|
1003
|
+
"6) uncertain parts must be explicitly stated in coverage_note.",
|
|
699
1004
|
...READ_FUSION_REGRESSION_SAMPLES,
|
|
700
1005
|
].join("\n");
|
|
701
1006
|
const body = {
|
|
702
1007
|
model: args.llm.model,
|
|
703
1008
|
temperature: 0.1,
|
|
704
1009
|
messages: [
|
|
705
|
-
{ role: "system", content: "
|
|
706
|
-
{ role: "user", content: `${prompt}\n\n
|
|
1010
|
+
{ role: "system", content: "Output JSON only. No extra text." },
|
|
1011
|
+
{ role: "user", content: `${prompt}\n\nQuery:\n${args.query}\n\nCandidate Evidence:\n${evidenceText}` },
|
|
707
1012
|
],
|
|
708
1013
|
};
|
|
709
1014
|
let lastError = null;
|
|
@@ -739,6 +1044,13 @@ async function requestFusion(args) {
|
|
|
739
1044
|
const evidenceIds = Array.isArray(parsed.evidence_ids)
|
|
740
1045
|
? parsed.evidence_ids.filter(item => typeof item === "string" && item.trim())
|
|
741
1046
|
: [];
|
|
1047
|
+
const whitelistedEvidenceIds = [...new Set(evidenceIds.filter(id => candidateIdSet.has(id)))];
|
|
1048
|
+
const needFulltextEventIds = Array.isArray(parsed.need_fulltext_event_ids)
|
|
1049
|
+
? [...new Set(parsed.need_fulltext_event_ids
|
|
1050
|
+
.filter(item => typeof item === "string")
|
|
1051
|
+
.map(item => item.trim())
|
|
1052
|
+
.filter(Boolean))]
|
|
1053
|
+
: [];
|
|
742
1054
|
return {
|
|
743
1055
|
canonical_answer: parsed.canonical_answer.trim().slice(0, 6000),
|
|
744
1056
|
coverage_note: typeof parsed.coverage_note === "string" ? parsed.coverage_note.trim().slice(0, 1200) : "",
|
|
@@ -751,7 +1063,8 @@ async function requestFusion(args) {
|
|
|
751
1063
|
risks: Array.isArray(parsed.risks) ? parsed.risks : [],
|
|
752
1064
|
action_items: Array.isArray(parsed.action_items) ? parsed.action_items : [],
|
|
753
1065
|
conflicts: Array.isArray(parsed.conflicts) ? parsed.conflicts : [],
|
|
754
|
-
evidence_ids:
|
|
1066
|
+
evidence_ids: whitelistedEvidenceIds,
|
|
1067
|
+
need_fulltext_event_ids: needFulltextEventIds,
|
|
755
1068
|
confidence: typeof parsed.confidence === "number"
|
|
756
1069
|
? Math.max(0, Math.min(1, parsed.confidence))
|
|
757
1070
|
: 0.5,
|
|
@@ -772,6 +1085,13 @@ function createReadStore(options) {
|
|
|
772
1085
|
let vectorFallbackCache = null;
|
|
773
1086
|
let bm25TokenCacheSignature = "";
|
|
774
1087
|
let bm25TokenCache = new Map();
|
|
1088
|
+
let hitStatsCache = null;
|
|
1089
|
+
let hitStatsDirty = false;
|
|
1090
|
+
let hitStatsPendingMutations = 0;
|
|
1091
|
+
let lastHitStatsFlushAt = 0;
|
|
1092
|
+
const hitStatsFlushIntervalMs = 5000;
|
|
1093
|
+
const hitStatsFlushBatch = 24;
|
|
1094
|
+
const readTuning = resolveReadTuning(options.readTuning);
|
|
775
1095
|
function fileSignature(filePath) {
|
|
776
1096
|
try {
|
|
777
1097
|
if (!fs.existsSync(filePath)) {
|
|
@@ -785,22 +1105,30 @@ function createReadStore(options) {
|
|
|
785
1105
|
}
|
|
786
1106
|
}
|
|
787
1107
|
function loadHitStats() {
|
|
1108
|
+
if (hitStatsCache) {
|
|
1109
|
+
return hitStatsCache;
|
|
1110
|
+
}
|
|
788
1111
|
try {
|
|
789
1112
|
if (!fs.existsSync(hitStatsPath)) {
|
|
790
|
-
|
|
1113
|
+
hitStatsCache = { items: {} };
|
|
1114
|
+
return hitStatsCache;
|
|
791
1115
|
}
|
|
792
1116
|
const content = fs.readFileSync(hitStatsPath, "utf-8").trim();
|
|
793
1117
|
if (!content) {
|
|
794
|
-
|
|
1118
|
+
hitStatsCache = { items: {} };
|
|
1119
|
+
return hitStatsCache;
|
|
795
1120
|
}
|
|
796
1121
|
const parsed = JSON.parse(content);
|
|
797
1122
|
if (!parsed || typeof parsed !== "object" || !parsed.items || typeof parsed.items !== "object") {
|
|
798
|
-
|
|
1123
|
+
hitStatsCache = { items: {} };
|
|
1124
|
+
return hitStatsCache;
|
|
799
1125
|
}
|
|
800
|
-
|
|
1126
|
+
hitStatsCache = parsed;
|
|
1127
|
+
return hitStatsCache;
|
|
801
1128
|
}
|
|
802
1129
|
catch {
|
|
803
|
-
|
|
1130
|
+
hitStatsCache = { items: {} };
|
|
1131
|
+
return hitStatsCache;
|
|
804
1132
|
}
|
|
805
1133
|
}
|
|
806
1134
|
function saveHitStats(state) {
|
|
@@ -815,6 +1143,21 @@ function createReadStore(options) {
|
|
|
815
1143
|
options.logger.warn(`Failed to persist read hit stats: ${error}`);
|
|
816
1144
|
}
|
|
817
1145
|
}
|
|
1146
|
+
function maybeFlushHitStats(force) {
|
|
1147
|
+
if (!hitStatsDirty || !hitStatsCache) {
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const now = Date.now();
|
|
1151
|
+
if (!force &&
|
|
1152
|
+
hitStatsPendingMutations < hitStatsFlushBatch &&
|
|
1153
|
+
(now - lastHitStatsFlushAt) < hitStatsFlushIntervalMs) {
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
saveHitStats(hitStatsCache);
|
|
1157
|
+
hitStatsDirty = false;
|
|
1158
|
+
hitStatsPendingMutations = 0;
|
|
1159
|
+
lastHitStatsFlushAt = now;
|
|
1160
|
+
}
|
|
818
1161
|
function markHit(ids) {
|
|
819
1162
|
if (!ids.length) {
|
|
820
1163
|
return;
|
|
@@ -839,27 +1182,143 @@ function createReadStore(options) {
|
|
|
839
1182
|
})
|
|
840
1183
|
.slice(0, 20000);
|
|
841
1184
|
state.items = Object.fromEntries(entries);
|
|
842
|
-
|
|
1185
|
+
hitStatsCache = state;
|
|
1186
|
+
hitStatsDirty = true;
|
|
1187
|
+
hitStatsPendingMutations += ids.length;
|
|
1188
|
+
maybeFlushHitStats(false);
|
|
843
1189
|
}
|
|
844
1190
|
function loadAllDocuments() {
|
|
845
1191
|
const cortexRulesPath = path.join(memoryRoot, "CORTEX_RULES.md");
|
|
846
1192
|
const memoryMdPath = path.join(memoryRoot, "MEMORY.md");
|
|
847
1193
|
const activeSessionsPath = path.join(memoryRoot, "sessions", "active", "sessions.jsonl");
|
|
848
1194
|
const archiveSessionsPath = path.join(memoryRoot, "sessions", "archive", "sessions.jsonl");
|
|
1195
|
+
const graphMemoryPath = path.join(memoryRoot, "graph", "memory.jsonl");
|
|
849
1196
|
const signature = [
|
|
850
1197
|
fileSignature(cortexRulesPath),
|
|
851
1198
|
fileSignature(memoryMdPath),
|
|
852
1199
|
fileSignature(activeSessionsPath),
|
|
853
1200
|
fileSignature(archiveSessionsPath),
|
|
1201
|
+
fileSignature(graphMemoryPath),
|
|
854
1202
|
].join("|");
|
|
855
1203
|
if (docsCache && docsCache.signature === signature) {
|
|
856
1204
|
return docsCache.docs;
|
|
857
1205
|
}
|
|
1206
|
+
const archiveEventTypeById = new Map();
|
|
1207
|
+
if (fs.existsSync(archiveSessionsPath)) {
|
|
1208
|
+
const archiveContent = safeReadFile(archiveSessionsPath);
|
|
1209
|
+
for (const line of archiveContent.split(/\r?\n/)) {
|
|
1210
|
+
const trimmed = line.trim();
|
|
1211
|
+
if (!trimmed)
|
|
1212
|
+
continue;
|
|
1213
|
+
try {
|
|
1214
|
+
const parsed = JSON.parse(trimmed);
|
|
1215
|
+
const id = typeof parsed.id === "string" ? parsed.id.trim() : "";
|
|
1216
|
+
const eventType = typeof parsed.event_type === "string" ? parsed.event_type.trim() : "";
|
|
1217
|
+
if (id && eventType) {
|
|
1218
|
+
archiveEventTypeById.set(id, eventType);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
catch { }
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const graphDocs = [];
|
|
1225
|
+
if (fs.existsSync(graphMemoryPath)) {
|
|
1226
|
+
const graphContent = safeReadFile(graphMemoryPath);
|
|
1227
|
+
for (const line of graphContent.split(/\r?\n/)) {
|
|
1228
|
+
const trimmed = line.trim();
|
|
1229
|
+
if (!trimmed)
|
|
1230
|
+
continue;
|
|
1231
|
+
try {
|
|
1232
|
+
const parsed = JSON.parse(trimmed);
|
|
1233
|
+
const id = typeof parsed.id === "string" ? parsed.id : "";
|
|
1234
|
+
const sourceEventId = typeof parsed.source_event_id === "string" ? parsed.source_event_id : "";
|
|
1235
|
+
const archiveEventId = typeof parsed.archive_event_id === "string" ? parsed.archive_event_id : "";
|
|
1236
|
+
const eventRefId = archiveEventId || sourceEventId;
|
|
1237
|
+
const sessionId = typeof parsed.session_id === "string" ? parsed.session_id : "";
|
|
1238
|
+
const sourceLayer = typeof parsed.source_layer === "string" ? parsed.source_layer : "";
|
|
1239
|
+
const sourceFile = typeof parsed.source_file === "string" ? parsed.source_file : "";
|
|
1240
|
+
const timestamp = typeof parsed.timestamp === "string" ? Date.parse(parsed.timestamp) : NaN;
|
|
1241
|
+
const entities = Array.isArray(parsed.entities)
|
|
1242
|
+
? parsed.entities.map((item) => (typeof item === "string" ? item.trim() : "")).filter(Boolean)
|
|
1243
|
+
: [];
|
|
1244
|
+
const entityTypes = typeof parsed.entity_types === "object" && parsed.entity_types !== null
|
|
1245
|
+
? parsed.entity_types
|
|
1246
|
+
: {};
|
|
1247
|
+
const relations = Array.isArray(parsed.relations)
|
|
1248
|
+
? parsed.relations
|
|
1249
|
+
.map((item) => {
|
|
1250
|
+
if (typeof item !== "object" || item === null)
|
|
1251
|
+
return null;
|
|
1252
|
+
const relation = item;
|
|
1253
|
+
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
1254
|
+
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
1255
|
+
const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
|
|
1256
|
+
if (!source || !target)
|
|
1257
|
+
return null;
|
|
1258
|
+
return { source, target, type };
|
|
1259
|
+
})
|
|
1260
|
+
.filter((item) => Boolean(item))
|
|
1261
|
+
: [];
|
|
1262
|
+
const eventType = (typeof parsed.event_type === "string" ? parsed.event_type : "") || archiveEventTypeById.get(eventRefId) || "";
|
|
1263
|
+
const entityLines = entities.length > 0
|
|
1264
|
+
? entities.map((entity, index) => {
|
|
1265
|
+
const entityType = entityTypes[entity];
|
|
1266
|
+
return `${index + 1}. ${entity}${entityType ? ` (${entityType})` : ""}`;
|
|
1267
|
+
}).join("\n")
|
|
1268
|
+
: "none";
|
|
1269
|
+
const relationLines = relations.length > 0
|
|
1270
|
+
? relations.map((relation, index) => `${index + 1}. ${relation.source} -[${relation.type}]-> ${relation.target}`).join("\n")
|
|
1271
|
+
: "none";
|
|
1272
|
+
const relationFacts = relations.length > 0
|
|
1273
|
+
? relations.map(relation => `${relation.source} ${relation.type} ${relation.target}`).join(" | ")
|
|
1274
|
+
: "none";
|
|
1275
|
+
const text = [
|
|
1276
|
+
`# Graph Record`,
|
|
1277
|
+
`record_id: ${id}`,
|
|
1278
|
+
`source_event_id: ${sourceEventId || archiveEventId || "unknown"}`,
|
|
1279
|
+
`source_layer: ${sourceLayer || "unknown"}`,
|
|
1280
|
+
`archive_event_id: ${archiveEventId || "n/a"}`,
|
|
1281
|
+
`event_type: ${eventType || "unknown"}`,
|
|
1282
|
+
`session_id: ${sessionId || "unknown"}`,
|
|
1283
|
+
`source_file: ${sourceFile || "unknown"}`,
|
|
1284
|
+
``,
|
|
1285
|
+
`## Entities`,
|
|
1286
|
+
entityLines,
|
|
1287
|
+
``,
|
|
1288
|
+
`## Relations`,
|
|
1289
|
+
relationLines,
|
|
1290
|
+
``,
|
|
1291
|
+
`## Relation Facts`,
|
|
1292
|
+
relationFacts,
|
|
1293
|
+
].join("\n");
|
|
1294
|
+
if (id && text.trim()) {
|
|
1295
|
+
graphDocs.push({
|
|
1296
|
+
id,
|
|
1297
|
+
text,
|
|
1298
|
+
source: "sessions_graph",
|
|
1299
|
+
timestamp: Number.isFinite(timestamp) ? timestamp : undefined,
|
|
1300
|
+
layer: sourceLayer === "active_only" ? "active" : "archive",
|
|
1301
|
+
sourceMemoryId: eventRefId || id,
|
|
1302
|
+
sourceEventId: sourceEventId || archiveEventId || undefined,
|
|
1303
|
+
sessionId,
|
|
1304
|
+
entities,
|
|
1305
|
+
relations,
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
catch (error) {
|
|
1310
|
+
options.logger.debug(`Skipping invalid graph memory line: ${error}`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
const entitySummaryDocs = buildEntityGraphSummaryDocs(graphDocs);
|
|
858
1315
|
const docs = [
|
|
859
1316
|
...parseMarkdownFile(cortexRulesPath, "CORTEX_RULES.md"),
|
|
860
1317
|
...parseMarkdownFile(memoryMdPath, "MEMORY.md"),
|
|
861
1318
|
...parseJsonlFile(activeSessionsPath, "sessions_active", options.logger),
|
|
862
1319
|
...parseJsonlFile(archiveSessionsPath, "sessions_archive", options.logger),
|
|
1320
|
+
...graphDocs,
|
|
1321
|
+
...entitySummaryDocs,
|
|
863
1322
|
];
|
|
864
1323
|
docsCache = { signature, docs };
|
|
865
1324
|
return docs;
|
|
@@ -892,6 +1351,8 @@ function createReadStore(options) {
|
|
|
892
1351
|
if (!query) {
|
|
893
1352
|
return { results: [] };
|
|
894
1353
|
}
|
|
1354
|
+
const mode = args.mode === "lightweight" ? "lightweight" : "default";
|
|
1355
|
+
const lightweightMode = mode === "lightweight";
|
|
895
1356
|
const docs = loadAllDocuments();
|
|
896
1357
|
const hitStats = loadHitStats();
|
|
897
1358
|
const intent = classifyIntent(query);
|
|
@@ -900,7 +1361,7 @@ function createReadStore(options) {
|
|
|
900
1361
|
const embeddingModel = options.embedding?.model || "";
|
|
901
1362
|
const embeddingApiKey = options.embedding?.apiKey || "";
|
|
902
1363
|
const embeddingBaseUrl = normalizeBaseUrl(options.embedding?.baseURL || options.embedding?.baseUrl);
|
|
903
|
-
if (embeddingModel && embeddingApiKey && embeddingBaseUrl) {
|
|
1364
|
+
if (!lightweightMode && embeddingModel && embeddingApiKey && embeddingBaseUrl) {
|
|
904
1365
|
try {
|
|
905
1366
|
queryEmbedding = await requestEmbedding({
|
|
906
1367
|
text: query,
|
|
@@ -921,8 +1382,38 @@ function createReadStore(options) {
|
|
|
921
1382
|
? []
|
|
922
1383
|
: loadVectorFallbackCached();
|
|
923
1384
|
const vectorDocs = [...vectorDocsFromLance, ...vectorDocsFallback];
|
|
1385
|
+
const archiveSourceById = new Map();
|
|
1386
|
+
for (const doc of docs) {
|
|
1387
|
+
if (doc.source !== "sessions_archive")
|
|
1388
|
+
continue;
|
|
1389
|
+
const key = (doc.sourceMemoryId || doc.id || "").trim();
|
|
1390
|
+
if (!key)
|
|
1391
|
+
continue;
|
|
1392
|
+
archiveSourceById.set(key, {
|
|
1393
|
+
sourceText: doc.sourceText,
|
|
1394
|
+
summaryText: doc.summaryText || doc.text,
|
|
1395
|
+
sourceFile: doc.sourceFile,
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
for (const doc of vectorDocs) {
|
|
1399
|
+
const key = (doc.sourceMemoryId || "").trim();
|
|
1400
|
+
if (!key)
|
|
1401
|
+
continue;
|
|
1402
|
+
const linked = archiveSourceById.get(key);
|
|
1403
|
+
if (!linked)
|
|
1404
|
+
continue;
|
|
1405
|
+
if (!doc.sourceText && linked.sourceText) {
|
|
1406
|
+
doc.sourceText = linked.sourceText;
|
|
1407
|
+
}
|
|
1408
|
+
if (!doc.summaryText && linked.summaryText) {
|
|
1409
|
+
doc.summaryText = linked.summaryText;
|
|
1410
|
+
}
|
|
1411
|
+
if (!doc.sourceFile && linked.sourceFile) {
|
|
1412
|
+
doc.sourceFile = linked.sourceFile;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
924
1415
|
const graphDocs = docs
|
|
925
|
-
.filter(doc =>
|
|
1416
|
+
.filter(doc => doc.source === "sessions_graph" || doc.source === "sessions_graph_entity")
|
|
926
1417
|
.map(doc => {
|
|
927
1418
|
const graphText = [
|
|
928
1419
|
doc.text,
|
|
@@ -934,7 +1425,7 @@ function createReadStore(options) {
|
|
|
934
1425
|
};
|
|
935
1426
|
});
|
|
936
1427
|
const rulesDocs = docs.filter(doc => doc.source === "CORTEX_RULES.md");
|
|
937
|
-
const archiveDocs = docs.filter(doc => doc.source.
|
|
1428
|
+
const archiveDocs = docs.filter(doc => doc.source === "sessions_active" || doc.source === "sessions_archive");
|
|
938
1429
|
const bm25Terms = tokenize(query);
|
|
939
1430
|
const bm25Corpus = [...rulesDocs, ...archiveDocs, ...vectorDocs, ...graphDocs];
|
|
940
1431
|
const bm25Signature = `${docsCache?.signature || "na"}|vector:${vectorDocs.length}:${vectorDocs.slice(0, 40).map(item => `${item.id}:${item.text.length}`).join(",")}`;
|
|
@@ -956,14 +1447,14 @@ function createReadStore(options) {
|
|
|
956
1447
|
avgDocLen: bm25Stats.avgDocLen,
|
|
957
1448
|
docFreq: bm25Stats.docFreq,
|
|
958
1449
|
});
|
|
959
|
-
const lexicalCombined = lexical + bm25 *
|
|
1450
|
+
const lexicalCombined = lexical + bm25 * readTuning.scoring.bm25Scale;
|
|
960
1451
|
const semantic = queryEmbedding && Array.isArray(doc.embedding) && doc.embedding.length > 0
|
|
961
1452
|
? Math.max(0, cosineSimilarity(queryEmbedding, doc.embedding) * 5)
|
|
962
1453
|
: 0;
|
|
963
1454
|
if (lexicalCombined <= 0 && semantic <= 0) {
|
|
964
1455
|
return null;
|
|
965
1456
|
}
|
|
966
|
-
const recency = recencyScore(doc.timestamp);
|
|
1457
|
+
const recency = recencyScore(doc.timestamp, readTuning.recency.buckets);
|
|
967
1458
|
const quality = typeof doc.qualityScore === "number" ? Math.max(0, Math.min(1, doc.qualityScore)) : 0.5;
|
|
968
1459
|
const typeMatch = preferredTypes.length > 0 && doc.eventType
|
|
969
1460
|
? (preferredTypes.includes(doc.eventType) ? 1 : 0)
|
|
@@ -972,12 +1463,12 @@ function createReadStore(options) {
|
|
|
972
1463
|
const sourceBaseWeight = sourceWeight(source, intent);
|
|
973
1464
|
const sourceConfigWeight = customChannelWeight(source, options.fusion);
|
|
974
1465
|
const lengthNorm = lengthNormalizeFactor(doc, options.fusion);
|
|
975
|
-
const baseWeighted = (
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1466
|
+
const baseWeighted = (readTuning.scoring.lexicalWeight * lexicalCombined +
|
|
1467
|
+
readTuning.scoring.semanticWeight * (semantic * lengthNorm) +
|
|
1468
|
+
readTuning.scoring.recencyWeight * recency +
|
|
1469
|
+
readTuning.scoring.qualityWeight * quality +
|
|
1470
|
+
readTuning.scoring.typeMatchWeight * typeMatch +
|
|
1471
|
+
readTuning.scoring.graphMatchWeight * graphMatch) * sourceBaseWeight * sourceConfigWeight;
|
|
981
1472
|
const decayFactor = computeDecayFactor(doc.id, doc.eventType, doc.timestamp, options.memoryDecay, hitStats);
|
|
982
1473
|
const weighted = baseWeighted * decayFactor;
|
|
983
1474
|
return {
|
|
@@ -1020,7 +1511,7 @@ function createReadStore(options) {
|
|
|
1020
1511
|
}
|
|
1021
1512
|
const rrfMap = new Map();
|
|
1022
1513
|
const weightedMap = new Map();
|
|
1023
|
-
const rrfK =
|
|
1514
|
+
const rrfK = readTuning.rrf.k;
|
|
1024
1515
|
for (const key of Object.keys(channels)) {
|
|
1025
1516
|
const list = channels[key];
|
|
1026
1517
|
for (let i = 0; i < list.length; i += 1) {
|
|
@@ -1040,13 +1531,18 @@ function createReadStore(options) {
|
|
|
1040
1531
|
merge_key: mergeKey,
|
|
1041
1532
|
source_memory_id: candidate.doc.sourceMemoryId || "",
|
|
1042
1533
|
source_memory_canonical_id: candidate.doc.sourceMemoryCanonicalId || "",
|
|
1043
|
-
|
|
1534
|
+
source_event_id: candidate.doc.sourceEventId || "",
|
|
1535
|
+
source_field: candidate.doc.sourceField || "",
|
|
1536
|
+
text: candidate.doc.summaryText || candidate.doc.text,
|
|
1537
|
+
source_text: candidate.doc.sourceText ? candidate.doc.sourceText.slice(0, 4000) : "",
|
|
1538
|
+
source_excerpt: candidate.doc.sourceText ? candidate.doc.sourceText.slice(0, 360) : "",
|
|
1539
|
+
source_file: candidate.doc.sourceFile || "",
|
|
1044
1540
|
source: candidate.doc.source,
|
|
1045
1541
|
layer: candidate.doc.layer || "",
|
|
1046
1542
|
event_type: candidate.doc.eventType || "",
|
|
1047
1543
|
quality_score: candidate.quality,
|
|
1048
1544
|
timestamp: candidate.doc.timestamp ? new Date(candidate.doc.timestamp).toISOString() : "",
|
|
1049
|
-
score: candidate.weighted + (rrfMap.get(mergeKey) || 0) *
|
|
1545
|
+
score: candidate.weighted + (rrfMap.get(mergeKey) || 0) * readTuning.rrf.weight,
|
|
1050
1546
|
score_breakdown: {
|
|
1051
1547
|
lexical: Number(candidate.lexical.toFixed(4)),
|
|
1052
1548
|
bm25: Number(candidate.bm25.toFixed(4)),
|
|
@@ -1056,7 +1552,7 @@ function createReadStore(options) {
|
|
|
1056
1552
|
type: Number(candidate.typeMatch.toFixed(4)),
|
|
1057
1553
|
graph: Number(candidate.graphMatch.toFixed(4)),
|
|
1058
1554
|
decay: Number(candidate.decayFactor.toFixed(4)),
|
|
1059
|
-
rrf: Number(((rrfMap.get(mergeKey) || 0) *
|
|
1555
|
+
rrf: Number(((rrfMap.get(mergeKey) || 0) * readTuning.rrf.weight).toFixed(4)),
|
|
1060
1556
|
weighted: Number(candidate.weighted.toFixed(4)),
|
|
1061
1557
|
},
|
|
1062
1558
|
reason_tags: [
|
|
@@ -1074,13 +1570,13 @@ function createReadStore(options) {
|
|
|
1074
1570
|
.slice(0, Math.max(1, Math.max(args.topK, 20)));
|
|
1075
1571
|
const lexicalRanked = preRanked
|
|
1076
1572
|
.map(doc => {
|
|
1077
|
-
const boost = withRecencyBoost(doc.score, doc.timestamp ? Date.parse(doc.timestamp) : undefined);
|
|
1573
|
+
const boost = withRecencyBoost(doc.score, doc.timestamp ? Date.parse(doc.timestamp) : undefined, readTuning.recency.buckets);
|
|
1078
1574
|
return { ...doc, score: Number(boost.toFixed(4)) };
|
|
1079
1575
|
});
|
|
1080
1576
|
const rerankerModel = options.reranker?.model || "";
|
|
1081
1577
|
const rerankerApiKey = options.reranker?.apiKey || "";
|
|
1082
1578
|
const rerankerBaseUrl = normalizeBaseUrl(options.reranker?.baseURL || options.reranker?.baseUrl);
|
|
1083
|
-
const fusionEnabled = options.fusion?.enabled !== false;
|
|
1579
|
+
const fusionEnabled = !lightweightMode && options.fusion?.enabled !== false;
|
|
1084
1580
|
const llmModel = options.llm?.model || "";
|
|
1085
1581
|
const llmApiKey = options.llm?.apiKey || "";
|
|
1086
1582
|
const llmBaseUrl = normalizeBaseUrl(options.llm?.baseURL || options.llm?.baseUrl);
|
|
@@ -1093,7 +1589,7 @@ function createReadStore(options) {
|
|
|
1093
1589
|
source: item.source,
|
|
1094
1590
|
score: item.score,
|
|
1095
1591
|
}));
|
|
1096
|
-
if (rerankerModel && rerankerApiKey && rerankerBaseUrl && lexicalRanked.length > 1 && !skipRerankerForFusion) {
|
|
1592
|
+
if (!lightweightMode && rerankerModel && rerankerApiKey && rerankerBaseUrl && lexicalRanked.length > 1 && !skipRerankerForFusion) {
|
|
1097
1593
|
try {
|
|
1098
1594
|
rerankedSimple = await requestRerank({
|
|
1099
1595
|
query,
|
|
@@ -1118,7 +1614,13 @@ function createReadStore(options) {
|
|
|
1118
1614
|
merge_key: hit?.merge_key || item.merge_key || item.id,
|
|
1119
1615
|
source_memory_id: hit?.source_memory_id || "",
|
|
1120
1616
|
source_memory_canonical_id: hit?.source_memory_canonical_id || "",
|
|
1617
|
+
source_event_id: hit?.source_event_id || "",
|
|
1618
|
+
source_field: hit?.source_field || "",
|
|
1619
|
+
fulltext_event_id: (hit?.source_event_id || hit?.source_memory_id || item.id || ""),
|
|
1121
1620
|
text: item.text,
|
|
1621
|
+
source_text: hit?.source_text || "",
|
|
1622
|
+
source_excerpt: hit?.source_excerpt || "",
|
|
1623
|
+
source_file: hit?.source_file || "",
|
|
1122
1624
|
source: item.source,
|
|
1123
1625
|
layer: hit?.layer || "",
|
|
1124
1626
|
event_type: hit?.event_type || "",
|
|
@@ -1131,7 +1633,10 @@ function createReadStore(options) {
|
|
|
1131
1633
|
merge_key: hit?.merge_key || item.merge_key || item.id,
|
|
1132
1634
|
source_memory_id: hit?.source_memory_id || "",
|
|
1133
1635
|
source_memory_canonical_id: hit?.source_memory_canonical_id || "",
|
|
1636
|
+
source_event_id: hit?.source_event_id || "",
|
|
1637
|
+
source_field: hit?.source_field || "",
|
|
1134
1638
|
channel: item.source,
|
|
1639
|
+
source_file: hit?.source_file || "",
|
|
1135
1640
|
layer: hit?.layer || "",
|
|
1136
1641
|
score_breakdown: hit?.score_breakdown || {},
|
|
1137
1642
|
reason_tags: Array.isArray(hit?.reason_tags) ? hit?.reason_tags : [],
|
|
@@ -1152,7 +1657,13 @@ function createReadStore(options) {
|
|
|
1152
1657
|
merge_key: item.merge_key,
|
|
1153
1658
|
source_memory_id: item.source_memory_id,
|
|
1154
1659
|
source_memory_canonical_id: item.source_memory_canonical_id,
|
|
1660
|
+
source_event_id: item.source_event_id || "",
|
|
1661
|
+
source_field: item.source_field || "",
|
|
1662
|
+
fulltext_event_id: item.source_event_id || item.source_memory_id || item.id,
|
|
1155
1663
|
text: item.text,
|
|
1664
|
+
source_text: item.source_text || "",
|
|
1665
|
+
source_excerpt: item.source_excerpt || "",
|
|
1666
|
+
source_file: item.source_file || "",
|
|
1156
1667
|
source: item.source,
|
|
1157
1668
|
layer: item.layer,
|
|
1158
1669
|
event_type: item.event_type,
|
|
@@ -1165,7 +1676,10 @@ function createReadStore(options) {
|
|
|
1165
1676
|
merge_key: item.merge_key,
|
|
1166
1677
|
source_memory_id: item.source_memory_id,
|
|
1167
1678
|
source_memory_canonical_id: item.source_memory_canonical_id,
|
|
1679
|
+
source_event_id: item.source_event_id || "",
|
|
1680
|
+
source_field: item.source_field || "",
|
|
1168
1681
|
channel: item.source,
|
|
1682
|
+
source_file: item.source_file || "",
|
|
1169
1683
|
layer: item.layer,
|
|
1170
1684
|
score_breakdown: item.score_breakdown || {},
|
|
1171
1685
|
reason_tags: Array.isArray(item.reason_tags) ? item.reason_tags : [],
|
|
@@ -1185,7 +1699,13 @@ function createReadStore(options) {
|
|
|
1185
1699
|
merge_key: item.merge_key,
|
|
1186
1700
|
source_memory_id: item.source_memory_id,
|
|
1187
1701
|
source_memory_canonical_id: item.source_memory_canonical_id,
|
|
1702
|
+
source_event_id: item.source_event_id || "",
|
|
1703
|
+
source_field: item.source_field || "",
|
|
1704
|
+
fulltext_event_id: item.source_event_id || item.source_memory_id || item.id,
|
|
1188
1705
|
text: item.text,
|
|
1706
|
+
source_text: item.source_text || "",
|
|
1707
|
+
source_excerpt: item.source_excerpt || "",
|
|
1708
|
+
source_file: item.source_file || "",
|
|
1189
1709
|
source: item.source,
|
|
1190
1710
|
layer: item.layer,
|
|
1191
1711
|
event_type: item.event_type,
|
|
@@ -1198,7 +1718,10 @@ function createReadStore(options) {
|
|
|
1198
1718
|
merge_key: item.merge_key,
|
|
1199
1719
|
source_memory_id: item.source_memory_id,
|
|
1200
1720
|
source_memory_canonical_id: item.source_memory_canonical_id,
|
|
1721
|
+
source_event_id: item.source_event_id || "",
|
|
1722
|
+
source_field: item.source_field || "",
|
|
1201
1723
|
channel: item.source,
|
|
1724
|
+
source_file: item.source_file || "",
|
|
1202
1725
|
layer: item.layer,
|
|
1203
1726
|
score_breakdown: item.score_breakdown || {},
|
|
1204
1727
|
reason_tags: Array.isArray(item.reason_tags) ? item.reason_tags : [],
|
|
@@ -1215,6 +1738,13 @@ function createReadStore(options) {
|
|
|
1215
1738
|
candidates: ranked.slice(0, maxCandidates).map(item => ({
|
|
1216
1739
|
id: item.id,
|
|
1217
1740
|
text: item.text,
|
|
1741
|
+
source_excerpt: typeof item.source_excerpt === "string" ? item.source_excerpt : "",
|
|
1742
|
+
source_file: typeof item.source_file === "string" ? item.source_file : "",
|
|
1743
|
+
source_memory_id: typeof item.source_memory_id === "string" ? item.source_memory_id : "",
|
|
1744
|
+
source_memory_canonical_id: typeof item.source_memory_canonical_id === "string" ? item.source_memory_canonical_id : "",
|
|
1745
|
+
source_layer: typeof item.layer === "string" ? item.layer : "",
|
|
1746
|
+
source_event_id: typeof item.source_event_id === "string" ? item.source_event_id : "",
|
|
1747
|
+
source_field: item.source_field === "summary" || item.source_field === "evidence" ? item.source_field : "",
|
|
1218
1748
|
source: item.source,
|
|
1219
1749
|
event_type: item.event_type,
|
|
1220
1750
|
quality_score: item.quality_score,
|
|
@@ -1229,6 +1759,20 @@ function createReadStore(options) {
|
|
|
1229
1759
|
},
|
|
1230
1760
|
});
|
|
1231
1761
|
if (fusion && fusion.canonical_answer) {
|
|
1762
|
+
if (!Array.isArray(fusion.evidence_ids) || fusion.evidence_ids.length === 0) {
|
|
1763
|
+
throw new Error("fusion_missing_whitelisted_evidence");
|
|
1764
|
+
}
|
|
1765
|
+
const fulltextFetchHints = (Array.isArray(fusion.need_fulltext_event_ids) ? fusion.need_fulltext_event_ids : [])
|
|
1766
|
+
.map(eventId => {
|
|
1767
|
+
const linked = ranked.find(item => item.source_memory_id === eventId ||
|
|
1768
|
+
item.source_memory_canonical_id === eventId ||
|
|
1769
|
+
item.id === eventId);
|
|
1770
|
+
return {
|
|
1771
|
+
event_id: eventId,
|
|
1772
|
+
source_file: linked?.source_file || "",
|
|
1773
|
+
source_excerpt: linked?.source_excerpt || "",
|
|
1774
|
+
};
|
|
1775
|
+
});
|
|
1232
1776
|
const fusedItem = {
|
|
1233
1777
|
id: `fusion_${Date.now().toString(36)}`,
|
|
1234
1778
|
text: fusion.canonical_answer,
|
|
@@ -1254,6 +1798,8 @@ function createReadStore(options) {
|
|
|
1254
1798
|
fused_action_items: fusion.action_items || [],
|
|
1255
1799
|
fused_conflicts: fusion.conflicts,
|
|
1256
1800
|
fused_evidence_ids: fusion.evidence_ids,
|
|
1801
|
+
fused_need_fulltext_event_ids: fusion.need_fulltext_event_ids || [],
|
|
1802
|
+
fulltext_fetch_hints: fulltextFetchHints,
|
|
1257
1803
|
};
|
|
1258
1804
|
const authoritative = options.fusion?.authoritative !== false;
|
|
1259
1805
|
if (authoritative) {
|
|
@@ -1280,6 +1826,7 @@ function createReadStore(options) {
|
|
|
1280
1826
|
const limit = Math.max(1, args.limit);
|
|
1281
1827
|
const docs = loadAllDocuments();
|
|
1282
1828
|
const coreRules = docs.find(doc => doc.source === "CORTEX_RULES.md");
|
|
1829
|
+
const ruleBudget = Math.max(1, Math.min(6, Math.floor(limit / 3)));
|
|
1283
1830
|
const archiveDocs = docs
|
|
1284
1831
|
.filter(doc => doc.source === "sessions_archive")
|
|
1285
1832
|
.sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0))
|
|
@@ -1290,7 +1837,14 @@ function createReadStore(options) {
|
|
|
1290
1837
|
.slice(0, 2);
|
|
1291
1838
|
const result = [];
|
|
1292
1839
|
if (coreRules) {
|
|
1293
|
-
|
|
1840
|
+
const selectedRules = extractPrioritizedRuleLines(coreRules.text, ruleBudget);
|
|
1841
|
+
if (selectedRules.length > 0) {
|
|
1842
|
+
result.push({
|
|
1843
|
+
id: `${coreRules.id}.hot`,
|
|
1844
|
+
text: `# Hot Rules\n${selectedRules.map((line, index) => `${index + 1}. ${line}`).join("\n")}`,
|
|
1845
|
+
source: coreRules.source,
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1294
1848
|
}
|
|
1295
1849
|
for (const doc of [...issueFixPairs, ...archiveDocs]) {
|
|
1296
1850
|
result.push({ id: doc.id, text: doc.text, source: doc.source });
|
|
@@ -1312,9 +1866,14 @@ function createReadStore(options) {
|
|
|
1312
1866
|
.sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
|
|
1313
1867
|
const latest = docs[0];
|
|
1314
1868
|
if (latest && latest.text.trim()) {
|
|
1315
|
-
const
|
|
1869
|
+
const autoQuery = latest.text.slice(0, Math.max(20, readTuning.autoContext.queryMaxChars));
|
|
1870
|
+
const light = await searchMemory({
|
|
1871
|
+
query: autoQuery,
|
|
1872
|
+
topK: 3,
|
|
1873
|
+
mode: readTuning.autoContext.lightweightSearch ? "lightweight" : "default",
|
|
1874
|
+
});
|
|
1316
1875
|
result.auto_search = {
|
|
1317
|
-
query:
|
|
1876
|
+
query: autoQuery,
|
|
1318
1877
|
results: light.results,
|
|
1319
1878
|
age_seconds: 0,
|
|
1320
1879
|
};
|