openclaw-cortex-memory 0.1.0-Alpha.3 → 0.1.0-Alpha.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +296 -203
- package/SIGNATURE.md +7 -0
- package/SKILL.md +92 -268
- package/dist/index.d.ts +100 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1249 -1252
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +501 -16
- package/dist/src/dedup/three_stage_deduplicator.d.ts +25 -0
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -0
- package/dist/src/dedup/three_stage_deduplicator.js +224 -0
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -0
- package/dist/src/engine/memory_engine.d.ts +6 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +242 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +1468 -52
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +29 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +125 -0
- package/dist/src/graph/ontology.d.ts.map +1 -0
- package/dist/src/graph/ontology.js +1237 -0
- package/dist/src/graph/ontology.js.map +1 -0
- 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 +66 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +659 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts +7 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +352 -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 +33 -0
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +67 -64
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +136 -0
- package/dist/src/store/archive_store.d.ts.map +1 -0
- package/dist/src/store/archive_store.js +635 -0
- package/dist/src/store/archive_store.js.map +1 -0
- package/dist/src/store/embedding_utils.d.ts +32 -0
- package/dist/src/store/embedding_utils.d.ts.map +1 -0
- package/dist/src/store/embedding_utils.js +173 -0
- package/dist/src/store/embedding_utils.js.map +1 -0
- package/dist/src/store/graph_memory_store.d.ts +114 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +841 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +89 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +2459 -28
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +45 -0
- package/dist/src/store/vector_store.d.ts.map +1 -0
- package/dist/src/store/vector_store.js +202 -0
- package/dist/src/store/vector_store.js.map +1 -0
- package/dist/src/store/write_store.d.ts +54 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +284 -6
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +119 -2
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +2377 -31
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/utils/runtime_env.d.ts +4 -0
- package/dist/src/utils/runtime_env.d.ts.map +1 -0
- package/dist/src/utils/runtime_env.js +51 -0
- package/dist/src/utils/runtime_env.js.map +1 -0
- package/dist/src/wiki/wiki_linter.d.ts +25 -0
- package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
- package/dist/src/wiki/wiki_linter.js +268 -0
- package/dist/src/wiki/wiki_linter.js.map +1 -0
- package/dist/src/wiki/wiki_logger.d.ts +10 -0
- package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
- package/dist/src/wiki/wiki_logger.js +78 -0
- package/dist/src/wiki/wiki_logger.js.map +1 -0
- package/dist/src/wiki/wiki_maintainer.d.ts +36 -0
- package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
- package/dist/src/wiki/wiki_maintainer.js +38 -0
- package/dist/src/wiki/wiki_maintainer.js.map +1 -0
- package/dist/src/wiki/wiki_projector.d.ts +33 -0
- package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
- package/dist/src/wiki/wiki_projector.js +633 -0
- package/dist/src/wiki/wiki_projector.js.map +1 -0
- package/dist/src/wiki/wiki_queue.d.ts +29 -0
- package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
- package/dist/src/wiki/wiki_queue.js +137 -0
- package/dist/src/wiki/wiki_queue.js.map +1 -0
- package/openclaw.plugin.json +501 -16
- package/package.json +58 -7
- package/schema/graph.schema.yaml +330 -0
- package/scripts/cli.js +19 -14
- package/scripts/repair-memory.js +321 -0
- package/scripts/uninstall.js +22 -5
- package/skills/cortex-memory/SKILL.md +49 -0
- package/skills/cortex-memory/references/agent-manual.md +115 -0
- package/skills/cortex-memory/references/configuration.md +92 -0
- package/skills/cortex-memory/references/publish-checklist.md +46 -0
- package/skills/cortex-memory/references/system-prompt-template.md +27 -0
- package/skills/cortex-memory/references/tools.md +181 -0
- package/skills/cortex-memory/scripts/smoke-check.ps1 +56 -0
- package/index.ts +0 -2142
|
@@ -36,7 +36,43 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.createTsEngine = createTsEngine;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
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");
|
|
44
|
+
const PROMPT_VERSIONS = {
|
|
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",
|
|
48
|
+
};
|
|
39
49
|
function createTsEngine(deps) {
|
|
50
|
+
const graphSchema = (0, ontology_1.loadGraphSchema)(deps.projectRoot);
|
|
51
|
+
const sessionMessageBuffer = new Map();
|
|
52
|
+
const maxMessagesPerSession = 500;
|
|
53
|
+
const maxBufferedSessions = 500;
|
|
54
|
+
function pushSessionMessage(sessionId, message) {
|
|
55
|
+
const current = sessionMessageBuffer.get(sessionId) || [];
|
|
56
|
+
current.push({
|
|
57
|
+
id: `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
58
|
+
session_id: sessionId,
|
|
59
|
+
role: message.role,
|
|
60
|
+
content: message.text,
|
|
61
|
+
timestamp: new Date().toISOString(),
|
|
62
|
+
});
|
|
63
|
+
if (current.length > maxMessagesPerSession) {
|
|
64
|
+
sessionMessageBuffer.set(sessionId, current.slice(current.length - maxMessagesPerSession));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
sessionMessageBuffer.set(sessionId, current);
|
|
68
|
+
}
|
|
69
|
+
if (sessionMessageBuffer.size > maxBufferedSessions) {
|
|
70
|
+
const first = sessionMessageBuffer.keys().next().value;
|
|
71
|
+
if (first) {
|
|
72
|
+
sessionMessageBuffer.delete(first);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
40
76
|
function asRecord(value) {
|
|
41
77
|
if (typeof value === "object" && value !== null) {
|
|
42
78
|
return value;
|
|
@@ -71,25 +107,460 @@ function createTsEngine(deps) {
|
|
|
71
107
|
archivePath: path.join(deps.memoryRoot, "sessions", "archive", "sessions.jsonl"),
|
|
72
108
|
};
|
|
73
109
|
}
|
|
110
|
+
function parseJsonFile(filePath) {
|
|
111
|
+
try {
|
|
112
|
+
if (!fs.existsSync(filePath)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const raw = fs.readFileSync(filePath, "utf-8").trim();
|
|
116
|
+
if (!raw) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function embeddingStats(records) {
|
|
126
|
+
let ok = 0;
|
|
127
|
+
let failed = 0;
|
|
128
|
+
let pending = 0;
|
|
129
|
+
for (const record of records) {
|
|
130
|
+
const explicit = typeof record.embedding_status === "string" ? record.embedding_status.trim() : "";
|
|
131
|
+
const hasEmbedding = Array.isArray(record.embedding) && record.embedding.length > 0;
|
|
132
|
+
if (explicit === "ok" || hasEmbedding) {
|
|
133
|
+
ok += 1;
|
|
134
|
+
}
|
|
135
|
+
else if (explicit === "failed") {
|
|
136
|
+
failed += 1;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
pending += 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const total = records.length;
|
|
143
|
+
const coverage = total > 0 ? Number((ok / total).toFixed(4)) : 0;
|
|
144
|
+
return { total, ok, failed, pending, coverage };
|
|
145
|
+
}
|
|
146
|
+
function normalizeBaseUrl(value) {
|
|
147
|
+
if (!value)
|
|
148
|
+
return "";
|
|
149
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
150
|
+
}
|
|
151
|
+
function estimateTokenCount(text) {
|
|
152
|
+
const parts = text
|
|
153
|
+
.split(/[\s,.;:!?,。;:!?、()()[\]{}"'`~]+/)
|
|
154
|
+
.map(part => part.trim())
|
|
155
|
+
.filter(Boolean);
|
|
156
|
+
return parts.length;
|
|
157
|
+
}
|
|
158
|
+
function buildVectorSourceText(record, layer) {
|
|
159
|
+
if (layer === "active") {
|
|
160
|
+
const content = typeof record.content === "string" && record.content.trim()
|
|
161
|
+
? record.content.trim()
|
|
162
|
+
: (typeof record.text === "string" ? record.text.trim() : "");
|
|
163
|
+
return content;
|
|
164
|
+
}
|
|
165
|
+
const sourceText = typeof record.source_text === "string" ? record.source_text.trim() : "";
|
|
166
|
+
if (sourceText) {
|
|
167
|
+
return sourceText;
|
|
168
|
+
}
|
|
169
|
+
const summary = typeof record.summary === "string" ? record.summary.trim() : "";
|
|
170
|
+
return summary;
|
|
171
|
+
}
|
|
172
|
+
function splitTextChunks(text, chunkSize, chunkOverlap) {
|
|
173
|
+
const normalizedSize = Number.isFinite(chunkSize) && chunkSize >= 200 ? Math.floor(chunkSize) : 600;
|
|
174
|
+
const normalizedOverlap = Number.isFinite(chunkOverlap) && chunkOverlap >= 0
|
|
175
|
+
? Math.floor(chunkOverlap)
|
|
176
|
+
: 100;
|
|
177
|
+
const overlap = Math.min(normalizedOverlap, Math.max(0, normalizedSize - 50));
|
|
178
|
+
const output = [];
|
|
179
|
+
let cursor = 0;
|
|
180
|
+
let index = 0;
|
|
181
|
+
const punctuationSet = new Set(["。", "!", "?", ".", "!", "?", "\n", ";", ";"]);
|
|
182
|
+
while (cursor < text.length) {
|
|
183
|
+
const rawEnd = Math.min(text.length, cursor + normalizedSize);
|
|
184
|
+
let end = rawEnd;
|
|
185
|
+
if (rawEnd < text.length) {
|
|
186
|
+
const backwardStart = Math.max(cursor + Math.floor(normalizedSize * 0.45), cursor + 1);
|
|
187
|
+
let found = -1;
|
|
188
|
+
for (let i = rawEnd - 1; i >= backwardStart; i -= 1) {
|
|
189
|
+
if (punctuationSet.has(text[i])) {
|
|
190
|
+
found = i + 1;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (found < 0) {
|
|
195
|
+
const forwardEnd = Math.min(text.length, rawEnd + Math.floor(normalizedSize * 0.2));
|
|
196
|
+
for (let i = rawEnd; i < forwardEnd; i += 1) {
|
|
197
|
+
if (punctuationSet.has(text[i])) {
|
|
198
|
+
found = i + 1;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (found > cursor) {
|
|
204
|
+
end = found;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (end <= cursor) {
|
|
208
|
+
end = Math.min(text.length, cursor + normalizedSize);
|
|
209
|
+
}
|
|
210
|
+
const chunkText = text.slice(cursor, end).trim();
|
|
211
|
+
if (chunkText) {
|
|
212
|
+
output.push({ index, start: cursor, end, text: chunkText });
|
|
213
|
+
index += 1;
|
|
214
|
+
}
|
|
215
|
+
if (end >= text.length) {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
const nextCursor = Math.max(cursor + 1, end - overlap);
|
|
219
|
+
cursor = nextCursor <= cursor ? end : nextCursor;
|
|
220
|
+
}
|
|
221
|
+
return output;
|
|
222
|
+
}
|
|
223
|
+
function pickEvidenceChunks(chunks, maxCount) {
|
|
224
|
+
if (!chunks.length || maxCount <= 0)
|
|
225
|
+
return [];
|
|
226
|
+
if (chunks.length <= maxCount)
|
|
227
|
+
return chunks;
|
|
228
|
+
const picked = new Map();
|
|
229
|
+
picked.set(chunks[0].index, chunks[0]);
|
|
230
|
+
if (maxCount >= 2) {
|
|
231
|
+
const mid = chunks[Math.floor(chunks.length / 2)];
|
|
232
|
+
picked.set(mid.index, mid);
|
|
233
|
+
}
|
|
234
|
+
if (maxCount >= 3) {
|
|
235
|
+
const last = chunks[chunks.length - 1];
|
|
236
|
+
picked.set(last.index, last);
|
|
237
|
+
}
|
|
238
|
+
if (picked.size < maxCount) {
|
|
239
|
+
for (const chunk of chunks) {
|
|
240
|
+
if (!picked.has(chunk.index)) {
|
|
241
|
+
picked.set(chunk.index, chunk);
|
|
242
|
+
}
|
|
243
|
+
if (picked.size >= maxCount)
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return [...picked.values()].sort((a, b) => a.index - b.index).slice(0, maxCount);
|
|
248
|
+
}
|
|
249
|
+
function upsertJsonFile(filePath, patch) {
|
|
250
|
+
const current = parseJsonFile(filePath) || {};
|
|
251
|
+
const next = { ...current, ...patch };
|
|
252
|
+
const dir = path.dirname(filePath);
|
|
253
|
+
if (!fs.existsSync(dir)) {
|
|
254
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf-8");
|
|
257
|
+
}
|
|
258
|
+
async function probeModelConnection(args) {
|
|
259
|
+
const defaultTimeoutMs = args.kind === "llm" ? 30000 : 15000;
|
|
260
|
+
const timeoutMs = typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs) && args.timeoutMs >= 1000
|
|
261
|
+
? Math.floor(args.timeoutMs)
|
|
262
|
+
: defaultTimeoutMs;
|
|
263
|
+
if (!args.model || !args.apiKey || !args.baseUrl) {
|
|
264
|
+
return {
|
|
265
|
+
configured: false,
|
|
266
|
+
connected: false,
|
|
267
|
+
model: args.model || "",
|
|
268
|
+
base_url: args.baseUrl || "",
|
|
269
|
+
error: "not_configured",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
let endpoint = args.baseUrl;
|
|
273
|
+
let payload = {};
|
|
274
|
+
if (args.kind === "embedding") {
|
|
275
|
+
endpoint = args.baseUrl.endsWith("/embeddings") ? args.baseUrl : `${args.baseUrl}/embeddings`;
|
|
276
|
+
payload = {
|
|
277
|
+
model: args.model,
|
|
278
|
+
input: "diagnostics connectivity probe",
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
else if (args.kind === "llm") {
|
|
282
|
+
endpoint = args.baseUrl.endsWith("/chat/completions") ? args.baseUrl : `${args.baseUrl}/chat/completions`;
|
|
283
|
+
payload = {
|
|
284
|
+
model: args.model,
|
|
285
|
+
messages: [{ role: "user", content: "ping" }],
|
|
286
|
+
max_tokens: 4,
|
|
287
|
+
temperature: 0,
|
|
288
|
+
stream: false,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
endpoint = args.baseUrl.endsWith("/rerank") ? args.baseUrl : `${args.baseUrl}/rerank`;
|
|
293
|
+
payload = {
|
|
294
|
+
model: args.model,
|
|
295
|
+
query: "diagnostics",
|
|
296
|
+
documents: ["diagnostics connectivity probe"],
|
|
297
|
+
top_n: 1,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
let lastError = "unknown_error";
|
|
301
|
+
const maxAttempts = args.kind === "llm" ? 3 : 1;
|
|
302
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
303
|
+
const response = await (0, http_post_1.postJsonWithTimeout)({
|
|
304
|
+
endpoint,
|
|
305
|
+
apiKey: args.apiKey,
|
|
306
|
+
body: payload,
|
|
307
|
+
timeoutMs,
|
|
308
|
+
headers: { accept: "application/json" },
|
|
309
|
+
});
|
|
310
|
+
if (response.ok) {
|
|
311
|
+
return {
|
|
312
|
+
configured: true,
|
|
313
|
+
connected: true,
|
|
314
|
+
model: args.model,
|
|
315
|
+
base_url: args.baseUrl,
|
|
316
|
+
error: "",
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (response.aborted) {
|
|
320
|
+
lastError = `timeout_${timeoutMs}ms`;
|
|
321
|
+
}
|
|
322
|
+
else if (response.status > 0) {
|
|
323
|
+
const details = (response.text || "").trim().slice(0, 180);
|
|
324
|
+
lastError = details ? `http_${response.status}:${details}` : `http_${response.status}`;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
lastError = response.error || "network_error";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
configured: true,
|
|
332
|
+
connected: false,
|
|
333
|
+
model: args.model,
|
|
334
|
+
base_url: args.baseUrl,
|
|
335
|
+
error: lastError,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
async function requestEmbedding(args) {
|
|
339
|
+
const endpoint = args.baseUrl.endsWith("/embeddings") ? args.baseUrl : `${args.baseUrl}/embeddings`;
|
|
340
|
+
const body = {
|
|
341
|
+
input: args.text,
|
|
342
|
+
model: args.model,
|
|
343
|
+
};
|
|
344
|
+
if (typeof args.dimensions === "number" && Number.isFinite(args.dimensions) && args.dimensions > 0) {
|
|
345
|
+
body.dimensions = args.dimensions;
|
|
346
|
+
}
|
|
347
|
+
const timeoutMs = typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs) && args.timeoutMs >= 1000
|
|
348
|
+
? Math.floor(args.timeoutMs)
|
|
349
|
+
: 20000;
|
|
350
|
+
const maxRetries = typeof args.maxRetries === "number" && Number.isFinite(args.maxRetries) && args.maxRetries >= 1
|
|
351
|
+
? Math.min(8, Math.floor(args.maxRetries))
|
|
352
|
+
: 4;
|
|
353
|
+
let lastError = null;
|
|
354
|
+
for (let attempt = 0; attempt < maxRetries; attempt += 1) {
|
|
355
|
+
const response = await (0, http_post_1.postJsonWithTimeout)({
|
|
356
|
+
endpoint,
|
|
357
|
+
apiKey: args.apiKey,
|
|
358
|
+
body,
|
|
359
|
+
timeoutMs,
|
|
360
|
+
});
|
|
361
|
+
if (!response.ok) {
|
|
362
|
+
lastError = new Error(response.status > 0 ? `embedding_http_${response.status}` : (response.error || "embedding_network_error"));
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const json = (response.json || {});
|
|
367
|
+
const embedding = json?.data?.[0]?.embedding;
|
|
368
|
+
if (Array.isArray(embedding) && embedding.length > 0) {
|
|
369
|
+
return embedding.filter(item => Number.isFinite(item));
|
|
370
|
+
}
|
|
371
|
+
lastError = new Error("embedding_empty");
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
lastError = error;
|
|
375
|
+
}
|
|
376
|
+
if (attempt < maxRetries - 1) {
|
|
377
|
+
await new Promise(resolve => setTimeout(resolve, 300 * Math.pow(2, attempt)));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError || "embedding_failed"));
|
|
381
|
+
}
|
|
74
382
|
async function storeEvent(args, _context) {
|
|
75
383
|
try {
|
|
76
|
-
|
|
384
|
+
const rawArgs = args;
|
|
385
|
+
const summaryCandidate = typeof rawArgs?.summary === "string"
|
|
386
|
+
? rawArgs.summary
|
|
387
|
+
: typeof rawArgs?.input?.summary === "string"
|
|
388
|
+
? String(rawArgs.input.summary)
|
|
389
|
+
: typeof rawArgs?.event?.summary === "string"
|
|
390
|
+
? String(rawArgs.event.summary)
|
|
391
|
+
: "";
|
|
392
|
+
const normalizedSummary = summaryCandidate.trim();
|
|
393
|
+
if (!normalizedSummary) {
|
|
77
394
|
return { success: false, error: "Invalid input provided. Missing 'summary' parameter." };
|
|
78
395
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
396
|
+
const entityInput = Array.isArray(rawArgs.entities)
|
|
397
|
+
? rawArgs.entities
|
|
398
|
+
: Array.isArray(rawArgs.input?.entities)
|
|
399
|
+
? rawArgs.input.entities
|
|
400
|
+
: Array.isArray(rawArgs.event?.entities)
|
|
401
|
+
? rawArgs.event.entities
|
|
402
|
+
: [];
|
|
403
|
+
const entities = Array.isArray(entityInput)
|
|
404
|
+
? entityInput.map(item => {
|
|
405
|
+
if (typeof item === "string") {
|
|
406
|
+
return item.trim();
|
|
407
|
+
}
|
|
408
|
+
if (item && typeof item === "object") {
|
|
409
|
+
const value = (item.name || item.id || "");
|
|
410
|
+
return typeof value === "string" ? value.trim() : "";
|
|
411
|
+
}
|
|
412
|
+
return "";
|
|
413
|
+
}).filter(Boolean)
|
|
414
|
+
: [];
|
|
415
|
+
const entityTypesFromEntities = {};
|
|
416
|
+
if (Array.isArray(entityInput)) {
|
|
417
|
+
for (const item of entityInput) {
|
|
418
|
+
if (!item || typeof item !== "object") {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const entityObj = item;
|
|
422
|
+
const entityName = typeof entityObj.name === "string" && entityObj.name.trim()
|
|
423
|
+
? entityObj.name.trim()
|
|
424
|
+
: (typeof entityObj.id === "string" ? entityObj.id.trim() : "");
|
|
425
|
+
const entityType = typeof entityObj.type === "string" ? entityObj.type.trim() : "";
|
|
426
|
+
if (entityName && entityType) {
|
|
427
|
+
entityTypesFromEntities[entityName] = entityType;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const relationInput = Array.isArray(rawArgs.relations)
|
|
432
|
+
? rawArgs.relations
|
|
433
|
+
: Array.isArray(rawArgs.input?.relations)
|
|
434
|
+
? rawArgs.input.relations
|
|
435
|
+
: Array.isArray(rawArgs.event?.relations)
|
|
436
|
+
? rawArgs.event.relations
|
|
437
|
+
: [];
|
|
438
|
+
const relations = Array.isArray(relationInput)
|
|
439
|
+
? relationInput
|
|
440
|
+
.map(item => {
|
|
441
|
+
if (typeof item === "string") {
|
|
442
|
+
const [sourceRaw, typeRaw, targetRaw] = item.split("|");
|
|
443
|
+
const source = (sourceRaw || "").trim();
|
|
444
|
+
const target = (targetRaw || "").trim();
|
|
445
|
+
const type = (0, ontology_1.normalizeRelationType)((typeRaw || "related_to").trim(), graphSchema);
|
|
446
|
+
if (!source || !target)
|
|
447
|
+
return null;
|
|
448
|
+
return { source, target, type };
|
|
449
|
+
}
|
|
450
|
+
if (!item || typeof item !== "object")
|
|
451
|
+
return null;
|
|
452
|
+
const relation = item;
|
|
453
|
+
if (!relation.source || !relation.target)
|
|
454
|
+
return null;
|
|
455
|
+
return {
|
|
456
|
+
source: relation.source.trim(),
|
|
457
|
+
target: relation.target.trim(),
|
|
458
|
+
type: (0, ontology_1.normalizeRelationType)(relation.type || "related_to", graphSchema),
|
|
459
|
+
evidence_span: typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : undefined,
|
|
460
|
+
confidence: typeof relation.confidence === "number" ? Math.max(0, Math.min(1, relation.confidence)) : undefined,
|
|
461
|
+
};
|
|
462
|
+
})
|
|
463
|
+
.filter((item) => Boolean(item))
|
|
464
|
+
: [];
|
|
465
|
+
const outcomeValue = typeof rawArgs.outcome === "string"
|
|
466
|
+
? rawArgs.outcome
|
|
467
|
+
: typeof rawArgs.input?.outcome === "string"
|
|
468
|
+
? String(rawArgs.input.outcome)
|
|
469
|
+
: typeof rawArgs.event?.outcome === "string"
|
|
470
|
+
? String(rawArgs.event.outcome)
|
|
471
|
+
: "";
|
|
472
|
+
const causeValue = typeof rawArgs.cause === "string"
|
|
473
|
+
? rawArgs.cause
|
|
474
|
+
: typeof rawArgs.input?.cause === "string"
|
|
475
|
+
? String(rawArgs.input.cause)
|
|
476
|
+
: typeof rawArgs.event?.cause === "string"
|
|
477
|
+
? String(rawArgs.event.cause)
|
|
478
|
+
: "";
|
|
479
|
+
const processValue = typeof rawArgs.process === "string"
|
|
480
|
+
? rawArgs.process
|
|
481
|
+
: typeof rawArgs.input?.process === "string"
|
|
482
|
+
? String(rawArgs.input.process)
|
|
483
|
+
: typeof rawArgs.event?.process === "string"
|
|
484
|
+
? String(rawArgs.event.process)
|
|
485
|
+
: "";
|
|
486
|
+
const resultValue = typeof rawArgs.result === "string"
|
|
487
|
+
? rawArgs.result
|
|
488
|
+
: typeof rawArgs.input?.result === "string"
|
|
489
|
+
? String(rawArgs.input.result)
|
|
490
|
+
: typeof rawArgs.event?.result === "string"
|
|
491
|
+
? String(rawArgs.event.result)
|
|
492
|
+
: "";
|
|
493
|
+
const entityTypesInput = typeof rawArgs.entity_types === "object" && rawArgs.entity_types !== null
|
|
494
|
+
? rawArgs.entity_types
|
|
495
|
+
: typeof rawArgs.input?.entity_types === "object"
|
|
496
|
+
? rawArgs.input.entity_types
|
|
497
|
+
: typeof rawArgs.event?.entity_types === "object"
|
|
498
|
+
? rawArgs.event.entity_types
|
|
499
|
+
: {};
|
|
500
|
+
const entityTypes = {};
|
|
501
|
+
for (const [key, value] of Object.entries(entityTypesInput)) {
|
|
502
|
+
if (typeof value === "string") {
|
|
503
|
+
entityTypes[key.trim()] = value.trim();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
for (const [key, value] of Object.entries(entityTypesFromEntities)) {
|
|
507
|
+
if (!entityTypes[key] && value) {
|
|
508
|
+
entityTypes[key] = value;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const result = await deps.archiveStore.storeEvents([
|
|
512
|
+
{
|
|
513
|
+
event_type: "manual_event",
|
|
514
|
+
summary: normalizedSummary,
|
|
515
|
+
cause: causeValue.trim() || normalizedSummary,
|
|
516
|
+
process: processValue.trim() || normalizedSummary,
|
|
517
|
+
result: resultValue.trim() || outcomeValue.trim() || normalizedSummary,
|
|
518
|
+
entities,
|
|
519
|
+
relations,
|
|
520
|
+
entity_types: entityTypes,
|
|
521
|
+
outcome: outcomeValue,
|
|
522
|
+
session_id: "manual",
|
|
523
|
+
source_file: "ts_store_event",
|
|
524
|
+
confidence: 1,
|
|
525
|
+
source_event_id: `manual:${Date.now().toString(36)}`,
|
|
526
|
+
actor: "manual_tool",
|
|
527
|
+
},
|
|
528
|
+
]);
|
|
529
|
+
if (result.stored.length === 0) {
|
|
530
|
+
return { success: false, error: result.skipped[0]?.reason || "store_event_skipped" };
|
|
531
|
+
}
|
|
532
|
+
const storedId = result.stored[0].id;
|
|
533
|
+
if (deps.graphMemoryStore && entities.length > 0 && Object.keys(entityTypes).length > 0 && relations.length > 0) {
|
|
534
|
+
const graphSummary = normalizedSummary.toLowerCase().includes("entities:")
|
|
535
|
+
? normalizedSummary
|
|
536
|
+
: `${normalizedSummary} Entities: ${entities.join(", ")}.`;
|
|
537
|
+
const graphResult = await deps.graphMemoryStore.append({
|
|
538
|
+
sourceEventId: storedId,
|
|
539
|
+
sourceLayer: "archive_event",
|
|
540
|
+
archiveEventId: storedId,
|
|
541
|
+
sessionId: "manual",
|
|
542
|
+
sourceFile: "ts_store_event",
|
|
543
|
+
summary: graphSummary,
|
|
544
|
+
source_text_nav: {
|
|
545
|
+
layer: "archive_event",
|
|
546
|
+
session_id: "manual",
|
|
547
|
+
source_file: "ts_store_event",
|
|
548
|
+
source_memory_id: storedId,
|
|
549
|
+
source_event_id: storedId,
|
|
550
|
+
},
|
|
551
|
+
eventType: "manual_event",
|
|
552
|
+
entities,
|
|
553
|
+
entity_types: entityTypes,
|
|
554
|
+
relations,
|
|
555
|
+
gateSource: "manual",
|
|
556
|
+
confidence: 1,
|
|
557
|
+
sourceText: normalizedSummary,
|
|
558
|
+
});
|
|
559
|
+
if (!graphResult.success) {
|
|
560
|
+
deps.logger.info(`store_event graph_skip_reason=${graphResult.reason} source_event_id=${storedId}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return { success: true, data: { event_id: storedId } };
|
|
93
564
|
}
|
|
94
565
|
catch (error) {
|
|
95
566
|
return { success: false, error: String(error) };
|
|
@@ -100,36 +571,323 @@ function createTsEngine(deps) {
|
|
|
100
571
|
if (!entity) {
|
|
101
572
|
return { success: false, error: "Invalid input provided. Missing 'entity' parameter." };
|
|
102
573
|
}
|
|
103
|
-
const
|
|
104
|
-
|
|
574
|
+
const relFilter = typeof args.rel === "string" && args.rel.trim()
|
|
575
|
+
? (0, ontology_1.normalizeRelationType)(args.rel, graphSchema)
|
|
576
|
+
: "";
|
|
577
|
+
const direction = args.dir === "incoming" || args.dir === "outgoing" || args.dir === "both"
|
|
578
|
+
? args.dir
|
|
579
|
+
: "both";
|
|
580
|
+
const pathTo = typeof args.path_to === "string" && args.path_to.trim() ? args.path_to.trim() : "";
|
|
581
|
+
const maxDepth = Math.max(2, Math.min(4, typeof args.max_depth === "number" ? Math.floor(args.max_depth) : 3));
|
|
582
|
+
const graphMemoryPath = path.join(deps.memoryRoot, "graph", "memory.jsonl");
|
|
583
|
+
const projectionIndexPath = path.join(deps.memoryRoot, "wiki", ".projection_index.json");
|
|
105
584
|
const nodes = new Map();
|
|
106
585
|
const edges = [];
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
586
|
+
const pathAdjacency = new Map();
|
|
587
|
+
const relationTypeDistribution = new Map();
|
|
588
|
+
const edgeKeySet = new Set();
|
|
589
|
+
const allEdges = [];
|
|
590
|
+
function relationKeyOf(source, type, target) {
|
|
591
|
+
return `${source.trim().toLowerCase()}|${type.trim().toLowerCase()}|${target.trim().toLowerCase()}`;
|
|
592
|
+
}
|
|
593
|
+
function pushPathEdge(edge) {
|
|
594
|
+
const source = edge.source;
|
|
595
|
+
const target = edge.target;
|
|
596
|
+
if (!pathAdjacency.has(source)) {
|
|
597
|
+
pathAdjacency.set(source, []);
|
|
598
|
+
}
|
|
599
|
+
if (!pathAdjacency.has(target)) {
|
|
600
|
+
pathAdjacency.set(target, []);
|
|
601
|
+
}
|
|
602
|
+
if (direction === "incoming") {
|
|
603
|
+
pathAdjacency.get(target)?.push({ next: source, edge });
|
|
604
|
+
}
|
|
605
|
+
else if (direction === "outgoing") {
|
|
606
|
+
pathAdjacency.get(source)?.push({ next: target, edge });
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
pathAdjacency.get(source)?.push({ next: target, edge });
|
|
610
|
+
pathAdjacency.get(target)?.push({ next: source, edge });
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function normalizeStatus(value) {
|
|
614
|
+
const token = (value || "").trim().toLowerCase();
|
|
615
|
+
if (token === "pending" || token === "pending_conflict")
|
|
616
|
+
return "pending_conflict";
|
|
617
|
+
if (token === "superseded")
|
|
618
|
+
return "superseded";
|
|
619
|
+
if (token === "rejected")
|
|
620
|
+
return "rejected";
|
|
621
|
+
return "active";
|
|
622
|
+
}
|
|
623
|
+
const viewData = deps.graphMemoryStore?.exportGraphView();
|
|
624
|
+
if (viewData && Array.isArray(viewData.edges)) {
|
|
625
|
+
for (const edge of viewData.edges) {
|
|
626
|
+
const source = typeof edge.source === "string" ? edge.source.trim() : "";
|
|
627
|
+
const target = typeof edge.target === "string" ? edge.target.trim() : "";
|
|
628
|
+
const type = (0, ontology_1.normalizeRelationType)(typeof edge.type === "string" ? edge.type : "related_to", graphSchema);
|
|
629
|
+
if (!source || !target)
|
|
630
|
+
continue;
|
|
631
|
+
const relationKey = typeof edge.relation_key === "string" && edge.relation_key.trim()
|
|
632
|
+
? edge.relation_key.trim().toLowerCase()
|
|
633
|
+
: relationKeyOf(source, type, target);
|
|
634
|
+
allEdges.push({
|
|
635
|
+
source,
|
|
636
|
+
target,
|
|
637
|
+
type,
|
|
638
|
+
fact_status: normalizeStatus(typeof edge.status === "string" ? edge.status : "active"),
|
|
639
|
+
relation_key: relationKey,
|
|
640
|
+
source_event_id: typeof edge.source_event_id === "string" ? edge.source_event_id : undefined,
|
|
641
|
+
evidence_span: typeof edge.evidence_span === "string" ? edge.evidence_span : undefined,
|
|
642
|
+
confidence: typeof edge.confidence === "number" ? edge.confidence : undefined,
|
|
643
|
+
conflict_id: typeof edge.conflict_id === "string" ? edge.conflict_id : undefined,
|
|
644
|
+
evidence_ids: [],
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (allEdges.length === 0) {
|
|
649
|
+
const graphRecords = deps.graphMemoryStore
|
|
650
|
+
? deps.graphMemoryStore.loadAll()
|
|
651
|
+
: (fs.existsSync(graphMemoryPath) ? readJsonl(graphMemoryPath) : []);
|
|
652
|
+
for (const record of graphRecords) {
|
|
653
|
+
const sourceEventId = typeof record.source_event_id === "string" ? record.source_event_id.trim() : "";
|
|
654
|
+
const relations = Array.isArray(record.relations) ? record.relations : [];
|
|
655
|
+
for (const relationRaw of relations) {
|
|
656
|
+
if (typeof relationRaw !== "object" || relationRaw === null)
|
|
657
|
+
continue;
|
|
658
|
+
const relation = relationRaw;
|
|
659
|
+
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
660
|
+
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
661
|
+
const type = (0, ontology_1.normalizeRelationType)(typeof relation.type === "string" && relation.type.trim() ? relation.type.trim() : "related_to", graphSchema);
|
|
662
|
+
if (!source || !target)
|
|
663
|
+
continue;
|
|
664
|
+
allEdges.push({
|
|
665
|
+
source,
|
|
666
|
+
target,
|
|
667
|
+
type,
|
|
668
|
+
fact_status: "active",
|
|
669
|
+
relation_key: relationKeyOf(source, type, target),
|
|
670
|
+
source_event_id: sourceEventId || undefined,
|
|
671
|
+
evidence_span: typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : undefined,
|
|
672
|
+
confidence: typeof relation.confidence === "number" ? relation.confidence : undefined,
|
|
673
|
+
evidence_ids: [],
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const projectionIndex = parseJsonFile(projectionIndexPath);
|
|
679
|
+
const entityWikiPathByName = new Map();
|
|
680
|
+
const topicWikiPathByType = new Map();
|
|
681
|
+
const projectionEntities = Array.isArray(projectionIndex?.entities) ? projectionIndex.entities : [];
|
|
682
|
+
const projectionTopics = Array.isArray(projectionIndex?.topics) ? projectionIndex.topics : [];
|
|
683
|
+
for (const item of projectionEntities) {
|
|
684
|
+
if (!item || typeof item !== "object")
|
|
685
|
+
continue;
|
|
686
|
+
const row = item;
|
|
687
|
+
const name = typeof row.name === "string" ? row.name.trim() : "";
|
|
688
|
+
const wikiPath = typeof row.path === "string" ? row.path.trim() : "";
|
|
689
|
+
if (!name || !wikiPath)
|
|
690
|
+
continue;
|
|
691
|
+
entityWikiPathByName.set(name.toLowerCase(), wikiPath);
|
|
692
|
+
}
|
|
693
|
+
for (const item of projectionTopics) {
|
|
694
|
+
if (!item || typeof item !== "object")
|
|
695
|
+
continue;
|
|
696
|
+
const row = item;
|
|
697
|
+
const topic = typeof row.type === "string" ? row.type.trim() : "";
|
|
698
|
+
const wikiPath = typeof row.path === "string" ? row.path.trim() : "";
|
|
699
|
+
if (!topic || !wikiPath)
|
|
700
|
+
continue;
|
|
701
|
+
topicWikiPathByType.set(topic.toLowerCase(), wikiPath);
|
|
702
|
+
}
|
|
703
|
+
function statusAnchor(status) {
|
|
704
|
+
if (status === "active")
|
|
705
|
+
return "current-facts";
|
|
706
|
+
if (status === "pending_conflict")
|
|
707
|
+
return "disputed-facts";
|
|
708
|
+
return "history";
|
|
709
|
+
}
|
|
710
|
+
function buildEvidenceIdsForEdge(edge) {
|
|
711
|
+
const ids = [
|
|
712
|
+
`graph:relation:${edge.relation_key}`,
|
|
713
|
+
edge.source_event_id ? `graph:event:${edge.source_event_id}` : "",
|
|
714
|
+
edge.evidence_span ? `graph:evidence:${edge.source_event_id || edge.relation_key}` : "",
|
|
715
|
+
edge.conflict_id ? `graph:conflict:${edge.conflict_id}` : "",
|
|
716
|
+
];
|
|
717
|
+
const anchor = statusAnchor(edge.fact_status);
|
|
718
|
+
for (const name of [edge.source, edge.target]) {
|
|
719
|
+
const wikiPath = entityWikiPathByName.get(name.toLowerCase());
|
|
720
|
+
if (wikiPath) {
|
|
721
|
+
ids.push(`wiki:${wikiPath}#${anchor}`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const topicPath = topicWikiPathByType.get(edge.type.toLowerCase());
|
|
725
|
+
if (topicPath) {
|
|
726
|
+
ids.push(`wiki:${topicPath}#relations`);
|
|
727
|
+
}
|
|
728
|
+
return [...new Set(ids.filter(Boolean))];
|
|
729
|
+
}
|
|
730
|
+
let explicitMatched = false;
|
|
731
|
+
for (const edge of allEdges) {
|
|
732
|
+
if (relFilter && edge.type !== relFilter) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
const pathEligible = edge.fact_status === "active" || edge.fact_status === "pending_conflict";
|
|
736
|
+
if (pathEligible) {
|
|
737
|
+
pushPathEdge(edge);
|
|
738
|
+
}
|
|
739
|
+
const outgoingMatch = edge.source === entity;
|
|
740
|
+
const incomingMatch = edge.target === entity;
|
|
741
|
+
const directionMatched = direction === "both" ? (outgoingMatch || incomingMatch)
|
|
742
|
+
: direction === "outgoing" ? outgoingMatch
|
|
743
|
+
: incomingMatch;
|
|
744
|
+
if (!directionMatched) {
|
|
111
745
|
continue;
|
|
112
746
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
747
|
+
explicitMatched = true;
|
|
748
|
+
const edgeKey = `${edge.relation_key}|${edge.fact_status}|${edge.conflict_id || ""}`;
|
|
749
|
+
if (edgeKeySet.has(edgeKey)) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
edgeKeySet.add(edgeKey);
|
|
753
|
+
const enrichedEdge = {
|
|
754
|
+
...edge,
|
|
755
|
+
evidence_ids: buildEvidenceIdsForEdge(edge),
|
|
756
|
+
};
|
|
757
|
+
edges.push(enrichedEdge);
|
|
758
|
+
relationTypeDistribution.set(enrichedEdge.type, (relationTypeDistribution.get(enrichedEdge.type) || 0) + 1);
|
|
759
|
+
if (!nodes.has(enrichedEdge.source))
|
|
760
|
+
nodes.set(enrichedEdge.source, { id: enrichedEdge.source, type: "entity" });
|
|
761
|
+
if (!nodes.has(enrichedEdge.target))
|
|
762
|
+
nodes.set(enrichedEdge.target, { id: enrichedEdge.target, type: "entity" });
|
|
763
|
+
}
|
|
764
|
+
if (!explicitMatched) {
|
|
765
|
+
if (!nodes.has(entity)) {
|
|
766
|
+
nodes.set(entity, { id: entity, type: "entity" });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
let graphPath = [];
|
|
770
|
+
if (pathTo) {
|
|
771
|
+
const visited = new Set();
|
|
772
|
+
const queue = [
|
|
773
|
+
{ node: entity, depth: 0, pathEdges: [] },
|
|
774
|
+
];
|
|
775
|
+
while (queue.length > 0) {
|
|
776
|
+
const current = queue.shift();
|
|
777
|
+
if (!current)
|
|
778
|
+
break;
|
|
779
|
+
if (current.node === pathTo) {
|
|
780
|
+
graphPath = current.pathEdges;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
if (current.depth >= maxDepth) {
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
const visitKey = `${current.node}:${current.depth}`;
|
|
787
|
+
if (visited.has(visitKey)) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
visited.add(visitKey);
|
|
791
|
+
for (const next of pathAdjacency.get(current.node) || []) {
|
|
792
|
+
queue.push({
|
|
793
|
+
node: next.next,
|
|
794
|
+
depth: current.depth + 1,
|
|
795
|
+
pathEdges: [...current.pathEdges, next.edge],
|
|
796
|
+
});
|
|
116
797
|
}
|
|
117
798
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
799
|
+
}
|
|
800
|
+
const statusCounts = {
|
|
801
|
+
active: 0,
|
|
802
|
+
pending_conflict: 0,
|
|
803
|
+
superseded: 0,
|
|
804
|
+
rejected: 0,
|
|
805
|
+
};
|
|
806
|
+
const wikiRefSet = new Set();
|
|
807
|
+
if (entityWikiPathByName.has(entity.toLowerCase())) {
|
|
808
|
+
wikiRefSet.add(`wiki/${entityWikiPathByName.get(entity.toLowerCase())}`);
|
|
809
|
+
}
|
|
810
|
+
for (const edge of edges) {
|
|
811
|
+
statusCounts[edge.fact_status] += 1;
|
|
812
|
+
for (const name of [edge.source, edge.target]) {
|
|
813
|
+
const wikiPath = entityWikiPathByName.get(name.toLowerCase());
|
|
814
|
+
if (wikiPath) {
|
|
815
|
+
wikiRefSet.add(`wiki/${wikiPath}`);
|
|
121
816
|
}
|
|
122
817
|
}
|
|
818
|
+
const topicPath = topicWikiPathByType.get(edge.type.toLowerCase());
|
|
819
|
+
if (topicPath) {
|
|
820
|
+
wikiRefSet.add(`wiki/${topicPath}`);
|
|
821
|
+
}
|
|
123
822
|
}
|
|
823
|
+
const evidenceIds = [...new Set(edges.flatMap(edge => edge.evidence_ids).filter(Boolean))];
|
|
824
|
+
const conflictEdges = edges.filter(edge => edge.fact_status === "pending_conflict" || edge.fact_status === "rejected");
|
|
825
|
+
const pendingCount = conflictEdges.filter(edge => edge.fact_status === "pending_conflict").length;
|
|
826
|
+
const rejectedCount = conflictEdges.filter(edge => edge.fact_status === "rejected").length;
|
|
827
|
+
const conflictIds = [...new Set(conflictEdges.map(edge => edge.conflict_id).filter((item) => !!item))];
|
|
124
828
|
return {
|
|
125
829
|
success: true,
|
|
126
830
|
data: {
|
|
127
831
|
entity,
|
|
832
|
+
rel: relFilter || "",
|
|
833
|
+
dir: direction,
|
|
128
834
|
nodes: [...nodes.values()],
|
|
129
835
|
edges,
|
|
836
|
+
status_counts: statusCounts,
|
|
837
|
+
path_to: pathTo || "",
|
|
838
|
+
max_depth: maxDepth,
|
|
839
|
+
path: graphPath,
|
|
840
|
+
wiki_refs: [...wikiRefSet],
|
|
841
|
+
evidence_ids: evidenceIds,
|
|
842
|
+
conflict_hint: conflictEdges.length > 0
|
|
843
|
+
? {
|
|
844
|
+
pending_count: pendingCount,
|
|
845
|
+
rejected_count: rejectedCount,
|
|
846
|
+
conflict_ids: conflictIds,
|
|
847
|
+
suggestion: pendingCount > 0
|
|
848
|
+
? "Pending graph conflicts found. Call list_graph_conflicts first, then resolve_graph_conflict(accept/reject)."
|
|
849
|
+
: "Rejected candidate facts exist. Submit stronger evidence if you want to update the graph.",
|
|
850
|
+
}
|
|
851
|
+
: undefined,
|
|
852
|
+
relation_type_distribution: [...relationTypeDistribution.entries()].map(([type, count]) => ({ type, count })),
|
|
130
853
|
},
|
|
131
854
|
};
|
|
132
855
|
}
|
|
856
|
+
async function exportGraphView(args, _context) {
|
|
857
|
+
if (!deps.graphMemoryStore) {
|
|
858
|
+
return { success: false, error: "Graph memory store is not available." };
|
|
859
|
+
}
|
|
860
|
+
const writeSnapshot = args.write_snapshot !== false;
|
|
861
|
+
const view = deps.graphMemoryStore.exportGraphView();
|
|
862
|
+
const projection = writeSnapshot
|
|
863
|
+
? (0, wiki_projector_1.writeGraphViewProjection)({
|
|
864
|
+
memoryRoot: deps.memoryRoot,
|
|
865
|
+
view,
|
|
866
|
+
})
|
|
867
|
+
: null;
|
|
868
|
+
return {
|
|
869
|
+
success: true,
|
|
870
|
+
data: {
|
|
871
|
+
...view,
|
|
872
|
+
snapshot_written: writeSnapshot,
|
|
873
|
+
projection,
|
|
874
|
+
},
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
async function lintMemoryWiki(_args, _context) {
|
|
878
|
+
if (!deps.graphMemoryStore) {
|
|
879
|
+
return { success: false, error: "Graph memory store is not available." };
|
|
880
|
+
}
|
|
881
|
+
const graphView = deps.graphMemoryStore.exportGraphView();
|
|
882
|
+
const report = (0, wiki_linter_1.lintMemoryWiki)({
|
|
883
|
+
memoryRoot: deps.memoryRoot,
|
|
884
|
+
graphView,
|
|
885
|
+
});
|
|
886
|
+
return {
|
|
887
|
+
success: true,
|
|
888
|
+
data: report,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
133
891
|
async function deleteMemory(args, _context) {
|
|
134
892
|
const targetId = args.memory_id?.trim();
|
|
135
893
|
if (!targetId) {
|
|
@@ -216,35 +974,648 @@ function createTsEngine(deps) {
|
|
|
216
974
|
}
|
|
217
975
|
return { success: true, data: { deletedCount } };
|
|
218
976
|
}
|
|
977
|
+
async function backfillEmbeddings(args, _context) {
|
|
978
|
+
const layer = args.layer === "active" || args.layer === "archive" || args.layer === "all" ? args.layer : "all";
|
|
979
|
+
const rebuildMode = args.rebuild_mode === "vector_only" || args.rebuild_mode === "full"
|
|
980
|
+
? args.rebuild_mode
|
|
981
|
+
: "incremental";
|
|
982
|
+
const batchSize = typeof args.batch_size === "number" && Number.isFinite(args.batch_size) && args.batch_size > 0
|
|
983
|
+
? Math.min(500, Math.floor(args.batch_size))
|
|
984
|
+
: 100;
|
|
985
|
+
const maxRetries = typeof args.max_retries === "number" && Number.isFinite(args.max_retries) && args.max_retries >= 1
|
|
986
|
+
? Math.min(10, Math.floor(args.max_retries))
|
|
987
|
+
: 3;
|
|
988
|
+
const retryFailedOnly = args.retry_failed_only === true;
|
|
989
|
+
const forceRebuild = rebuildMode === "vector_only" || rebuildMode === "full";
|
|
990
|
+
const model = deps.embedding?.model || "";
|
|
991
|
+
const apiKey = deps.embedding?.apiKey || "";
|
|
992
|
+
const baseUrl = normalizeBaseUrl(deps.embedding?.baseURL || deps.embedding?.baseUrl);
|
|
993
|
+
if (!model || !apiKey || !baseUrl) {
|
|
994
|
+
return { success: false, error: "Embedding config missing for backfill tool." };
|
|
995
|
+
}
|
|
996
|
+
const statePath = path.join(deps.memoryRoot, ".vector_backfill_state.json");
|
|
997
|
+
const syncStatePath = path.join(deps.memoryRoot, ".sync_state.json");
|
|
998
|
+
const previousState = parseJsonFile(statePath) || {};
|
|
999
|
+
const failureCountState = (typeof previousState.failureCounts === "object" && previousState.failureCounts !== null)
|
|
1000
|
+
? previousState.failureCounts
|
|
1001
|
+
: {};
|
|
1002
|
+
let fullSyncResult = null;
|
|
1003
|
+
if (rebuildMode === "full") {
|
|
1004
|
+
try {
|
|
1005
|
+
fullSyncResult = await deps.sessionSync.syncMemory();
|
|
1006
|
+
}
|
|
1007
|
+
catch (error) {
|
|
1008
|
+
deps.logger.warn(`backfill_full_rebuild_sync_failed error=${error}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const { activePath, archivePath } = memoryFiles();
|
|
1012
|
+
const targetFiles = [];
|
|
1013
|
+
if (layer === "all" || layer === "active") {
|
|
1014
|
+
targetFiles.push({ layer: "active", filePath: activePath });
|
|
1015
|
+
}
|
|
1016
|
+
if (layer === "all" || layer === "archive") {
|
|
1017
|
+
targetFiles.push({ layer: "archive", filePath: archivePath });
|
|
1018
|
+
}
|
|
1019
|
+
const vectorJsonlPath = path.join(deps.memoryRoot, "vector", "lancedb_events.jsonl");
|
|
1020
|
+
const vectorJsonlRecords = readJsonl(vectorJsonlPath);
|
|
1021
|
+
const vectorSourceIndex = new Set();
|
|
1022
|
+
for (const row of vectorJsonlRecords) {
|
|
1023
|
+
const rowLayer = row.layer === "active" || row.layer === "archive" ? row.layer : "";
|
|
1024
|
+
const sourceMemoryId = typeof row.source_memory_id === "string" ? row.source_memory_id.trim() : "";
|
|
1025
|
+
if (!rowLayer || !sourceMemoryId)
|
|
1026
|
+
continue;
|
|
1027
|
+
vectorSourceIndex.add(`${rowLayer}|${sourceMemoryId}`);
|
|
1028
|
+
}
|
|
1029
|
+
const queue = [];
|
|
1030
|
+
const recordsByFile = new Map();
|
|
1031
|
+
for (const target of targetFiles) {
|
|
1032
|
+
const records = readJsonl(target.filePath);
|
|
1033
|
+
recordsByFile.set(target.filePath, records);
|
|
1034
|
+
for (let i = 0; i < records.length; i += 1) {
|
|
1035
|
+
const record = records[i];
|
|
1036
|
+
const id = typeof record.id === "string" ? record.id : "";
|
|
1037
|
+
if (!id) {
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
const status = typeof record.embedding_status === "string" ? record.embedding_status.trim() : "";
|
|
1041
|
+
const hasEmbedding = Array.isArray(record.embedding) && record.embedding.length > 0;
|
|
1042
|
+
const hasVectorRows = vectorSourceIndex.has(`${target.layer}|${id}`);
|
|
1043
|
+
if (forceRebuild) {
|
|
1044
|
+
queue.push({ layer: target.layer, filePath: target.filePath, index: i });
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (retryFailedOnly) {
|
|
1048
|
+
if (status !== "failed") {
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
else if ((status === "ok" || hasEmbedding) && hasVectorRows) {
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
const failCountRaw = failureCountState[id];
|
|
1056
|
+
const failCount = typeof failCountRaw === "number" ? failCountRaw : 0;
|
|
1057
|
+
if (failCount >= maxRetries && status === "failed") {
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
queue.push({ layer: target.layer, filePath: target.filePath, index: i });
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
const totalCandidates = queue.length;
|
|
1064
|
+
let success = 0;
|
|
1065
|
+
let failed = 0;
|
|
1066
|
+
let skipped = 0;
|
|
1067
|
+
let processed = 0;
|
|
1068
|
+
const failureCounts = {};
|
|
1069
|
+
for (const [key, value] of Object.entries(failureCountState)) {
|
|
1070
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1071
|
+
failureCounts[key] = value;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
for (let start = 0; start < queue.length; start += batchSize) {
|
|
1075
|
+
const batch = queue.slice(start, start + batchSize);
|
|
1076
|
+
for (const item of batch) {
|
|
1077
|
+
processed += 1;
|
|
1078
|
+
const records = recordsByFile.get(item.filePath) || [];
|
|
1079
|
+
const record = records[item.index];
|
|
1080
|
+
if (!record) {
|
|
1081
|
+
skipped += 1;
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const id = typeof record.id === "string" ? record.id : "";
|
|
1085
|
+
if (!id) {
|
|
1086
|
+
skipped += 1;
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
const text = buildVectorSourceText(record, item.layer);
|
|
1090
|
+
if (!text) {
|
|
1091
|
+
record.embedding_status = "failed";
|
|
1092
|
+
failed += 1;
|
|
1093
|
+
failureCounts[id] = (failureCounts[id] || 0) + 1;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
const chunkSize = deps.vectorChunking?.chunkSize ?? 600;
|
|
1097
|
+
const chunkOverlap = deps.vectorChunking?.chunkOverlap ?? 100;
|
|
1098
|
+
const evidenceMaxChunks = typeof deps.vectorChunking?.evidenceMaxChunks === "number"
|
|
1099
|
+
? Math.max(0, Math.min(8, Math.floor(deps.vectorChunking.evidenceMaxChunks)))
|
|
1100
|
+
: 2;
|
|
1101
|
+
let chunks = [];
|
|
1102
|
+
if (item.layer === "archive") {
|
|
1103
|
+
const summaryText = typeof record.summary === "string" ? record.summary.trim() : "";
|
|
1104
|
+
const sourceText = typeof record.source_text === "string" ? record.source_text.trim() : "";
|
|
1105
|
+
const summaryChunk = summaryText
|
|
1106
|
+
? [{
|
|
1107
|
+
index: 0,
|
|
1108
|
+
start: 0,
|
|
1109
|
+
end: summaryText.length,
|
|
1110
|
+
text: summaryText,
|
|
1111
|
+
source_field: "summary",
|
|
1112
|
+
}]
|
|
1113
|
+
: [];
|
|
1114
|
+
const evidenceChunks = sourceText
|
|
1115
|
+
? pickEvidenceChunks(splitTextChunks(sourceText, chunkSize, chunkOverlap), evidenceMaxChunks)
|
|
1116
|
+
: [];
|
|
1117
|
+
chunks = [
|
|
1118
|
+
...summaryChunk,
|
|
1119
|
+
...evidenceChunks.map((chunk, idx) => ({
|
|
1120
|
+
index: idx + summaryChunk.length,
|
|
1121
|
+
start: chunk.start,
|
|
1122
|
+
end: chunk.end,
|
|
1123
|
+
text: chunk.text,
|
|
1124
|
+
source_field: "evidence",
|
|
1125
|
+
})),
|
|
1126
|
+
];
|
|
1127
|
+
if (chunks.length === 0 && text) {
|
|
1128
|
+
chunks = splitTextChunks(text, chunkSize, chunkOverlap).map(chunk => ({
|
|
1129
|
+
...chunk,
|
|
1130
|
+
source_field: "summary",
|
|
1131
|
+
}));
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
chunks = splitTextChunks(text, chunkSize, chunkOverlap).map(chunk => ({
|
|
1136
|
+
...chunk,
|
|
1137
|
+
source_field: "summary",
|
|
1138
|
+
}));
|
|
1139
|
+
}
|
|
1140
|
+
if (chunks.length === 0) {
|
|
1141
|
+
record.embedding_status = "failed";
|
|
1142
|
+
failed += 1;
|
|
1143
|
+
failureCounts[id] = (failureCounts[id] || 0) + 1;
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
if (forceRebuild) {
|
|
1148
|
+
record.embedding_status = "pending";
|
|
1149
|
+
}
|
|
1150
|
+
await deps.vectorStore.deleteBySourceMemory({ layer: item.layer, sourceMemoryId: id });
|
|
1151
|
+
let chunkOk = 0;
|
|
1152
|
+
for (const chunk of chunks) {
|
|
1153
|
+
const embedding = await requestEmbedding({
|
|
1154
|
+
text: chunk.text,
|
|
1155
|
+
model,
|
|
1156
|
+
apiKey,
|
|
1157
|
+
baseUrl,
|
|
1158
|
+
dimensions: deps.embedding?.dimensions,
|
|
1159
|
+
timeoutMs: deps.embedding?.timeoutMs,
|
|
1160
|
+
maxRetries: deps.embedding?.maxRetries,
|
|
1161
|
+
});
|
|
1162
|
+
if (!embedding || embedding.length === 0) {
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
if (!record.embedding) {
|
|
1166
|
+
record.embedding = embedding;
|
|
1167
|
+
}
|
|
1168
|
+
await deps.vectorStore.upsert({
|
|
1169
|
+
id: `${id}_c${chunk.index}`,
|
|
1170
|
+
session_id: typeof record.session_id === "string" ? record.session_id : "unknown",
|
|
1171
|
+
event_type: typeof record.event_type === "string" ? record.event_type : (item.layer === "active" ? "message" : "insight"),
|
|
1172
|
+
summary: chunk.text,
|
|
1173
|
+
timestamp: typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString(),
|
|
1174
|
+
layer: item.layer,
|
|
1175
|
+
source_memory_id: id,
|
|
1176
|
+
source_memory_canonical_id: typeof record.canonical_id === "string" ? record.canonical_id : id,
|
|
1177
|
+
source_event_id: typeof record.source_event_id === "string" ? record.source_event_id : id,
|
|
1178
|
+
source_field: chunk.source_field,
|
|
1179
|
+
outcome: typeof record.outcome === "string" ? record.outcome : "",
|
|
1180
|
+
entities: Array.isArray(record.entities) ? record.entities.filter(v => typeof v === "string") : [],
|
|
1181
|
+
relations: Array.isArray(record.relations)
|
|
1182
|
+
? record.relations
|
|
1183
|
+
.map(v => {
|
|
1184
|
+
if (!v || typeof v !== "object")
|
|
1185
|
+
return null;
|
|
1186
|
+
const relation = v;
|
|
1187
|
+
const source = typeof relation.source === "string" ? relation.source : "";
|
|
1188
|
+
const target = typeof relation.target === "string" ? relation.target : "";
|
|
1189
|
+
const type = typeof relation.type === "string" ? relation.type : "related_to";
|
|
1190
|
+
if (!source || !target)
|
|
1191
|
+
return null;
|
|
1192
|
+
return { source, target, type };
|
|
1193
|
+
})
|
|
1194
|
+
.filter((v) => Boolean(v))
|
|
1195
|
+
: [],
|
|
1196
|
+
embedding,
|
|
1197
|
+
quality_score: typeof record.quality_score === "number" ? record.quality_score : 0.5,
|
|
1198
|
+
char_count: chunk.text.length,
|
|
1199
|
+
token_count: estimateTokenCount(chunk.text),
|
|
1200
|
+
chunk_index: chunk.index,
|
|
1201
|
+
chunk_total: chunks.length,
|
|
1202
|
+
chunk_start: chunk.start,
|
|
1203
|
+
chunk_end: chunk.end,
|
|
1204
|
+
});
|
|
1205
|
+
chunkOk += 1;
|
|
1206
|
+
}
|
|
1207
|
+
record.vector_chunks_total = chunks.length;
|
|
1208
|
+
record.vector_chunks_ok = chunkOk;
|
|
1209
|
+
record.embedding_status = chunkOk === chunks.length ? "ok" : "failed";
|
|
1210
|
+
if (!record.layer) {
|
|
1211
|
+
record.layer = item.layer;
|
|
1212
|
+
}
|
|
1213
|
+
if (typeof record.char_count !== "number") {
|
|
1214
|
+
record.char_count = text.length;
|
|
1215
|
+
}
|
|
1216
|
+
if (typeof record.token_count !== "number") {
|
|
1217
|
+
record.token_count = estimateTokenCount(text);
|
|
1218
|
+
}
|
|
1219
|
+
if (chunkOk === chunks.length) {
|
|
1220
|
+
success += 1;
|
|
1221
|
+
failureCounts[id] = 0;
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
failed += 1;
|
|
1225
|
+
failureCounts[id] = (failureCounts[id] || 0) + 1;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
catch (error) {
|
|
1229
|
+
record.embedding_status = "failed";
|
|
1230
|
+
failed += 1;
|
|
1231
|
+
failureCounts[id] = (failureCounts[id] || 0) + 1;
|
|
1232
|
+
deps.logger.warn(`backfill_embedding_failed id=${id} layer=${item.layer} error=${error}`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
deps.logger.info(`backfill_progress processed=${processed}/${totalCandidates} success=${success} failed=${failed} skipped=${skipped}`);
|
|
1236
|
+
}
|
|
1237
|
+
for (const target of targetFiles) {
|
|
1238
|
+
const records = recordsByFile.get(target.filePath);
|
|
1239
|
+
if (records) {
|
|
1240
|
+
writeJsonl(target.filePath, records);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
const summary = {
|
|
1244
|
+
runAt: new Date().toISOString(),
|
|
1245
|
+
layer,
|
|
1246
|
+
rebuild_mode: rebuildMode,
|
|
1247
|
+
candidates: totalCandidates,
|
|
1248
|
+
success,
|
|
1249
|
+
failed,
|
|
1250
|
+
skipped,
|
|
1251
|
+
batch_size: batchSize,
|
|
1252
|
+
max_retries: maxRetries,
|
|
1253
|
+
retry_failed_only: retryFailedOnly,
|
|
1254
|
+
full_sync_result: fullSyncResult,
|
|
1255
|
+
};
|
|
1256
|
+
upsertJsonFile(statePath, {
|
|
1257
|
+
version: "1",
|
|
1258
|
+
lastRun: summary,
|
|
1259
|
+
failureCounts,
|
|
1260
|
+
});
|
|
1261
|
+
upsertJsonFile(syncStatePath, {
|
|
1262
|
+
version: "2",
|
|
1263
|
+
lastVectorBackfill: {
|
|
1264
|
+
runAt: summary.runAt,
|
|
1265
|
+
success,
|
|
1266
|
+
failed,
|
|
1267
|
+
skipped,
|
|
1268
|
+
},
|
|
1269
|
+
});
|
|
1270
|
+
return { success: true, data: summary };
|
|
1271
|
+
}
|
|
219
1272
|
async function runDiagnostics(_args, _context) {
|
|
220
1273
|
const { activePath, archivePath } = memoryFiles();
|
|
1274
|
+
const activeRecords = readJsonl(activePath);
|
|
1275
|
+
const archiveRecords = readJsonl(archivePath);
|
|
1276
|
+
const activeVector = embeddingStats(activeRecords);
|
|
1277
|
+
const archiveVector = embeddingStats(archiveRecords);
|
|
1278
|
+
const vectorJsonlPath = path.join(deps.memoryRoot, "vector", "lancedb_events.jsonl");
|
|
1279
|
+
const vectorJsonlRecords = readJsonl(vectorJsonlPath);
|
|
1280
|
+
const activeVectorRecords = vectorJsonlRecords.filter(record => (record.layer === "active"));
|
|
1281
|
+
const archiveVectorRecords = vectorJsonlRecords.filter(record => (record.layer === "archive"));
|
|
1282
|
+
const lancedbDir = path.join(deps.memoryRoot, "vector", "lancedb");
|
|
1283
|
+
const lancedbExists = fs.existsSync(lancedbDir);
|
|
1284
|
+
let lancedbRecordCount = 0;
|
|
1285
|
+
if (lancedbExists) {
|
|
1286
|
+
try {
|
|
1287
|
+
const lancedbFiles = fs.readdirSync(lancedbDir).filter(f => f.endsWith(".lance") || f.endsWith(".manifest"));
|
|
1288
|
+
lancedbRecordCount = lancedbFiles.length > 0 ? -1 : 0;
|
|
1289
|
+
}
|
|
1290
|
+
catch {
|
|
1291
|
+
lancedbRecordCount = 0;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
const totalVectorRecords = vectorJsonlRecords.length > 0 ? vectorJsonlRecords.length : (lancedbRecordCount === -1 ? -1 : 0);
|
|
1295
|
+
const vectorStorageType = lancedbExists && lancedbRecordCount === -1 ? "lancedb" : (vectorJsonlRecords.length > 0 ? "jsonl" : "none");
|
|
1296
|
+
const syncState = parseJsonFile(path.join(deps.memoryRoot, ".sync_state.json"));
|
|
1297
|
+
const backfillState = parseJsonFile(path.join(deps.memoryRoot, ".vector_backfill_state.json"));
|
|
1298
|
+
const failureCounts = backfillState && typeof backfillState.failureCounts === "object" && backfillState.failureCounts !== null
|
|
1299
|
+
? backfillState.failureCounts
|
|
1300
|
+
: {};
|
|
1301
|
+
const pendingRetry = Object.values(failureCounts).filter(value => typeof value === "number" && Number.isFinite(value) && value > 0).length;
|
|
1302
|
+
const lastVectorBackfill = syncState && typeof syncState.lastVectorBackfill === "object" && syncState.lastVectorBackfill !== null
|
|
1303
|
+
? syncState.lastVectorBackfill
|
|
1304
|
+
: null;
|
|
1305
|
+
const embeddingConnectivity = await probeModelConnection({
|
|
1306
|
+
kind: "embedding",
|
|
1307
|
+
model: deps.embedding?.model || "",
|
|
1308
|
+
apiKey: deps.embedding?.apiKey || "",
|
|
1309
|
+
baseUrl: normalizeBaseUrl(deps.embedding?.baseURL || deps.embedding?.baseUrl),
|
|
1310
|
+
timeoutMs: deps.embedding?.timeoutMs,
|
|
1311
|
+
});
|
|
1312
|
+
const llmConnectivity = await probeModelConnection({
|
|
1313
|
+
kind: "llm",
|
|
1314
|
+
model: deps.llm?.model || "",
|
|
1315
|
+
apiKey: deps.llm?.apiKey || "",
|
|
1316
|
+
baseUrl: normalizeBaseUrl(deps.llm?.baseURL || deps.llm?.baseUrl),
|
|
1317
|
+
timeoutMs: 8000,
|
|
1318
|
+
});
|
|
1319
|
+
const rerankerConnectivity = await probeModelConnection({
|
|
1320
|
+
kind: "reranker",
|
|
1321
|
+
model: deps.reranker?.model || "",
|
|
1322
|
+
apiKey: deps.reranker?.apiKey || "",
|
|
1323
|
+
baseUrl: normalizeBaseUrl(deps.reranker?.baseURL || deps.reranker?.baseUrl),
|
|
1324
|
+
timeoutMs: 8000,
|
|
1325
|
+
});
|
|
221
1326
|
const checks = [
|
|
222
1327
|
{ name: "Engine mode", passed: true, message: "TS engine active" },
|
|
223
1328
|
{ name: "Active sessions store", passed: fs.existsSync(activePath), message: activePath },
|
|
224
1329
|
{ name: "Archive sessions store", passed: fs.existsSync(archivePath), message: archivePath },
|
|
225
1330
|
{ name: "Core rules store", passed: fs.existsSync(path.join(deps.memoryRoot, "CORTEX_RULES.md")), message: "CORTEX_RULES.md checked" },
|
|
1331
|
+
{ name: "Embedding model connectivity", passed: embeddingConnectivity.connected, message: embeddingConnectivity.error || "ok" },
|
|
1332
|
+
{ name: "LLM model connectivity", passed: llmConnectivity.connected, message: llmConnectivity.error || "ok" },
|
|
1333
|
+
{ name: "Reranker model connectivity", passed: rerankerConnectivity.connected, message: rerankerConnectivity.error || "ok" },
|
|
226
1334
|
];
|
|
1335
|
+
const qualityCheck = {
|
|
1336
|
+
active: { total: 0, valid: 0, invalid: 0, issues: [] },
|
|
1337
|
+
archive: { total: 0, valid: 0, invalid: 0, issues: [] },
|
|
1338
|
+
};
|
|
1339
|
+
if (fs.existsSync(activePath)) {
|
|
1340
|
+
const content = fs.readFileSync(activePath, "utf-8");
|
|
1341
|
+
const lines = content.split(/\r?\n/).filter(l => l.trim());
|
|
1342
|
+
qualityCheck.active.total = lines.length;
|
|
1343
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1344
|
+
const validation = (0, llm_output_validator_1.validateJsonlLine)(lines[i]);
|
|
1345
|
+
if (validation.valid) {
|
|
1346
|
+
qualityCheck.active.valid++;
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
qualityCheck.active.invalid++;
|
|
1350
|
+
if (qualityCheck.active.issues.length < 5) {
|
|
1351
|
+
qualityCheck.active.issues.push({ line: i + 1, errors: validation.errors });
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (fs.existsSync(archivePath)) {
|
|
1357
|
+
const content = fs.readFileSync(archivePath, "utf-8");
|
|
1358
|
+
const lines = content.split(/\r?\n/).filter(l => l.trim());
|
|
1359
|
+
qualityCheck.archive.total = lines.length;
|
|
1360
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1361
|
+
const validation = (0, llm_output_validator_1.validateJsonlLine)(lines[i]);
|
|
1362
|
+
if (validation.valid) {
|
|
1363
|
+
qualityCheck.archive.valid++;
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
qualityCheck.archive.invalid++;
|
|
1367
|
+
if (qualityCheck.archive.issues.length < 5) {
|
|
1368
|
+
qualityCheck.archive.issues.push({ line: i + 1, errors: validation.errors });
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const totalInvalid = qualityCheck.active.invalid + qualityCheck.archive.invalid;
|
|
1374
|
+
if (totalInvalid > 0) {
|
|
1375
|
+
checks.push({ name: "Data integrity", passed: false, message: `${totalInvalid} invalid records found` });
|
|
1376
|
+
}
|
|
1377
|
+
else {
|
|
1378
|
+
checks.push({ name: "Data integrity", passed: true, message: "All records valid" });
|
|
1379
|
+
}
|
|
1380
|
+
const graphMemoryPath = path.join(deps.memoryRoot, "graph", "memory.jsonl");
|
|
1381
|
+
const graphRecords = readJsonl(graphMemoryPath);
|
|
1382
|
+
function stringField(record, key) {
|
|
1383
|
+
const value = record[key];
|
|
1384
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1385
|
+
}
|
|
1386
|
+
const activeFieldIssues = {
|
|
1387
|
+
missing_id: 0,
|
|
1388
|
+
missing_timestamp: 0,
|
|
1389
|
+
missing_layer: 0,
|
|
1390
|
+
missing_text_payload: 0,
|
|
1391
|
+
};
|
|
1392
|
+
for (const record of activeRecords) {
|
|
1393
|
+
if (!stringField(record, "id"))
|
|
1394
|
+
activeFieldIssues.missing_id += 1;
|
|
1395
|
+
if (!stringField(record, "timestamp"))
|
|
1396
|
+
activeFieldIssues.missing_timestamp += 1;
|
|
1397
|
+
if (stringField(record, "layer") !== "active")
|
|
1398
|
+
activeFieldIssues.missing_layer += 1;
|
|
1399
|
+
const hasPayload = [
|
|
1400
|
+
stringField(record, "content"),
|
|
1401
|
+
stringField(record, "summary"),
|
|
1402
|
+
stringField(record, "text"),
|
|
1403
|
+
stringField(record, "message"),
|
|
1404
|
+
].some(Boolean);
|
|
1405
|
+
if (!hasPayload)
|
|
1406
|
+
activeFieldIssues.missing_text_payload += 1;
|
|
1407
|
+
}
|
|
1408
|
+
const archiveFieldIssues = {
|
|
1409
|
+
missing_id: 0,
|
|
1410
|
+
missing_timestamp: 0,
|
|
1411
|
+
missing_layer: 0,
|
|
1412
|
+
missing_summary: 0,
|
|
1413
|
+
missing_source_memory_id: 0,
|
|
1414
|
+
};
|
|
1415
|
+
for (const record of archiveRecords) {
|
|
1416
|
+
if (!stringField(record, "id"))
|
|
1417
|
+
archiveFieldIssues.missing_id += 1;
|
|
1418
|
+
if (!stringField(record, "timestamp"))
|
|
1419
|
+
archiveFieldIssues.missing_timestamp += 1;
|
|
1420
|
+
if (stringField(record, "layer") !== "archive")
|
|
1421
|
+
archiveFieldIssues.missing_layer += 1;
|
|
1422
|
+
if (!stringField(record, "summary"))
|
|
1423
|
+
archiveFieldIssues.missing_summary += 1;
|
|
1424
|
+
if (!stringField(record, "source_memory_id") && !stringField(record, "canonical_id")) {
|
|
1425
|
+
archiveFieldIssues.missing_source_memory_id += 1;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
const vectorFieldIssues = {
|
|
1429
|
+
missing_id: 0,
|
|
1430
|
+
missing_layer: 0,
|
|
1431
|
+
missing_summary: 0,
|
|
1432
|
+
missing_source_memory_id: 0,
|
|
1433
|
+
missing_embedding: 0,
|
|
1434
|
+
};
|
|
1435
|
+
for (const record of vectorJsonlRecords) {
|
|
1436
|
+
if (!stringField(record, "id"))
|
|
1437
|
+
vectorFieldIssues.missing_id += 1;
|
|
1438
|
+
const layer = stringField(record, "layer");
|
|
1439
|
+
if (layer !== "active" && layer !== "archive")
|
|
1440
|
+
vectorFieldIssues.missing_layer += 1;
|
|
1441
|
+
if (!stringField(record, "summary"))
|
|
1442
|
+
vectorFieldIssues.missing_summary += 1;
|
|
1443
|
+
if (!stringField(record, "source_memory_id"))
|
|
1444
|
+
vectorFieldIssues.missing_source_memory_id += 1;
|
|
1445
|
+
const embedding = record.embedding;
|
|
1446
|
+
const vector = record.vector;
|
|
1447
|
+
const hasEmbedding = Array.isArray(embedding) ? embedding.length > 0 : (Array.isArray(vector) ? vector.length > 0 : false);
|
|
1448
|
+
if (!hasEmbedding)
|
|
1449
|
+
vectorFieldIssues.missing_embedding += 1;
|
|
1450
|
+
}
|
|
1451
|
+
const graphFieldIssues = {
|
|
1452
|
+
missing_id: 0,
|
|
1453
|
+
missing_event_ref: 0,
|
|
1454
|
+
missing_layer: 0,
|
|
1455
|
+
malformed_relations: 0,
|
|
1456
|
+
};
|
|
1457
|
+
for (const record of graphRecords) {
|
|
1458
|
+
if (!stringField(record, "id"))
|
|
1459
|
+
graphFieldIssues.missing_id += 1;
|
|
1460
|
+
if (!stringField(record, "source_event_id") && !stringField(record, "archive_event_id")) {
|
|
1461
|
+
graphFieldIssues.missing_event_ref += 1;
|
|
1462
|
+
}
|
|
1463
|
+
const layer = stringField(record, "source_layer");
|
|
1464
|
+
if (layer !== "archive_event" && layer !== "active_only")
|
|
1465
|
+
graphFieldIssues.missing_layer += 1;
|
|
1466
|
+
if (!Array.isArray(record.relations))
|
|
1467
|
+
graphFieldIssues.malformed_relations += 1;
|
|
1468
|
+
}
|
|
1469
|
+
const archiveIdSet = new Set(archiveRecords
|
|
1470
|
+
.map(record => stringField(record, "id"))
|
|
1471
|
+
.filter(Boolean));
|
|
1472
|
+
const vectorLinkedToArchive = vectorJsonlRecords.filter(record => {
|
|
1473
|
+
const sourceMemoryId = stringField(record, "source_memory_id");
|
|
1474
|
+
return !!sourceMemoryId && archiveIdSet.has(sourceMemoryId);
|
|
1475
|
+
}).length;
|
|
1476
|
+
const graphLinkedToArchive = graphRecords.filter(record => {
|
|
1477
|
+
const sourceLayer = stringField(record, "source_layer");
|
|
1478
|
+
const refId = stringField(record, "source_event_id") || stringField(record, "archive_event_id");
|
|
1479
|
+
return sourceLayer === "archive_event" && !!refId && archiveIdSet.has(refId);
|
|
1480
|
+
}).length;
|
|
1481
|
+
const graphConflictStats = deps.graphMemoryStore
|
|
1482
|
+
? deps.graphMemoryStore.getConflictStats()
|
|
1483
|
+
: { pending: 0, accepted: 0, rejected: 0 };
|
|
1484
|
+
const schemaIssueTotal = Object.values(activeFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1485
|
+
+ Object.values(archiveFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1486
|
+
+ Object.values(vectorFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1487
|
+
+ Object.values(graphFieldIssues).reduce((sum, n) => sum + n, 0);
|
|
1488
|
+
checks.push({
|
|
1489
|
+
name: "Field mapping alignment",
|
|
1490
|
+
passed: schemaIssueTotal === 0,
|
|
1491
|
+
message: schemaIssueTotal === 0
|
|
1492
|
+
? "active/archive/vector/graph field mapping aligned with read path"
|
|
1493
|
+
: `${schemaIssueTotal} field mapping issues detected across four memory stores`,
|
|
1494
|
+
});
|
|
227
1495
|
return {
|
|
228
1496
|
success: true,
|
|
229
1497
|
data: {
|
|
230
1498
|
status: "ok",
|
|
1499
|
+
prompt_versions: PROMPT_VERSIONS,
|
|
231
1500
|
checks,
|
|
232
|
-
|
|
1501
|
+
layers: {
|
|
1502
|
+
active: {
|
|
1503
|
+
records: activeRecords.length,
|
|
1504
|
+
path: activePath,
|
|
1505
|
+
},
|
|
1506
|
+
archive: {
|
|
1507
|
+
records: archiveRecords.length,
|
|
1508
|
+
path: archivePath,
|
|
1509
|
+
},
|
|
1510
|
+
vector: {
|
|
1511
|
+
storage_type: vectorStorageType,
|
|
1512
|
+
lancedb_exists: lancedbExists,
|
|
1513
|
+
active_coverage: activeVector.coverage,
|
|
1514
|
+
archive_coverage: archiveVector.coverage,
|
|
1515
|
+
active_unembedded: activeVector.pending + activeVector.failed,
|
|
1516
|
+
archive_unembedded: archiveVector.pending + archiveVector.failed,
|
|
1517
|
+
chunking: {
|
|
1518
|
+
chunk_size: deps.vectorChunking?.chunkSize ?? 600,
|
|
1519
|
+
chunk_overlap: deps.vectorChunking?.chunkOverlap ?? 100,
|
|
1520
|
+
},
|
|
1521
|
+
vector_jsonl_records: vectorJsonlRecords.length,
|
|
1522
|
+
vector_jsonl_by_layer: {
|
|
1523
|
+
active: activeVectorRecords.length,
|
|
1524
|
+
archive: archiveVectorRecords.length,
|
|
1525
|
+
},
|
|
1526
|
+
total_vector_records: totalVectorRecords,
|
|
1527
|
+
last_backfill_summary: lastVectorBackfill,
|
|
1528
|
+
backfill_state: {
|
|
1529
|
+
pending_retry_records: pendingRetry,
|
|
1530
|
+
has_state_file: fs.existsSync(path.join(deps.memoryRoot, ".vector_backfill_state.json")),
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
graph_rules: {
|
|
1534
|
+
graph_mutation_log_exists: fs.existsSync(path.join(deps.memoryRoot, "graph", "mutation_log.jsonl")),
|
|
1535
|
+
graph_conflicts: graphConflictStats,
|
|
1536
|
+
rules_exists: fs.existsSync(path.join(deps.memoryRoot, "CORTEX_RULES.md")),
|
|
1537
|
+
},
|
|
1538
|
+
},
|
|
1539
|
+
model_connectivity: {
|
|
1540
|
+
embedding: embeddingConnectivity,
|
|
1541
|
+
llm: llmConnectivity,
|
|
1542
|
+
reranker: rerankerConnectivity,
|
|
1543
|
+
},
|
|
1544
|
+
quality_check: qualityCheck,
|
|
1545
|
+
schema_alignment: {
|
|
1546
|
+
active: {
|
|
1547
|
+
records: activeRecords.length,
|
|
1548
|
+
issues: activeFieldIssues,
|
|
1549
|
+
},
|
|
1550
|
+
archive: {
|
|
1551
|
+
records: archiveRecords.length,
|
|
1552
|
+
issues: archiveFieldIssues,
|
|
1553
|
+
},
|
|
1554
|
+
vector: {
|
|
1555
|
+
records: vectorJsonlRecords.length,
|
|
1556
|
+
issues: vectorFieldIssues,
|
|
1557
|
+
linked_to_archive: vectorLinkedToArchive,
|
|
1558
|
+
},
|
|
1559
|
+
graph: {
|
|
1560
|
+
records: graphRecords.length,
|
|
1561
|
+
issues: graphFieldIssues,
|
|
1562
|
+
linked_archive_events: graphLinkedToArchive,
|
|
1563
|
+
},
|
|
1564
|
+
cross_layer_links: {
|
|
1565
|
+
archive_records: archiveIdSet.size,
|
|
1566
|
+
vector_archive_link_coverage: archiveIdSet.size > 0
|
|
1567
|
+
? Number((vectorLinkedToArchive / archiveIdSet.size).toFixed(4))
|
|
1568
|
+
: 0,
|
|
1569
|
+
graph_archive_link_coverage: archiveIdSet.size > 0
|
|
1570
|
+
? Number((graphLinkedToArchive / archiveIdSet.size).toFixed(4))
|
|
1571
|
+
: 0,
|
|
1572
|
+
},
|
|
1573
|
+
},
|
|
1574
|
+
recommendations: [
|
|
1575
|
+
...(totalInvalid > 0 ? ["Run repair-memory --fix to clean invalid records"] : []),
|
|
1576
|
+
...(schemaIssueTotal > 0 ? ["Run diagnostics output schema_alignment and repair missing cross-layer fields"] : []),
|
|
1577
|
+
],
|
|
233
1578
|
},
|
|
234
1579
|
};
|
|
235
1580
|
}
|
|
236
1581
|
async function searchMemory(args, context) {
|
|
237
|
-
|
|
1582
|
+
const argsRecord = asRecord(args) || {};
|
|
1583
|
+
const argsInput = asRecord(argsRecord.input);
|
|
1584
|
+
const queryCandidate = [
|
|
1585
|
+
typeof args.query === "string" ? args.query : "",
|
|
1586
|
+
typeof argsRecord.query === "string" ? String(argsRecord.query) : "",
|
|
1587
|
+
typeof argsRecord.q === "string" ? String(argsRecord.q) : "",
|
|
1588
|
+
typeof argsRecord.keyword === "string" ? String(argsRecord.keyword) : "",
|
|
1589
|
+
typeof argsInput?.query === "string" ? String(argsInput.query) : "",
|
|
1590
|
+
typeof argsInput?.q === "string" ? String(argsInput.q) : "",
|
|
1591
|
+
].find(item => item.trim());
|
|
1592
|
+
const query = queryCandidate ? queryCandidate.trim() : "";
|
|
1593
|
+
if (!query) {
|
|
238
1594
|
return {
|
|
239
1595
|
success: false,
|
|
240
1596
|
error: "Invalid input provided. Missing 'query' parameter.",
|
|
241
1597
|
};
|
|
242
1598
|
}
|
|
1599
|
+
const topKRaw = [
|
|
1600
|
+
typeof args.top_k === "number" ? args.top_k : undefined,
|
|
1601
|
+
typeof argsRecord.top_k === "number" ? Number(argsRecord.top_k) : undefined,
|
|
1602
|
+
typeof argsRecord.topK === "number" ? Number(argsRecord.topK) : undefined,
|
|
1603
|
+
typeof argsInput?.top_k === "number" ? Number(argsInput.top_k) : undefined,
|
|
1604
|
+
typeof argsInput?.topK === "number" ? Number(argsInput.topK) : undefined,
|
|
1605
|
+
].find(value => typeof value === "number" && Number.isFinite(value));
|
|
243
1606
|
const result = await deps.readStore.searchMemory({
|
|
244
|
-
query
|
|
245
|
-
topK: typeof
|
|
1607
|
+
query,
|
|
1608
|
+
topK: typeof topKRaw === "number" && topKRaw > 0 ? Math.floor(topKRaw) : 3,
|
|
246
1609
|
});
|
|
247
|
-
return {
|
|
1610
|
+
return {
|
|
1611
|
+
success: true,
|
|
1612
|
+
data: {
|
|
1613
|
+
results: result.results,
|
|
1614
|
+
vector_semantic_results: result.semantic_results,
|
|
1615
|
+
vector_keyword_results: result.keyword_results,
|
|
1616
|
+
vector_search_strategy: result.strategy,
|
|
1617
|
+
},
|
|
1618
|
+
};
|
|
248
1619
|
}
|
|
249
1620
|
async function getHotContext(args, _context) {
|
|
250
1621
|
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 20;
|
|
@@ -252,10 +1623,19 @@ function createTsEngine(deps) {
|
|
|
252
1623
|
return { success: true, data: result.context };
|
|
253
1624
|
}
|
|
254
1625
|
async function getAutoContext(args, context) {
|
|
255
|
-
const
|
|
1626
|
+
const argsRecord = asRecord(args) || {};
|
|
1627
|
+
const argsInput = asRecord(argsRecord.input);
|
|
1628
|
+
const includeHotRaw = [
|
|
1629
|
+
typeof args.include_hot === "boolean" ? args.include_hot : undefined,
|
|
1630
|
+
typeof argsRecord.include_hot === "boolean" ? Boolean(argsRecord.include_hot) : undefined,
|
|
1631
|
+
typeof argsRecord.includeHot === "boolean" ? Boolean(argsRecord.includeHot) : undefined,
|
|
1632
|
+
typeof argsInput?.include_hot === "boolean" ? Boolean(argsInput.include_hot) : undefined,
|
|
1633
|
+
typeof argsInput?.includeHot === "boolean" ? Boolean(argsInput.includeHot) : undefined,
|
|
1634
|
+
].find(value => typeof value === "boolean");
|
|
1635
|
+
const sessionId = deps.resolveSessionId((context || {}));
|
|
256
1636
|
const cached = deps.getCachedAutoSearch(sessionId);
|
|
257
1637
|
const result = await deps.readStore.getAutoContext({
|
|
258
|
-
includeHot:
|
|
1638
|
+
includeHot: includeHotRaw !== false,
|
|
259
1639
|
sessionId,
|
|
260
1640
|
cachedAutoSearch: cached ?? undefined,
|
|
261
1641
|
});
|
|
@@ -270,6 +1650,49 @@ function createTsEngine(deps) {
|
|
|
270
1650
|
}
|
|
271
1651
|
return { success: true, data: result };
|
|
272
1652
|
}
|
|
1653
|
+
async function listGraphConflicts(args, _context) {
|
|
1654
|
+
if (!deps.graphMemoryStore) {
|
|
1655
|
+
return { success: false, error: "Graph memory store is not available." };
|
|
1656
|
+
}
|
|
1657
|
+
const status = args.status === "pending" || args.status === "accepted" || args.status === "rejected" || args.status === "all"
|
|
1658
|
+
? args.status
|
|
1659
|
+
: "pending";
|
|
1660
|
+
const limit = typeof args.limit === "number" && Number.isFinite(args.limit) && args.limit > 0
|
|
1661
|
+
? Math.min(500, Math.floor(args.limit))
|
|
1662
|
+
: 50;
|
|
1663
|
+
const items = deps.graphMemoryStore.listConflicts({ status, limit });
|
|
1664
|
+
return { success: true, data: { status, count: items.length, items } };
|
|
1665
|
+
}
|
|
1666
|
+
async function resolveGraphConflict(args, _context) {
|
|
1667
|
+
if (!deps.graphMemoryStore) {
|
|
1668
|
+
return { success: false, error: "Graph memory store is not available." };
|
|
1669
|
+
}
|
|
1670
|
+
const conflictId = (args.conflict_id || "").trim();
|
|
1671
|
+
const action = args.action === "accept" || args.action === "reject" ? args.action : null;
|
|
1672
|
+
if (!conflictId) {
|
|
1673
|
+
return { success: false, error: "Invalid input provided. Missing 'conflict_id' parameter." };
|
|
1674
|
+
}
|
|
1675
|
+
if (!action) {
|
|
1676
|
+
return { success: false, error: "Invalid input provided. 'action' must be accept or reject." };
|
|
1677
|
+
}
|
|
1678
|
+
const note = typeof args.note === "string" ? args.note.trim() : undefined;
|
|
1679
|
+
const result = await deps.graphMemoryStore.resolveConflict({
|
|
1680
|
+
conflictId,
|
|
1681
|
+
action,
|
|
1682
|
+
note,
|
|
1683
|
+
});
|
|
1684
|
+
if (!result.success) {
|
|
1685
|
+
return { success: false, error: result.reason || "resolve_graph_conflict_failed" };
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
success: true,
|
|
1689
|
+
data: {
|
|
1690
|
+
conflict_id: conflictId,
|
|
1691
|
+
action,
|
|
1692
|
+
applied_record_id: result.appliedRecordId,
|
|
1693
|
+
},
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
273
1696
|
async function syncMemory(_args, _context) {
|
|
274
1697
|
try {
|
|
275
1698
|
const result = await deps.sessionSync.syncMemory();
|
|
@@ -305,12 +1728,15 @@ function createTsEngine(deps) {
|
|
|
305
1728
|
const sessionId = deps.resolveSessionId(context, payload);
|
|
306
1729
|
const syncRecordsRaw = payloadObj?.sync_records;
|
|
307
1730
|
const syncRecords = typeof syncRecordsRaw === "boolean" ? syncRecordsRaw : deps.defaultAutoSync;
|
|
1731
|
+
const bufferedMessages = sessionMessageBuffer.get(sessionId) || [];
|
|
308
1732
|
try {
|
|
309
1733
|
const result = await deps.sessionEnd.onSessionEnd({
|
|
310
1734
|
sessionId,
|
|
311
1735
|
syncRecords,
|
|
1736
|
+
messages: bufferedMessages,
|
|
312
1737
|
});
|
|
313
1738
|
deps.logger.info(`TS session_end completed for ${sessionId}, events=${result.events_generated}`);
|
|
1739
|
+
sessionMessageBuffer.delete(sessionId);
|
|
314
1740
|
}
|
|
315
1741
|
catch (error) {
|
|
316
1742
|
deps.logger.warn(`TS session_end failed for ${sessionId}: ${error}`);
|
|
@@ -323,26 +1749,11 @@ function createTsEngine(deps) {
|
|
|
323
1749
|
}
|
|
324
1750
|
const { text, role, source } = normalized;
|
|
325
1751
|
const sessionId = deps.resolveSessionId(context, payload);
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
text,
|
|
329
|
-
role,
|
|
330
|
-
source,
|
|
331
|
-
sessionId,
|
|
332
|
-
});
|
|
333
|
-
if (writeResult.status === "ok") {
|
|
334
|
-
deps.logger.info(`TS write stored ${role} message for session ${sessionId}`);
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
deps.logger.debug(`TS write skipped for session ${sessionId}: ${writeResult.reason || "unknown"}`);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
catch (error) {
|
|
341
|
-
deps.logger.warn(`TS write failed for session ${sessionId}: ${error}`);
|
|
342
|
-
}
|
|
1752
|
+
pushSessionMessage(sessionId, { role, text });
|
|
1753
|
+
deps.logger.debug(`TS buffered ${role} message for session ${sessionId} source=${source}`);
|
|
343
1754
|
if (role === "user" && text.length > 5) {
|
|
344
1755
|
try {
|
|
345
|
-
const searchResult = await deps.readStore.searchMemory({ query: text, topK: 3 });
|
|
1756
|
+
const searchResult = await deps.readStore.searchMemory({ query: text, topK: 3, mode: "lightweight" });
|
|
346
1757
|
if (searchResult.results.length > 0) {
|
|
347
1758
|
deps.setSessionAutoSearchCache(sessionId, text, searchResult.results);
|
|
348
1759
|
deps.logger.info(`TS auto-search cached ${searchResult.results.length} results for context`);
|
|
@@ -375,12 +1786,17 @@ function createTsEngine(deps) {
|
|
|
375
1786
|
getAutoContext,
|
|
376
1787
|
storeEvent,
|
|
377
1788
|
queryGraph,
|
|
1789
|
+
exportGraphView,
|
|
1790
|
+
lintMemoryWiki,
|
|
1791
|
+
listGraphConflicts,
|
|
1792
|
+
resolveGraphConflict,
|
|
378
1793
|
reflectMemory,
|
|
379
1794
|
syncMemory,
|
|
380
1795
|
promoteMemory,
|
|
381
1796
|
deleteMemory,
|
|
382
1797
|
updateMemory,
|
|
383
1798
|
cleanupMemories,
|
|
1799
|
+
backfillEmbeddings,
|
|
384
1800
|
runDiagnostics,
|
|
385
1801
|
onMessage,
|
|
386
1802
|
onSessionEnd,
|