openclaw-cortex-memory 0.1.0-Alpha.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +347 -290
- package/SIGNATURE.md +7 -0
- package/SKILL.md +96 -345
- package/dist/index.d.ts +69 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1130 -1330
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +377 -18
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -1
- package/dist/src/dedup/three_stage_deduplicator.js +13 -3
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -1
- 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 +149 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +863 -203
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +20 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +87 -15
- package/dist/src/graph/ontology.d.ts.map +1 -1
- package/dist/src/graph/ontology.js +999 -12
- package/dist/src/graph/ontology.js.map +1 -1
- package/dist/src/net/http_post.d.ts +17 -0
- package/dist/src/net/http_post.d.ts.map +1 -0
- package/dist/src/net/http_post.js +56 -0
- package/dist/src/net/http_post.js.map +1 -0
- package/dist/src/quality/llm_output_validator.d.ts +65 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +635 -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 +296 -26
- 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 -42
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +21 -218
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +28 -7
- package/dist/src/store/archive_store.d.ts.map +1 -1
- package/dist/src/store/archive_store.js +367 -130
- package/dist/src/store/archive_store.js.map +1 -1
- package/dist/src/store/graph_memory_store.d.ts +115 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +1061 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +75 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1837 -312
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +2 -0
- package/dist/src/store/vector_store.d.ts.map +1 -1
- package/dist/src/store/vector_store.js +19 -3
- package/dist/src/store/vector_store.js.map +1 -1
- package/dist/src/store/write_store.d.ts +11 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +242 -42
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +72 -1
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +2246 -126
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/wiki/wiki_linter.d.ts +26 -0
- package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
- package/dist/src/wiki/wiki_linter.js +339 -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 +39 -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 +35 -0
- package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
- package/dist/src/wiki/wiki_projector.js +1151 -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 +377 -18
- package/package.json +52 -6
- package/schema/graph.schema.yaml +330 -0
- package/scripts/cli.js +67 -13
- package/scripts/repair-memory.js +321 -0
- package/skills/cortex-memory/SKILL.md +83 -0
- package/skills/cortex-memory/references/agent-manual.md +127 -0
- package/skills/cortex-memory/references/configuration.md +109 -0
- package/skills/cortex-memory/references/publish-checklist.md +45 -0
- package/skills/cortex-memory/references/system-prompt-template.md +27 -0
- package/skills/cortex-memory/references/tools.md +191 -0
|
@@ -37,10 +37,14 @@ exports.createTsEngine = createTsEngine;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const ontology_1 = require("../graph/ontology");
|
|
40
|
+
const http_post_1 = require("../net/http_post");
|
|
41
|
+
const llm_output_validator_1 = require("../quality/llm_output_validator");
|
|
42
|
+
const wiki_projector_1 = require("../wiki/wiki_projector");
|
|
43
|
+
const wiki_linter_1 = require("../wiki/wiki_linter");
|
|
40
44
|
const PROMPT_VERSIONS = {
|
|
41
|
-
write_gate: "write-gate.v1.
|
|
42
|
-
session_end_write: "session-end-write.v1.
|
|
43
|
-
read_fusion: "read-fusion.v1.
|
|
45
|
+
write_gate: "write-gate.v1.7.9",
|
|
46
|
+
session_end_write: "session-end-write.v1.2.0",
|
|
47
|
+
read_fusion: "read-fusion.v1.2.0",
|
|
44
48
|
};
|
|
45
49
|
function createTsEngine(deps) {
|
|
46
50
|
const graphSchema = (0, ontology_1.loadGraphSchema)(deps.projectRoot);
|
|
@@ -69,6 +73,27 @@ function createTsEngine(deps) {
|
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
}
|
|
76
|
+
function getRecentSessionMessages(sessionId, limit) {
|
|
77
|
+
const messages = sessionMessageBuffer.get(sessionId) || [];
|
|
78
|
+
return messages.slice(Math.max(0, messages.length - Math.max(1, limit)));
|
|
79
|
+
}
|
|
80
|
+
function isHistoricalMemoryQuery(text) {
|
|
81
|
+
return /(上次|之前|以前|你记得|记得|历史|上个月|去年|上个星期|昨天|前天|查一下|回忆|记忆|偏好|项目上下文|既有决策|决策|决定|修复|方案|last time|previous|previously|before|remember|history|prior|preference|decision|fix|what did we)/i.test(text);
|
|
82
|
+
}
|
|
83
|
+
function buildAutoSearchQuery(sessionId, latestUserText, historical) {
|
|
84
|
+
if (!historical) {
|
|
85
|
+
return latestUserText.trim();
|
|
86
|
+
}
|
|
87
|
+
const recent = getRecentSessionMessages(sessionId, 8)
|
|
88
|
+
.map(message => {
|
|
89
|
+
const role = typeof message.role === "string" && message.role.trim() ? message.role.trim() : "message";
|
|
90
|
+
const content = typeof message.content === "string" ? message.content.trim().replace(/\s+/g, " ") : "";
|
|
91
|
+
return content ? `${role}: ${content}` : "";
|
|
92
|
+
})
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.join("\n");
|
|
95
|
+
return (recent || latestUserText).slice(-240).trim();
|
|
96
|
+
}
|
|
72
97
|
function asRecord(value) {
|
|
73
98
|
if (typeof value === "object" && value !== null) {
|
|
74
99
|
return value;
|
|
@@ -153,44 +178,25 @@ function createTsEngine(deps) {
|
|
|
153
178
|
}
|
|
154
179
|
function buildVectorSourceText(record, layer) {
|
|
155
180
|
if (layer === "active") {
|
|
156
|
-
const
|
|
157
|
-
? record.
|
|
158
|
-
:
|
|
159
|
-
|
|
181
|
+
const sourceText = typeof record.source_text === "string" && record.source_text.trim()
|
|
182
|
+
? record.source_text.trim()
|
|
183
|
+
: "";
|
|
184
|
+
if (sourceText) {
|
|
185
|
+
return sourceText;
|
|
186
|
+
}
|
|
187
|
+
const summary = typeof record.summary === "string" ? record.summary.trim() : "";
|
|
188
|
+
if (summary) {
|
|
189
|
+
return summary;
|
|
190
|
+
}
|
|
191
|
+
const text = typeof record.text === "string" ? record.text.trim() : "";
|
|
192
|
+
return text;
|
|
193
|
+
}
|
|
194
|
+
const sourceText = typeof record.source_text === "string" ? record.source_text.trim() : "";
|
|
195
|
+
if (sourceText) {
|
|
196
|
+
return sourceText;
|
|
160
197
|
}
|
|
161
198
|
const summary = typeof record.summary === "string" ? record.summary.trim() : "";
|
|
162
|
-
|
|
163
|
-
const outcome = typeof record.outcome === "string" ? record.outcome.trim() : "";
|
|
164
|
-
const sourceFile = typeof record.source_file === "string" ? record.source_file.trim() : "";
|
|
165
|
-
const actor = typeof record.actor === "string" ? record.actor.trim() : "";
|
|
166
|
-
const entities = Array.isArray(record.entities)
|
|
167
|
-
? record.entities.filter(v => typeof v === "string").map(v => String(v).trim()).filter(Boolean)
|
|
168
|
-
: [];
|
|
169
|
-
const relations = Array.isArray(record.relations)
|
|
170
|
-
? record.relations
|
|
171
|
-
.map(v => {
|
|
172
|
-
if (!v || typeof v !== "object")
|
|
173
|
-
return "";
|
|
174
|
-
const relation = v;
|
|
175
|
-
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
176
|
-
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
177
|
-
const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
|
|
178
|
-
if (!source || !target)
|
|
179
|
-
return "";
|
|
180
|
-
return `${source} -[${type}]-> ${target}`;
|
|
181
|
-
})
|
|
182
|
-
.filter(Boolean)
|
|
183
|
-
: [];
|
|
184
|
-
const lines = [
|
|
185
|
-
`event_type: ${eventType}`,
|
|
186
|
-
`summary: ${summary}`,
|
|
187
|
-
`outcome: ${outcome}`,
|
|
188
|
-
`entities: ${entities.join(", ")}`,
|
|
189
|
-
`source_file: ${sourceFile}`,
|
|
190
|
-
`actor: ${actor}`,
|
|
191
|
-
relations.length > 0 ? `relations: ${relations.join(" ; ")}` : "",
|
|
192
|
-
].filter(Boolean);
|
|
193
|
-
return lines.join("\n").trim();
|
|
199
|
+
return summary;
|
|
194
200
|
}
|
|
195
201
|
function splitTextChunks(text, chunkSize, chunkOverlap) {
|
|
196
202
|
const normalizedSize = Number.isFinite(chunkSize) && chunkSize >= 200 ? Math.floor(chunkSize) : 600;
|
|
@@ -243,6 +249,32 @@ function createTsEngine(deps) {
|
|
|
243
249
|
}
|
|
244
250
|
return output;
|
|
245
251
|
}
|
|
252
|
+
function pickEvidenceChunks(chunks, maxCount) {
|
|
253
|
+
if (!chunks.length || maxCount <= 0)
|
|
254
|
+
return [];
|
|
255
|
+
if (chunks.length <= maxCount)
|
|
256
|
+
return chunks;
|
|
257
|
+
const picked = new Map();
|
|
258
|
+
picked.set(chunks[0].index, chunks[0]);
|
|
259
|
+
if (maxCount >= 2) {
|
|
260
|
+
const mid = chunks[Math.floor(chunks.length / 2)];
|
|
261
|
+
picked.set(mid.index, mid);
|
|
262
|
+
}
|
|
263
|
+
if (maxCount >= 3) {
|
|
264
|
+
const last = chunks[chunks.length - 1];
|
|
265
|
+
picked.set(last.index, last);
|
|
266
|
+
}
|
|
267
|
+
if (picked.size < maxCount) {
|
|
268
|
+
for (const chunk of chunks) {
|
|
269
|
+
if (!picked.has(chunk.index)) {
|
|
270
|
+
picked.set(chunk.index, chunk);
|
|
271
|
+
}
|
|
272
|
+
if (picked.size >= maxCount)
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return [...picked.values()].sort((a, b) => a.index - b.index).slice(0, maxCount);
|
|
277
|
+
}
|
|
246
278
|
function upsertJsonFile(filePath, patch) {
|
|
247
279
|
const current = parseJsonFile(filePath) || {};
|
|
248
280
|
const next = { ...current, ...patch };
|
|
@@ -253,9 +285,10 @@ function createTsEngine(deps) {
|
|
|
253
285
|
fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf-8");
|
|
254
286
|
}
|
|
255
287
|
async function probeModelConnection(args) {
|
|
288
|
+
const defaultTimeoutMs = args.kind === "llm" ? 30000 : 15000;
|
|
256
289
|
const timeoutMs = typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs) && args.timeoutMs >= 1000
|
|
257
290
|
? Math.floor(args.timeoutMs)
|
|
258
|
-
:
|
|
291
|
+
: defaultTimeoutMs;
|
|
259
292
|
if (!args.model || !args.apiKey || !args.baseUrl) {
|
|
260
293
|
return {
|
|
261
294
|
configured: false,
|
|
@@ -265,73 +298,71 @@ function createTsEngine(deps) {
|
|
|
265
298
|
error: "not_configured",
|
|
266
299
|
};
|
|
267
300
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
signal: controller.signal,
|
|
301
|
+
let endpoint = args.baseUrl;
|
|
302
|
+
let payload = {};
|
|
303
|
+
if (args.kind === "embedding") {
|
|
304
|
+
endpoint = args.baseUrl.endsWith("/embeddings") ? args.baseUrl : `${args.baseUrl}/embeddings`;
|
|
305
|
+
payload = {
|
|
306
|
+
model: args.model,
|
|
307
|
+
input: "diagnostics connectivity probe",
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
else if (args.kind === "llm") {
|
|
311
|
+
endpoint = args.baseUrl.endsWith("/chat/completions") ? args.baseUrl : `${args.baseUrl}/chat/completions`;
|
|
312
|
+
payload = {
|
|
313
|
+
model: args.model,
|
|
314
|
+
messages: [{ role: "user", content: "ping" }],
|
|
315
|
+
max_tokens: 4,
|
|
316
|
+
temperature: 0,
|
|
317
|
+
stream: false,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
endpoint = args.baseUrl.endsWith("/rerank") ? args.baseUrl : `${args.baseUrl}/rerank`;
|
|
322
|
+
payload = {
|
|
323
|
+
model: args.model,
|
|
324
|
+
query: "diagnostics",
|
|
325
|
+
documents: ["diagnostics connectivity probe"],
|
|
326
|
+
top_n: 1,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
let lastError = "unknown_error";
|
|
330
|
+
const maxAttempts = args.kind === "llm" ? 3 : 1;
|
|
331
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
332
|
+
const response = await (0, http_post_1.postJsonWithTimeout)({
|
|
333
|
+
endpoint,
|
|
334
|
+
apiKey: args.apiKey,
|
|
335
|
+
body: payload,
|
|
336
|
+
timeoutMs,
|
|
337
|
+
headers: { accept: "application/json" },
|
|
306
338
|
});
|
|
307
|
-
|
|
308
|
-
if (!response.ok) {
|
|
339
|
+
if (response.ok) {
|
|
309
340
|
return {
|
|
310
341
|
configured: true,
|
|
311
|
-
connected:
|
|
342
|
+
connected: true,
|
|
312
343
|
model: args.model,
|
|
313
344
|
base_url: args.baseUrl,
|
|
314
|
-
error:
|
|
345
|
+
error: "",
|
|
315
346
|
};
|
|
316
347
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
return {
|
|
328
|
-
configured: true,
|
|
329
|
-
connected: false,
|
|
330
|
-
model: args.model,
|
|
331
|
-
base_url: args.baseUrl,
|
|
332
|
-
error: error instanceof Error ? error.message : String(error),
|
|
333
|
-
};
|
|
348
|
+
if (response.aborted) {
|
|
349
|
+
lastError = `timeout_${timeoutMs}ms`;
|
|
350
|
+
}
|
|
351
|
+
else if (response.status > 0) {
|
|
352
|
+
const details = (response.text || "").trim().slice(0, 180);
|
|
353
|
+
lastError = details ? `http_${response.status}:${details}` : `http_${response.status}`;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
lastError = response.error || "network_error";
|
|
357
|
+
}
|
|
334
358
|
}
|
|
359
|
+
return {
|
|
360
|
+
configured: true,
|
|
361
|
+
connected: false,
|
|
362
|
+
model: args.model,
|
|
363
|
+
base_url: args.baseUrl,
|
|
364
|
+
error: lastError,
|
|
365
|
+
};
|
|
335
366
|
}
|
|
336
367
|
async function requestEmbedding(args) {
|
|
337
368
|
const endpoint = args.baseUrl.endsWith("/embeddings") ? args.baseUrl : `${args.baseUrl}/embeddings`;
|
|
@@ -350,24 +381,18 @@ function createTsEngine(deps) {
|
|
|
350
381
|
: 4;
|
|
351
382
|
let lastError = null;
|
|
352
383
|
for (let attempt = 0; attempt < maxRetries; attempt += 1) {
|
|
353
|
-
const
|
|
354
|
-
|
|
384
|
+
const response = await (0, http_post_1.postJsonWithTimeout)({
|
|
385
|
+
endpoint,
|
|
386
|
+
apiKey: args.apiKey,
|
|
387
|
+
body,
|
|
388
|
+
timeoutMs,
|
|
389
|
+
});
|
|
390
|
+
if (!response.ok) {
|
|
391
|
+
lastError = new Error(response.status > 0 ? `embedding_http_${response.status}` : (response.error || "embedding_network_error"));
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
355
394
|
try {
|
|
356
|
-
const
|
|
357
|
-
method: "POST",
|
|
358
|
-
headers: {
|
|
359
|
-
"content-type": "application/json",
|
|
360
|
-
authorization: `Bearer ${args.apiKey}`,
|
|
361
|
-
},
|
|
362
|
-
body: JSON.stringify(body),
|
|
363
|
-
signal: controller.signal,
|
|
364
|
-
});
|
|
365
|
-
clearTimeout(timeoutId);
|
|
366
|
-
if (!response.ok) {
|
|
367
|
-
lastError = new Error(`embedding_http_${response.status}`);
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
const json = await response.json();
|
|
395
|
+
const json = (response.json || {});
|
|
371
396
|
const embedding = json?.data?.[0]?.embedding;
|
|
372
397
|
if (Array.isArray(embedding) && embedding.length > 0) {
|
|
373
398
|
return embedding.filter(item => Number.isFinite(item));
|
|
@@ -375,7 +400,6 @@ function createTsEngine(deps) {
|
|
|
375
400
|
lastError = new Error("embedding_empty");
|
|
376
401
|
}
|
|
377
402
|
catch (error) {
|
|
378
|
-
clearTimeout(timeoutId);
|
|
379
403
|
lastError = error;
|
|
380
404
|
}
|
|
381
405
|
if (attempt < maxRetries - 1) {
|
|
@@ -417,6 +441,22 @@ function createTsEngine(deps) {
|
|
|
417
441
|
return "";
|
|
418
442
|
}).filter(Boolean)
|
|
419
443
|
: [];
|
|
444
|
+
const entityTypesFromEntities = {};
|
|
445
|
+
if (Array.isArray(entityInput)) {
|
|
446
|
+
for (const item of entityInput) {
|
|
447
|
+
if (!item || typeof item !== "object") {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const entityObj = item;
|
|
451
|
+
const entityName = typeof entityObj.name === "string" && entityObj.name.trim()
|
|
452
|
+
? entityObj.name.trim()
|
|
453
|
+
: (typeof entityObj.id === "string" ? entityObj.id.trim() : "");
|
|
454
|
+
const entityType = typeof entityObj.type === "string" ? entityObj.type.trim() : "";
|
|
455
|
+
if (entityName && entityType) {
|
|
456
|
+
entityTypesFromEntities[entityName] = entityType;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
420
460
|
const relationInput = Array.isArray(rawArgs.relations)
|
|
421
461
|
? rawArgs.relations
|
|
422
462
|
: Array.isArray(rawArgs.input?.relations)
|
|
@@ -445,6 +485,8 @@ function createTsEngine(deps) {
|
|
|
445
485
|
source: relation.source.trim(),
|
|
446
486
|
target: relation.target.trim(),
|
|
447
487
|
type: (0, ontology_1.normalizeRelationType)(relation.type || "related_to", graphSchema),
|
|
488
|
+
evidence_span: typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : undefined,
|
|
489
|
+
confidence: typeof relation.confidence === "number" ? Math.max(0, Math.min(1, relation.confidence)) : undefined,
|
|
448
490
|
};
|
|
449
491
|
})
|
|
450
492
|
.filter((item) => Boolean(item))
|
|
@@ -456,24 +498,98 @@ function createTsEngine(deps) {
|
|
|
456
498
|
: typeof rawArgs.event?.outcome === "string"
|
|
457
499
|
? String(rawArgs.event.outcome)
|
|
458
500
|
: "";
|
|
501
|
+
const causeValue = typeof rawArgs.cause === "string"
|
|
502
|
+
? rawArgs.cause
|
|
503
|
+
: typeof rawArgs.input?.cause === "string"
|
|
504
|
+
? String(rawArgs.input.cause)
|
|
505
|
+
: typeof rawArgs.event?.cause === "string"
|
|
506
|
+
? String(rawArgs.event.cause)
|
|
507
|
+
: "";
|
|
508
|
+
const processValue = typeof rawArgs.process === "string"
|
|
509
|
+
? rawArgs.process
|
|
510
|
+
: typeof rawArgs.input?.process === "string"
|
|
511
|
+
? String(rawArgs.input.process)
|
|
512
|
+
: typeof rawArgs.event?.process === "string"
|
|
513
|
+
? String(rawArgs.event.process)
|
|
514
|
+
: "";
|
|
515
|
+
const resultValue = typeof rawArgs.result === "string"
|
|
516
|
+
? rawArgs.result
|
|
517
|
+
: typeof rawArgs.input?.result === "string"
|
|
518
|
+
? String(rawArgs.input.result)
|
|
519
|
+
: typeof rawArgs.event?.result === "string"
|
|
520
|
+
? String(rawArgs.event.result)
|
|
521
|
+
: "";
|
|
522
|
+
const entityTypesInput = typeof rawArgs.entity_types === "object" && rawArgs.entity_types !== null
|
|
523
|
+
? rawArgs.entity_types
|
|
524
|
+
: typeof rawArgs.input?.entity_types === "object"
|
|
525
|
+
? rawArgs.input.entity_types
|
|
526
|
+
: typeof rawArgs.event?.entity_types === "object"
|
|
527
|
+
? rawArgs.event.entity_types
|
|
528
|
+
: {};
|
|
529
|
+
const entityTypes = {};
|
|
530
|
+
for (const [key, value] of Object.entries(entityTypesInput)) {
|
|
531
|
+
if (typeof value === "string") {
|
|
532
|
+
entityTypes[key.trim()] = value.trim();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
for (const [key, value] of Object.entries(entityTypesFromEntities)) {
|
|
536
|
+
if (!entityTypes[key] && value) {
|
|
537
|
+
entityTypes[key] = value;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
459
540
|
const result = await deps.archiveStore.storeEvents([
|
|
460
541
|
{
|
|
461
542
|
event_type: "manual_event",
|
|
462
543
|
summary: normalizedSummary,
|
|
544
|
+
cause: causeValue.trim() || normalizedSummary,
|
|
545
|
+
process: processValue.trim() || normalizedSummary,
|
|
546
|
+
result: resultValue.trim() || outcomeValue.trim() || normalizedSummary,
|
|
463
547
|
entities,
|
|
464
548
|
relations,
|
|
549
|
+
entity_types: entityTypes,
|
|
465
550
|
outcome: outcomeValue,
|
|
466
551
|
session_id: "manual",
|
|
467
552
|
source_file: "ts_store_event",
|
|
468
553
|
confidence: 1,
|
|
469
|
-
source_event_id:
|
|
554
|
+
source_event_id: `manual:${Date.now().toString(36)}`,
|
|
470
555
|
actor: "manual_tool",
|
|
471
556
|
},
|
|
472
557
|
]);
|
|
473
558
|
if (result.stored.length === 0) {
|
|
474
559
|
return { success: false, error: result.skipped[0]?.reason || "store_event_skipped" };
|
|
475
560
|
}
|
|
476
|
-
|
|
561
|
+
const storedId = result.stored[0].id;
|
|
562
|
+
if (deps.graphMemoryStore && entities.length > 0 && Object.keys(entityTypes).length > 0 && relations.length > 0) {
|
|
563
|
+
const graphSummary = normalizedSummary.toLowerCase().includes("entities:")
|
|
564
|
+
? normalizedSummary
|
|
565
|
+
: `${normalizedSummary} Entities: ${entities.join(", ")}.`;
|
|
566
|
+
const graphResult = await deps.graphMemoryStore.append({
|
|
567
|
+
sourceEventId: storedId,
|
|
568
|
+
sourceLayer: "archive_event",
|
|
569
|
+
archiveEventId: storedId,
|
|
570
|
+
sessionId: "manual",
|
|
571
|
+
sourceFile: "ts_store_event",
|
|
572
|
+
summary: graphSummary,
|
|
573
|
+
source_text_nav: {
|
|
574
|
+
layer: "archive_event",
|
|
575
|
+
session_id: "manual",
|
|
576
|
+
source_file: "ts_store_event",
|
|
577
|
+
source_memory_id: storedId,
|
|
578
|
+
source_event_id: storedId,
|
|
579
|
+
},
|
|
580
|
+
eventType: "manual_event",
|
|
581
|
+
entities,
|
|
582
|
+
entity_types: entityTypes,
|
|
583
|
+
relations,
|
|
584
|
+
gateSource: "manual",
|
|
585
|
+
confidence: 1,
|
|
586
|
+
sourceText: normalizedSummary,
|
|
587
|
+
});
|
|
588
|
+
if (!graphResult.success) {
|
|
589
|
+
deps.logger.info(`store_event graph_skip_reason=${graphResult.reason} source_event_id=${storedId}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return { success: true, data: { event_id: storedId } };
|
|
477
593
|
}
|
|
478
594
|
catch (error) {
|
|
479
595
|
return { success: false, error: String(error) };
|
|
@@ -492,32 +608,20 @@ function createTsEngine(deps) {
|
|
|
492
608
|
: "both";
|
|
493
609
|
const pathTo = typeof args.path_to === "string" && args.path_to.trim() ? args.path_to.trim() : "";
|
|
494
610
|
const maxDepth = Math.max(2, Math.min(4, typeof args.max_depth === "number" ? Math.floor(args.max_depth) : 3));
|
|
495
|
-
const
|
|
496
|
-
const
|
|
611
|
+
const graphMemoryPath = path.join(deps.memoryRoot, "graph", "memory.jsonl");
|
|
612
|
+
const projectionIndexPath = path.join(deps.memoryRoot, "wiki", ".projection_index.json");
|
|
497
613
|
const nodes = new Map();
|
|
498
614
|
const edges = [];
|
|
499
|
-
const adjacency = new Map();
|
|
500
615
|
const pathAdjacency = new Map();
|
|
501
616
|
const relationTypeDistribution = new Map();
|
|
502
617
|
const edgeKeySet = new Set();
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
edgeKeySet.add(key);
|
|
509
|
-
edges.push({ source, target, type });
|
|
510
|
-
relationTypeDistribution.set(type, (relationTypeDistribution.get(type) || 0) + 1);
|
|
511
|
-
if (!adjacency.has(source)) {
|
|
512
|
-
adjacency.set(source, []);
|
|
513
|
-
}
|
|
514
|
-
adjacency.get(source)?.push({ next: target, edge: { source, target, type } });
|
|
515
|
-
if (!adjacency.has(target)) {
|
|
516
|
-
adjacency.set(target, []);
|
|
517
|
-
}
|
|
518
|
-
adjacency.get(target)?.push({ next: source, edge: { source, target, type } });
|
|
618
|
+
const allEdges = [];
|
|
619
|
+
function relationKeyOf(source, type, target) {
|
|
620
|
+
return `${source.trim().toLowerCase()}|${type.trim().toLowerCase()}|${target.trim().toLowerCase()}`;
|
|
519
621
|
}
|
|
520
|
-
function pushPathEdge(
|
|
622
|
+
function pushPathEdge(edge) {
|
|
623
|
+
const source = edge.source;
|
|
624
|
+
const target = edge.target;
|
|
521
625
|
if (!pathAdjacency.has(source)) {
|
|
522
626
|
pathAdjacency.set(source, []);
|
|
523
627
|
}
|
|
@@ -525,71 +629,173 @@ function createTsEngine(deps) {
|
|
|
525
629
|
pathAdjacency.set(target, []);
|
|
526
630
|
}
|
|
527
631
|
if (direction === "incoming") {
|
|
528
|
-
pathAdjacency.get(target)?.push({ next: source, edge
|
|
632
|
+
pathAdjacency.get(target)?.push({ next: source, edge });
|
|
529
633
|
}
|
|
530
634
|
else if (direction === "outgoing") {
|
|
531
|
-
pathAdjacency.get(source)?.push({ next: target, edge
|
|
635
|
+
pathAdjacency.get(source)?.push({ next: target, edge });
|
|
532
636
|
}
|
|
533
637
|
else {
|
|
534
|
-
pathAdjacency.get(source)?.push({ next: target, edge
|
|
535
|
-
pathAdjacency.get(target)?.push({ next: source, edge
|
|
638
|
+
pathAdjacency.get(source)?.push({ next: target, edge });
|
|
639
|
+
pathAdjacency.get(target)?.push({ next: source, edge });
|
|
536
640
|
}
|
|
537
641
|
}
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if (
|
|
642
|
+
function normalizeStatus(value) {
|
|
643
|
+
const token = (value || "").trim().toLowerCase();
|
|
644
|
+
if (token === "pending" || token === "pending_conflict")
|
|
645
|
+
return "pending_conflict";
|
|
646
|
+
if (token === "superseded")
|
|
647
|
+
return "superseded";
|
|
648
|
+
if (token === "rejected")
|
|
649
|
+
return "rejected";
|
|
650
|
+
return "active";
|
|
651
|
+
}
|
|
652
|
+
const viewData = deps.graphMemoryStore?.exportGraphView();
|
|
653
|
+
if (viewData && Array.isArray(viewData.edges)) {
|
|
654
|
+
for (const edge of viewData.edges) {
|
|
655
|
+
const source = typeof edge.source === "string" ? edge.source.trim() : "";
|
|
656
|
+
const target = typeof edge.target === "string" ? edge.target.trim() : "";
|
|
657
|
+
const type = (0, ontology_1.normalizeRelationType)(typeof edge.type === "string" ? edge.type : "related_to", graphSchema);
|
|
658
|
+
if (!source || !target)
|
|
555
659
|
continue;
|
|
660
|
+
const relationKey = typeof edge.relation_key === "string" && edge.relation_key.trim()
|
|
661
|
+
? edge.relation_key.trim().toLowerCase()
|
|
662
|
+
: relationKeyOf(source, type, target);
|
|
663
|
+
allEdges.push({
|
|
664
|
+
source,
|
|
665
|
+
target,
|
|
666
|
+
type,
|
|
667
|
+
fact_status: normalizeStatus(typeof edge.status === "string" ? edge.status : "active"),
|
|
668
|
+
relation_key: relationKey,
|
|
669
|
+
source_event_id: typeof edge.source_event_id === "string" ? edge.source_event_id : undefined,
|
|
670
|
+
evidence_span: typeof edge.evidence_span === "string" ? edge.evidence_span : undefined,
|
|
671
|
+
confidence: typeof edge.confidence === "number" ? edge.confidence : undefined,
|
|
672
|
+
conflict_id: typeof edge.conflict_id === "string" ? edge.conflict_id : undefined,
|
|
673
|
+
evidence_ids: [],
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (allEdges.length === 0) {
|
|
678
|
+
const graphRecords = deps.graphMemoryStore
|
|
679
|
+
? deps.graphMemoryStore.loadAll()
|
|
680
|
+
: (fs.existsSync(graphMemoryPath) ? readJsonl(graphMemoryPath) : []);
|
|
681
|
+
for (const record of graphRecords) {
|
|
682
|
+
const sourceEventId = typeof record.source_event_id === "string" ? record.source_event_id.trim() : "";
|
|
683
|
+
const relations = Array.isArray(record.relations) ? record.relations : [];
|
|
684
|
+
for (const relationRaw of relations) {
|
|
685
|
+
if (typeof relationRaw !== "object" || relationRaw === null)
|
|
686
|
+
continue;
|
|
687
|
+
const relation = relationRaw;
|
|
688
|
+
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
689
|
+
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
690
|
+
const type = (0, ontology_1.normalizeRelationType)(typeof relation.type === "string" && relation.type.trim() ? relation.type.trim() : "related_to", graphSchema);
|
|
691
|
+
if (!source || !target)
|
|
692
|
+
continue;
|
|
693
|
+
allEdges.push({
|
|
694
|
+
source,
|
|
695
|
+
target,
|
|
696
|
+
type,
|
|
697
|
+
fact_status: "active",
|
|
698
|
+
relation_key: relationKeyOf(source, type, target),
|
|
699
|
+
source_event_id: sourceEventId || undefined,
|
|
700
|
+
evidence_span: typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : undefined,
|
|
701
|
+
confidence: typeof relation.confidence === "number" ? relation.confidence : undefined,
|
|
702
|
+
evidence_ids: [],
|
|
703
|
+
});
|
|
556
704
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const projectionIndex = parseJsonFile(projectionIndexPath);
|
|
708
|
+
const entityWikiPathByName = new Map();
|
|
709
|
+
const topicWikiPathByType = new Map();
|
|
710
|
+
const projectionEntities = Array.isArray(projectionIndex?.entities) ? projectionIndex.entities : [];
|
|
711
|
+
const projectionTopics = Array.isArray(projectionIndex?.topics) ? projectionIndex.topics : [];
|
|
712
|
+
for (const item of projectionEntities) {
|
|
713
|
+
if (!item || typeof item !== "object")
|
|
714
|
+
continue;
|
|
715
|
+
const row = item;
|
|
716
|
+
const name = typeof row.name === "string" ? row.name.trim() : "";
|
|
717
|
+
const wikiPath = typeof row.path === "string" ? row.path.trim() : "";
|
|
718
|
+
if (!name || !wikiPath)
|
|
719
|
+
continue;
|
|
720
|
+
entityWikiPathByName.set(name.toLowerCase(), wikiPath);
|
|
721
|
+
}
|
|
722
|
+
for (const item of projectionTopics) {
|
|
723
|
+
if (!item || typeof item !== "object")
|
|
724
|
+
continue;
|
|
725
|
+
const row = item;
|
|
726
|
+
const topic = typeof row.type === "string" ? row.type.trim() : "";
|
|
727
|
+
const wikiPath = typeof row.path === "string" ? row.path.trim() : "";
|
|
728
|
+
if (!topic || !wikiPath)
|
|
729
|
+
continue;
|
|
730
|
+
topicWikiPathByType.set(topic.toLowerCase(), wikiPath);
|
|
731
|
+
}
|
|
732
|
+
function statusAnchor(status) {
|
|
733
|
+
if (status === "active")
|
|
734
|
+
return "current-facts";
|
|
735
|
+
if (status === "pending_conflict")
|
|
736
|
+
return "disputed-facts";
|
|
737
|
+
return "history";
|
|
738
|
+
}
|
|
739
|
+
function buildEvidenceIdsForEdge(edge) {
|
|
740
|
+
const ids = [
|
|
741
|
+
`graph:relation:${edge.relation_key}`,
|
|
742
|
+
edge.source_event_id ? `graph:event:${edge.source_event_id}` : "",
|
|
743
|
+
edge.evidence_span ? `graph:evidence:${edge.source_event_id || edge.relation_key}` : "",
|
|
744
|
+
edge.conflict_id ? `graph:conflict:${edge.conflict_id}` : "",
|
|
745
|
+
];
|
|
746
|
+
const anchor = statusAnchor(edge.fact_status);
|
|
747
|
+
for (const name of [edge.source, edge.target]) {
|
|
748
|
+
const wikiPath = entityWikiPathByName.get(name.toLowerCase());
|
|
749
|
+
if (wikiPath) {
|
|
750
|
+
ids.push(`wiki:${wikiPath}#${anchor}`);
|
|
565
751
|
}
|
|
566
|
-
explicitMatched = true;
|
|
567
|
-
if (!nodes.has(source))
|
|
568
|
-
nodes.set(source, { id: source, type: "entity" });
|
|
569
|
-
if (!nodes.has(target))
|
|
570
|
-
nodes.set(target, { id: target, type: "entity" });
|
|
571
|
-
pushEdge(source, target, type);
|
|
572
752
|
}
|
|
573
|
-
|
|
753
|
+
const topicPath = topicWikiPathByType.get(edge.type.toLowerCase());
|
|
754
|
+
if (topicPath) {
|
|
755
|
+
ids.push(`wiki:${topicPath}#relations`);
|
|
756
|
+
}
|
|
757
|
+
return [...new Set(ids.filter(Boolean))];
|
|
758
|
+
}
|
|
759
|
+
let explicitMatched = false;
|
|
760
|
+
for (const edge of allEdges) {
|
|
761
|
+
if (relFilter && edge.type !== relFilter) {
|
|
574
762
|
continue;
|
|
575
763
|
}
|
|
576
|
-
|
|
764
|
+
const pathEligible = edge.fact_status === "active" || edge.fact_status === "pending_conflict";
|
|
765
|
+
if (pathEligible) {
|
|
766
|
+
pushPathEdge(edge);
|
|
767
|
+
}
|
|
768
|
+
const outgoingMatch = edge.source === entity;
|
|
769
|
+
const incomingMatch = edge.target === entity;
|
|
770
|
+
const directionMatched = direction === "both" ? (outgoingMatch || incomingMatch)
|
|
771
|
+
: direction === "outgoing" ? outgoingMatch
|
|
772
|
+
: incomingMatch;
|
|
773
|
+
if (!directionMatched) {
|
|
577
774
|
continue;
|
|
578
775
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
776
|
+
explicitMatched = true;
|
|
777
|
+
const edgeKey = `${edge.relation_key}|${edge.fact_status}|${edge.conflict_id || ""}`;
|
|
778
|
+
if (edgeKeySet.has(edgeKey)) {
|
|
779
|
+
continue;
|
|
583
780
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
781
|
+
edgeKeySet.add(edgeKey);
|
|
782
|
+
const enrichedEdge = {
|
|
783
|
+
...edge,
|
|
784
|
+
evidence_ids: buildEvidenceIdsForEdge(edge),
|
|
785
|
+
};
|
|
786
|
+
edges.push(enrichedEdge);
|
|
787
|
+
relationTypeDistribution.set(enrichedEdge.type, (relationTypeDistribution.get(enrichedEdge.type) || 0) + 1);
|
|
788
|
+
if (!nodes.has(enrichedEdge.source))
|
|
789
|
+
nodes.set(enrichedEdge.source, { id: enrichedEdge.source, type: "entity" });
|
|
790
|
+
if (!nodes.has(enrichedEdge.target))
|
|
791
|
+
nodes.set(enrichedEdge.target, { id: enrichedEdge.target, type: "entity" });
|
|
792
|
+
}
|
|
793
|
+
if (!explicitMatched) {
|
|
794
|
+
if (!nodes.has(entity)) {
|
|
795
|
+
nodes.set(entity, { id: entity, type: "entity" });
|
|
590
796
|
}
|
|
591
797
|
}
|
|
592
|
-
let
|
|
798
|
+
let graphPath = [];
|
|
593
799
|
if (pathTo) {
|
|
594
800
|
const visited = new Set();
|
|
595
801
|
const queue = [
|
|
@@ -600,7 +806,7 @@ function createTsEngine(deps) {
|
|
|
600
806
|
if (!current)
|
|
601
807
|
break;
|
|
602
808
|
if (current.node === pathTo) {
|
|
603
|
-
|
|
809
|
+
graphPath = current.pathEdges;
|
|
604
810
|
break;
|
|
605
811
|
}
|
|
606
812
|
if (current.depth >= maxDepth) {
|
|
@@ -620,6 +826,34 @@ function createTsEngine(deps) {
|
|
|
620
826
|
}
|
|
621
827
|
}
|
|
622
828
|
}
|
|
829
|
+
const statusCounts = {
|
|
830
|
+
active: 0,
|
|
831
|
+
pending_conflict: 0,
|
|
832
|
+
superseded: 0,
|
|
833
|
+
rejected: 0,
|
|
834
|
+
};
|
|
835
|
+
const wikiRefSet = new Set();
|
|
836
|
+
if (entityWikiPathByName.has(entity.toLowerCase())) {
|
|
837
|
+
wikiRefSet.add(`wiki/${entityWikiPathByName.get(entity.toLowerCase())}`);
|
|
838
|
+
}
|
|
839
|
+
for (const edge of edges) {
|
|
840
|
+
statusCounts[edge.fact_status] += 1;
|
|
841
|
+
for (const name of [edge.source, edge.target]) {
|
|
842
|
+
const wikiPath = entityWikiPathByName.get(name.toLowerCase());
|
|
843
|
+
if (wikiPath) {
|
|
844
|
+
wikiRefSet.add(`wiki/${wikiPath}`);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const topicPath = topicWikiPathByType.get(edge.type.toLowerCase());
|
|
848
|
+
if (topicPath) {
|
|
849
|
+
wikiRefSet.add(`wiki/${topicPath}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
const evidenceIds = [...new Set(edges.flatMap(edge => edge.evidence_ids).filter(Boolean))];
|
|
853
|
+
const conflictEdges = edges.filter(edge => edge.fact_status === "pending_conflict" || edge.fact_status === "rejected");
|
|
854
|
+
const pendingCount = conflictEdges.filter(edge => edge.fact_status === "pending_conflict").length;
|
|
855
|
+
const rejectedCount = conflictEdges.filter(edge => edge.fact_status === "rejected").length;
|
|
856
|
+
const conflictIds = [...new Set(conflictEdges.map(edge => edge.conflict_id).filter((item) => !!item))];
|
|
623
857
|
return {
|
|
624
858
|
success: true,
|
|
625
859
|
data: {
|
|
@@ -628,13 +862,61 @@ function createTsEngine(deps) {
|
|
|
628
862
|
dir: direction,
|
|
629
863
|
nodes: [...nodes.values()],
|
|
630
864
|
edges,
|
|
865
|
+
status_counts: statusCounts,
|
|
631
866
|
path_to: pathTo || "",
|
|
632
867
|
max_depth: maxDepth,
|
|
633
|
-
path,
|
|
868
|
+
path: graphPath,
|
|
869
|
+
wiki_refs: [...wikiRefSet],
|
|
870
|
+
evidence_ids: evidenceIds,
|
|
871
|
+
conflict_hint: conflictEdges.length > 0
|
|
872
|
+
? {
|
|
873
|
+
pending_count: pendingCount,
|
|
874
|
+
rejected_count: rejectedCount,
|
|
875
|
+
conflict_ids: conflictIds,
|
|
876
|
+
suggestion: pendingCount > 0
|
|
877
|
+
? "Pending graph conflicts found. Call list_graph_conflicts first, then resolve_graph_conflict(accept/reject)."
|
|
878
|
+
: "Rejected candidate facts exist. Submit stronger evidence if you want to update the graph.",
|
|
879
|
+
}
|
|
880
|
+
: undefined,
|
|
634
881
|
relation_type_distribution: [...relationTypeDistribution.entries()].map(([type, count]) => ({ type, count })),
|
|
635
882
|
},
|
|
636
883
|
};
|
|
637
884
|
}
|
|
885
|
+
async function exportGraphView(args, _context) {
|
|
886
|
+
if (!deps.graphMemoryStore) {
|
|
887
|
+
return { success: false, error: "Graph memory store is not available." };
|
|
888
|
+
}
|
|
889
|
+
const writeSnapshot = args.write_snapshot !== false;
|
|
890
|
+
const view = deps.graphMemoryStore.exportGraphView();
|
|
891
|
+
const projection = writeSnapshot
|
|
892
|
+
? (0, wiki_projector_1.writeGraphViewProjection)({
|
|
893
|
+
memoryRoot: deps.memoryRoot,
|
|
894
|
+
view,
|
|
895
|
+
})
|
|
896
|
+
: null;
|
|
897
|
+
return {
|
|
898
|
+
success: true,
|
|
899
|
+
data: {
|
|
900
|
+
...view,
|
|
901
|
+
snapshot_written: writeSnapshot,
|
|
902
|
+
projection,
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
async function lintMemoryWiki(_args, _context) {
|
|
907
|
+
if (!deps.graphMemoryStore) {
|
|
908
|
+
return { success: false, error: "Graph memory store is not available." };
|
|
909
|
+
}
|
|
910
|
+
const graphView = deps.graphMemoryStore.exportGraphView();
|
|
911
|
+
const report = (0, wiki_linter_1.lintMemoryWiki)({
|
|
912
|
+
memoryRoot: deps.memoryRoot,
|
|
913
|
+
graphView,
|
|
914
|
+
});
|
|
915
|
+
return {
|
|
916
|
+
success: true,
|
|
917
|
+
data: report,
|
|
918
|
+
};
|
|
919
|
+
}
|
|
638
920
|
async function deleteMemory(args, _context) {
|
|
639
921
|
const targetId = args.memory_id?.trim();
|
|
640
922
|
if (!targetId) {
|
|
@@ -674,11 +956,14 @@ function createTsEngine(deps) {
|
|
|
674
956
|
continue;
|
|
675
957
|
}
|
|
676
958
|
if (typeof args.text === "string") {
|
|
677
|
-
|
|
678
|
-
|
|
959
|
+
const nextText = args.text.trim();
|
|
960
|
+
const layer = typeof record.layer === "string" ? record.layer.trim() : "";
|
|
961
|
+
if (layer === "active") {
|
|
962
|
+
record.summary = nextText;
|
|
963
|
+
record.source_text = nextText;
|
|
679
964
|
}
|
|
680
965
|
else {
|
|
681
|
-
record.summary =
|
|
966
|
+
record.summary = nextText;
|
|
682
967
|
}
|
|
683
968
|
}
|
|
684
969
|
if (typeof args.type === "string") {
|
|
@@ -763,6 +1048,16 @@ function createTsEngine(deps) {
|
|
|
763
1048
|
if (layer === "all" || layer === "archive") {
|
|
764
1049
|
targetFiles.push({ layer: "archive", filePath: archivePath });
|
|
765
1050
|
}
|
|
1051
|
+
const vectorJsonlPath = path.join(deps.memoryRoot, "vector", "lancedb_events.jsonl");
|
|
1052
|
+
const vectorJsonlRecords = readJsonl(vectorJsonlPath);
|
|
1053
|
+
const vectorSourceIndex = new Set();
|
|
1054
|
+
for (const row of vectorJsonlRecords) {
|
|
1055
|
+
const rowLayer = row.layer === "active" || row.layer === "archive" ? row.layer : "";
|
|
1056
|
+
const sourceMemoryId = typeof row.source_memory_id === "string" ? row.source_memory_id.trim() : "";
|
|
1057
|
+
if (!rowLayer || !sourceMemoryId)
|
|
1058
|
+
continue;
|
|
1059
|
+
vectorSourceIndex.add(`${rowLayer}|${sourceMemoryId}`);
|
|
1060
|
+
}
|
|
766
1061
|
const queue = [];
|
|
767
1062
|
const recordsByFile = new Map();
|
|
768
1063
|
for (const target of targetFiles) {
|
|
@@ -776,6 +1071,7 @@ function createTsEngine(deps) {
|
|
|
776
1071
|
}
|
|
777
1072
|
const status = typeof record.embedding_status === "string" ? record.embedding_status.trim() : "";
|
|
778
1073
|
const hasEmbedding = Array.isArray(record.embedding) && record.embedding.length > 0;
|
|
1074
|
+
const hasVectorRows = vectorSourceIndex.has(`${target.layer}|${id}`);
|
|
779
1075
|
if (forceRebuild) {
|
|
780
1076
|
queue.push({ layer: target.layer, filePath: target.filePath, index: i });
|
|
781
1077
|
continue;
|
|
@@ -785,7 +1081,7 @@ function createTsEngine(deps) {
|
|
|
785
1081
|
continue;
|
|
786
1082
|
}
|
|
787
1083
|
}
|
|
788
|
-
else if (status === "ok" || hasEmbedding) {
|
|
1084
|
+
else if ((status === "ok" || hasEmbedding) && hasVectorRows) {
|
|
789
1085
|
continue;
|
|
790
1086
|
}
|
|
791
1087
|
const failCountRaw = failureCountState[id];
|
|
@@ -831,7 +1127,48 @@ function createTsEngine(deps) {
|
|
|
831
1127
|
}
|
|
832
1128
|
const chunkSize = deps.vectorChunking?.chunkSize ?? 600;
|
|
833
1129
|
const chunkOverlap = deps.vectorChunking?.chunkOverlap ?? 100;
|
|
834
|
-
const
|
|
1130
|
+
const evidenceMaxChunks = typeof deps.vectorChunking?.evidenceMaxChunks === "number"
|
|
1131
|
+
? Math.max(0, Math.min(8, Math.floor(deps.vectorChunking.evidenceMaxChunks)))
|
|
1132
|
+
: 2;
|
|
1133
|
+
let chunks = [];
|
|
1134
|
+
if (item.layer === "archive") {
|
|
1135
|
+
const summaryText = typeof record.summary === "string" ? record.summary.trim() : "";
|
|
1136
|
+
const sourceText = typeof record.source_text === "string" ? record.source_text.trim() : "";
|
|
1137
|
+
const summaryChunk = summaryText
|
|
1138
|
+
? [{
|
|
1139
|
+
index: 0,
|
|
1140
|
+
start: 0,
|
|
1141
|
+
end: summaryText.length,
|
|
1142
|
+
text: summaryText,
|
|
1143
|
+
source_field: "summary",
|
|
1144
|
+
}]
|
|
1145
|
+
: [];
|
|
1146
|
+
const evidenceChunks = sourceText
|
|
1147
|
+
? pickEvidenceChunks(splitTextChunks(sourceText, chunkSize, chunkOverlap), evidenceMaxChunks)
|
|
1148
|
+
: [];
|
|
1149
|
+
chunks = [
|
|
1150
|
+
...summaryChunk,
|
|
1151
|
+
...evidenceChunks.map((chunk, idx) => ({
|
|
1152
|
+
index: idx + summaryChunk.length,
|
|
1153
|
+
start: chunk.start,
|
|
1154
|
+
end: chunk.end,
|
|
1155
|
+
text: chunk.text,
|
|
1156
|
+
source_field: "evidence",
|
|
1157
|
+
})),
|
|
1158
|
+
];
|
|
1159
|
+
if (chunks.length === 0 && text) {
|
|
1160
|
+
chunks = splitTextChunks(text, chunkSize, chunkOverlap).map(chunk => ({
|
|
1161
|
+
...chunk,
|
|
1162
|
+
source_field: "summary",
|
|
1163
|
+
}));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
chunks = splitTextChunks(text, chunkSize, chunkOverlap).map(chunk => ({
|
|
1168
|
+
...chunk,
|
|
1169
|
+
source_field: "summary",
|
|
1170
|
+
}));
|
|
1171
|
+
}
|
|
835
1172
|
if (chunks.length === 0) {
|
|
836
1173
|
record.embedding_status = "failed";
|
|
837
1174
|
failed += 1;
|
|
@@ -869,6 +1206,8 @@ function createTsEngine(deps) {
|
|
|
869
1206
|
layer: item.layer,
|
|
870
1207
|
source_memory_id: id,
|
|
871
1208
|
source_memory_canonical_id: typeof record.canonical_id === "string" ? record.canonical_id : id,
|
|
1209
|
+
source_event_id: typeof record.source_event_id === "string" ? record.source_event_id : id,
|
|
1210
|
+
source_field: chunk.source_field,
|
|
872
1211
|
outcome: typeof record.outcome === "string" ? record.outcome : "",
|
|
873
1212
|
entities: Array.isArray(record.entities) ? record.entities.filter(v => typeof v === "string") : [],
|
|
874
1213
|
relations: Array.isArray(record.relations)
|
|
@@ -972,6 +1311,20 @@ function createTsEngine(deps) {
|
|
|
972
1311
|
const vectorJsonlRecords = readJsonl(vectorJsonlPath);
|
|
973
1312
|
const activeVectorRecords = vectorJsonlRecords.filter(record => (record.layer === "active"));
|
|
974
1313
|
const archiveVectorRecords = vectorJsonlRecords.filter(record => (record.layer === "archive"));
|
|
1314
|
+
const lancedbDir = path.join(deps.memoryRoot, "vector", "lancedb");
|
|
1315
|
+
const lancedbExists = fs.existsSync(lancedbDir);
|
|
1316
|
+
let lancedbRecordCount = 0;
|
|
1317
|
+
if (lancedbExists) {
|
|
1318
|
+
try {
|
|
1319
|
+
const lancedbFiles = fs.readdirSync(lancedbDir).filter(f => f.endsWith(".lance") || f.endsWith(".manifest"));
|
|
1320
|
+
lancedbRecordCount = lancedbFiles.length > 0 ? -1 : 0;
|
|
1321
|
+
}
|
|
1322
|
+
catch {
|
|
1323
|
+
lancedbRecordCount = 0;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
const totalVectorRecords = vectorJsonlRecords.length > 0 ? vectorJsonlRecords.length : (lancedbRecordCount === -1 ? -1 : 0);
|
|
1327
|
+
const vectorStorageType = lancedbExists && lancedbRecordCount === -1 ? "lancedb" : (vectorJsonlRecords.length > 0 ? "jsonl" : "none");
|
|
975
1328
|
const syncState = parseJsonFile(path.join(deps.memoryRoot, ".sync_state.json"));
|
|
976
1329
|
const backfillState = parseJsonFile(path.join(deps.memoryRoot, ".vector_backfill_state.json"));
|
|
977
1330
|
const failureCounts = backfillState && typeof backfillState.failureCounts === "object" && backfillState.failureCounts !== null
|
|
@@ -1011,6 +1364,166 @@ function createTsEngine(deps) {
|
|
|
1011
1364
|
{ name: "LLM model connectivity", passed: llmConnectivity.connected, message: llmConnectivity.error || "ok" },
|
|
1012
1365
|
{ name: "Reranker model connectivity", passed: rerankerConnectivity.connected, message: rerankerConnectivity.error || "ok" },
|
|
1013
1366
|
];
|
|
1367
|
+
const qualityCheck = {
|
|
1368
|
+
active: { total: 0, valid: 0, invalid: 0, issues: [] },
|
|
1369
|
+
archive: { total: 0, valid: 0, invalid: 0, issues: [] },
|
|
1370
|
+
};
|
|
1371
|
+
if (fs.existsSync(activePath)) {
|
|
1372
|
+
const content = fs.readFileSync(activePath, "utf-8");
|
|
1373
|
+
const lines = content.split(/\r?\n/).filter(l => l.trim());
|
|
1374
|
+
qualityCheck.active.total = lines.length;
|
|
1375
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1376
|
+
const validation = (0, llm_output_validator_1.validateJsonlLine)(lines[i]);
|
|
1377
|
+
if (validation.valid) {
|
|
1378
|
+
qualityCheck.active.valid++;
|
|
1379
|
+
}
|
|
1380
|
+
else {
|
|
1381
|
+
qualityCheck.active.invalid++;
|
|
1382
|
+
if (qualityCheck.active.issues.length < 5) {
|
|
1383
|
+
qualityCheck.active.issues.push({ line: i + 1, errors: validation.errors });
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
if (fs.existsSync(archivePath)) {
|
|
1389
|
+
const content = fs.readFileSync(archivePath, "utf-8");
|
|
1390
|
+
const lines = content.split(/\r?\n/).filter(l => l.trim());
|
|
1391
|
+
qualityCheck.archive.total = lines.length;
|
|
1392
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1393
|
+
const validation = (0, llm_output_validator_1.validateJsonlLine)(lines[i]);
|
|
1394
|
+
if (validation.valid) {
|
|
1395
|
+
qualityCheck.archive.valid++;
|
|
1396
|
+
}
|
|
1397
|
+
else {
|
|
1398
|
+
qualityCheck.archive.invalid++;
|
|
1399
|
+
if (qualityCheck.archive.issues.length < 5) {
|
|
1400
|
+
qualityCheck.archive.issues.push({ line: i + 1, errors: validation.errors });
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
const totalInvalid = qualityCheck.active.invalid + qualityCheck.archive.invalid;
|
|
1406
|
+
if (totalInvalid > 0) {
|
|
1407
|
+
checks.push({ name: "Data integrity", passed: false, message: `${totalInvalid} invalid records found` });
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
checks.push({ name: "Data integrity", passed: true, message: "All records valid" });
|
|
1411
|
+
}
|
|
1412
|
+
const graphMemoryPath = path.join(deps.memoryRoot, "graph", "memory.jsonl");
|
|
1413
|
+
const graphRecords = readJsonl(graphMemoryPath);
|
|
1414
|
+
function stringField(record, key) {
|
|
1415
|
+
const value = record[key];
|
|
1416
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1417
|
+
}
|
|
1418
|
+
const activeFieldIssues = {
|
|
1419
|
+
missing_id: 0,
|
|
1420
|
+
missing_timestamp: 0,
|
|
1421
|
+
missing_layer: 0,
|
|
1422
|
+
missing_text_payload: 0,
|
|
1423
|
+
};
|
|
1424
|
+
for (const record of activeRecords) {
|
|
1425
|
+
if (!stringField(record, "id"))
|
|
1426
|
+
activeFieldIssues.missing_id += 1;
|
|
1427
|
+
if (!stringField(record, "timestamp"))
|
|
1428
|
+
activeFieldIssues.missing_timestamp += 1;
|
|
1429
|
+
if (stringField(record, "layer") !== "active")
|
|
1430
|
+
activeFieldIssues.missing_layer += 1;
|
|
1431
|
+
const hasPayload = [
|
|
1432
|
+
stringField(record, "summary"),
|
|
1433
|
+
stringField(record, "source_text"),
|
|
1434
|
+
stringField(record, "text"),
|
|
1435
|
+
stringField(record, "message"),
|
|
1436
|
+
].some(Boolean);
|
|
1437
|
+
if (!hasPayload)
|
|
1438
|
+
activeFieldIssues.missing_text_payload += 1;
|
|
1439
|
+
}
|
|
1440
|
+
const archiveFieldIssues = {
|
|
1441
|
+
missing_id: 0,
|
|
1442
|
+
missing_timestamp: 0,
|
|
1443
|
+
missing_layer: 0,
|
|
1444
|
+
missing_summary: 0,
|
|
1445
|
+
missing_source_memory_id: 0,
|
|
1446
|
+
};
|
|
1447
|
+
for (const record of archiveRecords) {
|
|
1448
|
+
if (!stringField(record, "id"))
|
|
1449
|
+
archiveFieldIssues.missing_id += 1;
|
|
1450
|
+
if (!stringField(record, "timestamp"))
|
|
1451
|
+
archiveFieldIssues.missing_timestamp += 1;
|
|
1452
|
+
if (stringField(record, "layer") !== "archive")
|
|
1453
|
+
archiveFieldIssues.missing_layer += 1;
|
|
1454
|
+
if (!stringField(record, "summary"))
|
|
1455
|
+
archiveFieldIssues.missing_summary += 1;
|
|
1456
|
+
if (!stringField(record, "source_memory_id") && !stringField(record, "canonical_id")) {
|
|
1457
|
+
archiveFieldIssues.missing_source_memory_id += 1;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
const vectorFieldIssues = {
|
|
1461
|
+
missing_id: 0,
|
|
1462
|
+
missing_layer: 0,
|
|
1463
|
+
missing_summary: 0,
|
|
1464
|
+
missing_source_memory_id: 0,
|
|
1465
|
+
missing_embedding: 0,
|
|
1466
|
+
};
|
|
1467
|
+
for (const record of vectorJsonlRecords) {
|
|
1468
|
+
if (!stringField(record, "id"))
|
|
1469
|
+
vectorFieldIssues.missing_id += 1;
|
|
1470
|
+
const layer = stringField(record, "layer");
|
|
1471
|
+
if (layer !== "active" && layer !== "archive")
|
|
1472
|
+
vectorFieldIssues.missing_layer += 1;
|
|
1473
|
+
if (!stringField(record, "summary"))
|
|
1474
|
+
vectorFieldIssues.missing_summary += 1;
|
|
1475
|
+
if (!stringField(record, "source_memory_id"))
|
|
1476
|
+
vectorFieldIssues.missing_source_memory_id += 1;
|
|
1477
|
+
const embedding = record.embedding;
|
|
1478
|
+
const vector = record.vector;
|
|
1479
|
+
const hasEmbedding = Array.isArray(embedding) ? embedding.length > 0 : (Array.isArray(vector) ? vector.length > 0 : false);
|
|
1480
|
+
if (!hasEmbedding)
|
|
1481
|
+
vectorFieldIssues.missing_embedding += 1;
|
|
1482
|
+
}
|
|
1483
|
+
const graphFieldIssues = {
|
|
1484
|
+
missing_id: 0,
|
|
1485
|
+
missing_event_ref: 0,
|
|
1486
|
+
missing_layer: 0,
|
|
1487
|
+
malformed_relations: 0,
|
|
1488
|
+
};
|
|
1489
|
+
for (const record of graphRecords) {
|
|
1490
|
+
if (!stringField(record, "id"))
|
|
1491
|
+
graphFieldIssues.missing_id += 1;
|
|
1492
|
+
if (!stringField(record, "source_event_id") && !stringField(record, "archive_event_id")) {
|
|
1493
|
+
graphFieldIssues.missing_event_ref += 1;
|
|
1494
|
+
}
|
|
1495
|
+
const layer = stringField(record, "source_layer");
|
|
1496
|
+
if (layer !== "archive_event" && layer !== "active_only")
|
|
1497
|
+
graphFieldIssues.missing_layer += 1;
|
|
1498
|
+
if (!Array.isArray(record.relations))
|
|
1499
|
+
graphFieldIssues.malformed_relations += 1;
|
|
1500
|
+
}
|
|
1501
|
+
const archiveIdSet = new Set(archiveRecords
|
|
1502
|
+
.map(record => stringField(record, "id"))
|
|
1503
|
+
.filter(Boolean));
|
|
1504
|
+
const vectorLinkedToArchive = vectorJsonlRecords.filter(record => {
|
|
1505
|
+
const sourceMemoryId = stringField(record, "source_memory_id");
|
|
1506
|
+
return !!sourceMemoryId && archiveIdSet.has(sourceMemoryId);
|
|
1507
|
+
}).length;
|
|
1508
|
+
const graphLinkedToArchive = graphRecords.filter(record => {
|
|
1509
|
+
const sourceLayer = stringField(record, "source_layer");
|
|
1510
|
+
const refId = stringField(record, "source_event_id") || stringField(record, "archive_event_id");
|
|
1511
|
+
return sourceLayer === "archive_event" && !!refId && archiveIdSet.has(refId);
|
|
1512
|
+
}).length;
|
|
1513
|
+
const graphConflictStats = deps.graphMemoryStore
|
|
1514
|
+
? deps.graphMemoryStore.getConflictStats()
|
|
1515
|
+
: { pending: 0, accepted: 0, rejected: 0 };
|
|
1516
|
+
const schemaIssueTotal = Object.values(activeFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1517
|
+
+ Object.values(archiveFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1518
|
+
+ Object.values(vectorFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1519
|
+
+ Object.values(graphFieldIssues).reduce((sum, n) => sum + n, 0);
|
|
1520
|
+
checks.push({
|
|
1521
|
+
name: "Field mapping alignment",
|
|
1522
|
+
passed: schemaIssueTotal === 0,
|
|
1523
|
+
message: schemaIssueTotal === 0
|
|
1524
|
+
? "active/archive/vector/graph field mapping aligned with read path"
|
|
1525
|
+
: `${schemaIssueTotal} field mapping issues detected across four memory stores`,
|
|
1526
|
+
});
|
|
1014
1527
|
return {
|
|
1015
1528
|
success: true,
|
|
1016
1529
|
data: {
|
|
@@ -1027,6 +1540,8 @@ function createTsEngine(deps) {
|
|
|
1027
1540
|
path: archivePath,
|
|
1028
1541
|
},
|
|
1029
1542
|
vector: {
|
|
1543
|
+
storage_type: vectorStorageType,
|
|
1544
|
+
lancedb_exists: lancedbExists,
|
|
1030
1545
|
active_coverage: activeVector.coverage,
|
|
1031
1546
|
archive_coverage: archiveVector.coverage,
|
|
1032
1547
|
active_unembedded: activeVector.pending + activeVector.failed,
|
|
@@ -1040,6 +1555,7 @@ function createTsEngine(deps) {
|
|
|
1040
1555
|
active: activeVectorRecords.length,
|
|
1041
1556
|
archive: archiveVectorRecords.length,
|
|
1042
1557
|
},
|
|
1558
|
+
total_vector_records: totalVectorRecords,
|
|
1043
1559
|
last_backfill_summary: lastVectorBackfill,
|
|
1044
1560
|
backfill_state: {
|
|
1045
1561
|
pending_retry_records: pendingRetry,
|
|
@@ -1048,6 +1564,7 @@ function createTsEngine(deps) {
|
|
|
1048
1564
|
},
|
|
1049
1565
|
graph_rules: {
|
|
1050
1566
|
graph_mutation_log_exists: fs.existsSync(path.join(deps.memoryRoot, "graph", "mutation_log.jsonl")),
|
|
1567
|
+
graph_conflicts: graphConflictStats,
|
|
1051
1568
|
rules_exists: fs.existsSync(path.join(deps.memoryRoot, "CORTEX_RULES.md")),
|
|
1052
1569
|
},
|
|
1053
1570
|
},
|
|
@@ -1056,22 +1573,100 @@ function createTsEngine(deps) {
|
|
|
1056
1573
|
llm: llmConnectivity,
|
|
1057
1574
|
reranker: rerankerConnectivity,
|
|
1058
1575
|
},
|
|
1059
|
-
|
|
1576
|
+
quality_check: qualityCheck,
|
|
1577
|
+
schema_alignment: {
|
|
1578
|
+
active: {
|
|
1579
|
+
records: activeRecords.length,
|
|
1580
|
+
issues: activeFieldIssues,
|
|
1581
|
+
},
|
|
1582
|
+
archive: {
|
|
1583
|
+
records: archiveRecords.length,
|
|
1584
|
+
issues: archiveFieldIssues,
|
|
1585
|
+
},
|
|
1586
|
+
vector: {
|
|
1587
|
+
records: vectorJsonlRecords.length,
|
|
1588
|
+
issues: vectorFieldIssues,
|
|
1589
|
+
linked_to_archive: vectorLinkedToArchive,
|
|
1590
|
+
},
|
|
1591
|
+
graph: {
|
|
1592
|
+
records: graphRecords.length,
|
|
1593
|
+
issues: graphFieldIssues,
|
|
1594
|
+
linked_archive_events: graphLinkedToArchive,
|
|
1595
|
+
},
|
|
1596
|
+
cross_layer_links: {
|
|
1597
|
+
archive_records: archiveIdSet.size,
|
|
1598
|
+
vector_archive_link_coverage: archiveIdSet.size > 0
|
|
1599
|
+
? Number((vectorLinkedToArchive / archiveIdSet.size).toFixed(4))
|
|
1600
|
+
: 0,
|
|
1601
|
+
graph_archive_link_coverage: archiveIdSet.size > 0
|
|
1602
|
+
? Number((graphLinkedToArchive / archiveIdSet.size).toFixed(4))
|
|
1603
|
+
: 0,
|
|
1604
|
+
},
|
|
1605
|
+
},
|
|
1606
|
+
recommendations: [
|
|
1607
|
+
...(totalInvalid > 0 ? ["Run repair-memory --fix to clean invalid records"] : []),
|
|
1608
|
+
...(schemaIssueTotal > 0 ? ["Run diagnostics output schema_alignment and repair missing cross-layer fields"] : []),
|
|
1609
|
+
],
|
|
1060
1610
|
},
|
|
1061
1611
|
};
|
|
1062
1612
|
}
|
|
1063
1613
|
async function searchMemory(args, context) {
|
|
1064
|
-
|
|
1614
|
+
const argsRecord = asRecord(args) || {};
|
|
1615
|
+
const argsInput = asRecord(argsRecord.input);
|
|
1616
|
+
const queryCandidate = [
|
|
1617
|
+
typeof args.query === "string" ? args.query : "",
|
|
1618
|
+
typeof argsRecord.query === "string" ? String(argsRecord.query) : "",
|
|
1619
|
+
typeof argsRecord.q === "string" ? String(argsRecord.q) : "",
|
|
1620
|
+
typeof argsRecord.keyword === "string" ? String(argsRecord.keyword) : "",
|
|
1621
|
+
typeof argsInput?.query === "string" ? String(argsInput.query) : "",
|
|
1622
|
+
typeof argsInput?.q === "string" ? String(argsInput.q) : "",
|
|
1623
|
+
].find(item => item.trim());
|
|
1624
|
+
const query = queryCandidate ? queryCandidate.trim() : "";
|
|
1625
|
+
if (!query) {
|
|
1065
1626
|
return {
|
|
1066
1627
|
success: false,
|
|
1067
1628
|
error: "Invalid input provided. Missing 'query' parameter.",
|
|
1068
1629
|
};
|
|
1069
1630
|
}
|
|
1631
|
+
const topKRaw = [
|
|
1632
|
+
typeof args.top_k === "number" ? args.top_k : undefined,
|
|
1633
|
+
typeof argsRecord.top_k === "number" ? Number(argsRecord.top_k) : undefined,
|
|
1634
|
+
typeof argsRecord.topK === "number" ? Number(argsRecord.topK) : undefined,
|
|
1635
|
+
typeof argsInput?.top_k === "number" ? Number(argsInput.top_k) : undefined,
|
|
1636
|
+
typeof argsInput?.topK === "number" ? Number(argsInput.topK) : undefined,
|
|
1637
|
+
].find(value => typeof value === "number" && Number.isFinite(value));
|
|
1638
|
+
const fusionModeRaw = [
|
|
1639
|
+
typeof args.fusion_mode === "string" ? args.fusion_mode : "",
|
|
1640
|
+
typeof argsRecord.fusion_mode === "string" ? String(argsRecord.fusion_mode) : "",
|
|
1641
|
+
typeof argsRecord.fusionMode === "string" ? String(argsRecord.fusionMode) : "",
|
|
1642
|
+
typeof argsInput?.fusion_mode === "string" ? String(argsInput.fusion_mode) : "",
|
|
1643
|
+
typeof argsInput?.fusionMode === "string" ? String(argsInput.fusionMode) : "",
|
|
1644
|
+
].find(value => value === "auto" || value === "authoritative" || value === "candidates" || value === "off");
|
|
1645
|
+
const trackHitsRaw = [
|
|
1646
|
+
typeof args.track_hits === "boolean" ? args.track_hits : undefined,
|
|
1647
|
+
typeof argsRecord.track_hits === "boolean" ? Boolean(argsRecord.track_hits) : undefined,
|
|
1648
|
+
typeof argsRecord.trackHits === "boolean" ? Boolean(argsRecord.trackHits) : undefined,
|
|
1649
|
+
typeof argsInput?.track_hits === "boolean" ? Boolean(argsInput.track_hits) : undefined,
|
|
1650
|
+
typeof argsInput?.trackHits === "boolean" ? Boolean(argsInput.trackHits) : undefined,
|
|
1651
|
+
].find(value => typeof value === "boolean");
|
|
1070
1652
|
const result = await deps.readStore.searchMemory({
|
|
1071
|
-
query
|
|
1072
|
-
topK: typeof
|
|
1653
|
+
query,
|
|
1654
|
+
topK: typeof topKRaw === "number" && topKRaw > 0 ? Math.floor(topKRaw) : 3,
|
|
1655
|
+
fusionMode: fusionModeRaw || "auto",
|
|
1656
|
+
trackHits: trackHitsRaw !== false,
|
|
1073
1657
|
});
|
|
1074
|
-
return {
|
|
1658
|
+
return {
|
|
1659
|
+
success: true,
|
|
1660
|
+
data: {
|
|
1661
|
+
results: result.results,
|
|
1662
|
+
vector_semantic_results: result.semantic_results,
|
|
1663
|
+
vector_keyword_results: result.keyword_results,
|
|
1664
|
+
channel_results: result.channel_results,
|
|
1665
|
+
vector_search_strategy: result.strategy,
|
|
1666
|
+
timing_ms: result.timing_ms,
|
|
1667
|
+
debug: result.debug,
|
|
1668
|
+
},
|
|
1669
|
+
};
|
|
1075
1670
|
}
|
|
1076
1671
|
async function getHotContext(args, _context) {
|
|
1077
1672
|
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 20;
|
|
@@ -1079,12 +1674,22 @@ function createTsEngine(deps) {
|
|
|
1079
1674
|
return { success: true, data: result.context };
|
|
1080
1675
|
}
|
|
1081
1676
|
async function getAutoContext(args, context) {
|
|
1082
|
-
const
|
|
1677
|
+
const argsRecord = asRecord(args) || {};
|
|
1678
|
+
const argsInput = asRecord(argsRecord.input);
|
|
1679
|
+
const includeHotRaw = [
|
|
1680
|
+
typeof args.include_hot === "boolean" ? args.include_hot : undefined,
|
|
1681
|
+
typeof argsRecord.include_hot === "boolean" ? Boolean(argsRecord.include_hot) : undefined,
|
|
1682
|
+
typeof argsRecord.includeHot === "boolean" ? Boolean(argsRecord.includeHot) : undefined,
|
|
1683
|
+
typeof argsInput?.include_hot === "boolean" ? Boolean(argsInput.include_hot) : undefined,
|
|
1684
|
+
typeof argsInput?.includeHot === "boolean" ? Boolean(argsInput.includeHot) : undefined,
|
|
1685
|
+
].find(value => typeof value === "boolean");
|
|
1686
|
+
const sessionId = deps.resolveSessionId((context || {}));
|
|
1083
1687
|
const cached = deps.getCachedAutoSearch(sessionId);
|
|
1084
1688
|
const result = await deps.readStore.getAutoContext({
|
|
1085
|
-
includeHot:
|
|
1689
|
+
includeHot: includeHotRaw !== false,
|
|
1086
1690
|
sessionId,
|
|
1087
1691
|
cachedAutoSearch: cached ?? undefined,
|
|
1692
|
+
recentMessages: getRecentSessionMessages(sessionId, 8),
|
|
1088
1693
|
});
|
|
1089
1694
|
if (!result.auto_search && !result.hot_context) {
|
|
1090
1695
|
return {
|
|
@@ -1097,6 +1702,49 @@ function createTsEngine(deps) {
|
|
|
1097
1702
|
}
|
|
1098
1703
|
return { success: true, data: result };
|
|
1099
1704
|
}
|
|
1705
|
+
async function listGraphConflicts(args, _context) {
|
|
1706
|
+
if (!deps.graphMemoryStore) {
|
|
1707
|
+
return { success: false, error: "Graph memory store is not available." };
|
|
1708
|
+
}
|
|
1709
|
+
const status = args.status === "pending" || args.status === "accepted" || args.status === "rejected" || args.status === "all"
|
|
1710
|
+
? args.status
|
|
1711
|
+
: "pending";
|
|
1712
|
+
const limit = typeof args.limit === "number" && Number.isFinite(args.limit) && args.limit > 0
|
|
1713
|
+
? Math.min(500, Math.floor(args.limit))
|
|
1714
|
+
: 50;
|
|
1715
|
+
const items = deps.graphMemoryStore.listConflicts({ status, limit });
|
|
1716
|
+
return { success: true, data: { status, count: items.length, items } };
|
|
1717
|
+
}
|
|
1718
|
+
async function resolveGraphConflict(args, _context) {
|
|
1719
|
+
if (!deps.graphMemoryStore) {
|
|
1720
|
+
return { success: false, error: "Graph memory store is not available." };
|
|
1721
|
+
}
|
|
1722
|
+
const conflictId = (args.conflict_id || "").trim();
|
|
1723
|
+
const action = args.action === "accept" || args.action === "reject" ? args.action : null;
|
|
1724
|
+
if (!conflictId) {
|
|
1725
|
+
return { success: false, error: "Invalid input provided. Missing 'conflict_id' parameter." };
|
|
1726
|
+
}
|
|
1727
|
+
if (!action) {
|
|
1728
|
+
return { success: false, error: "Invalid input provided. 'action' must be accept or reject." };
|
|
1729
|
+
}
|
|
1730
|
+
const note = typeof args.note === "string" ? args.note.trim() : undefined;
|
|
1731
|
+
const result = await deps.graphMemoryStore.resolveConflict({
|
|
1732
|
+
conflictId,
|
|
1733
|
+
action,
|
|
1734
|
+
note,
|
|
1735
|
+
});
|
|
1736
|
+
if (!result.success) {
|
|
1737
|
+
return { success: false, error: result.reason || "resolve_graph_conflict_failed" };
|
|
1738
|
+
}
|
|
1739
|
+
return {
|
|
1740
|
+
success: true,
|
|
1741
|
+
data: {
|
|
1742
|
+
conflict_id: conflictId,
|
|
1743
|
+
action,
|
|
1744
|
+
applied_record_id: result.appliedRecordId,
|
|
1745
|
+
},
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1100
1748
|
async function syncMemory(_args, _context) {
|
|
1101
1749
|
try {
|
|
1102
1750
|
const result = await deps.sessionSync.syncMemory();
|
|
@@ -1157,9 +1805,17 @@ function createTsEngine(deps) {
|
|
|
1157
1805
|
deps.logger.debug(`TS buffered ${role} message for session ${sessionId} source=${source}`);
|
|
1158
1806
|
if (role === "user" && text.length > 5) {
|
|
1159
1807
|
try {
|
|
1160
|
-
const
|
|
1808
|
+
const historical = isHistoricalMemoryQuery(text);
|
|
1809
|
+
const query = buildAutoSearchQuery(sessionId, text, historical);
|
|
1810
|
+
const searchResult = await deps.readStore.searchMemory({
|
|
1811
|
+
query,
|
|
1812
|
+
topK: 3,
|
|
1813
|
+
mode: historical ? "auto" : "lightweight",
|
|
1814
|
+
fusionMode: "off",
|
|
1815
|
+
trackHits: false,
|
|
1816
|
+
});
|
|
1161
1817
|
if (searchResult.results.length > 0) {
|
|
1162
|
-
deps.setSessionAutoSearchCache(sessionId,
|
|
1818
|
+
deps.setSessionAutoSearchCache(sessionId, query, searchResult.results);
|
|
1163
1819
|
deps.logger.info(`TS auto-search cached ${searchResult.results.length} results for context`);
|
|
1164
1820
|
}
|
|
1165
1821
|
}
|
|
@@ -1190,6 +1846,10 @@ function createTsEngine(deps) {
|
|
|
1190
1846
|
getAutoContext,
|
|
1191
1847
|
storeEvent,
|
|
1192
1848
|
queryGraph,
|
|
1849
|
+
exportGraphView,
|
|
1850
|
+
lintMemoryWiki,
|
|
1851
|
+
listGraphConflicts,
|
|
1852
|
+
resolveGraphConflict,
|
|
1193
1853
|
reflectMemory,
|
|
1194
1854
|
syncMemory,
|
|
1195
1855
|
promoteMemory,
|