openclaw-cortex-memory 0.1.0-Alpha.2 → 0.1.0-Alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -203
- package/SKILL.md +71 -268
- package/dist/index.d.ts +88 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +859 -1189
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +362 -14
- 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 +2 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +126 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +1172 -44
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +12 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +103 -0
- package/dist/src/graph/ontology.d.ts.map +1 -0
- package/dist/src/graph/ontology.js +564 -0
- package/dist/src/graph/ontology.js.map +1 -0
- package/dist/src/quality/llm_output_validator.d.ts +48 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +404 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts +7 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +358 -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 +128 -0
- package/dist/src/store/archive_store.d.ts.map +1 -0
- package/dist/src/store/archive_store.js +481 -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 +44 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +168 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +86 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1681 -25
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +44 -0
- package/dist/src/store/vector_store.d.ts.map +1 -0
- package/dist/src/store/vector_store.js +201 -0
- package/dist/src/store/vector_store.js.map +1 -0
- package/dist/src/store/write_store.d.ts +52 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +245 -3
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +100 -2
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +673 -22
- 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/openclaw.plugin.json +362 -14
- package/package.json +23 -6
- package/scripts/cli.js +19 -14
- package/scripts/uninstall.js +22 -5
- package/index.ts +0 -2092
- package/scripts/install.js +0 -27
|
@@ -36,7 +36,40 @@ 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 llm_output_validator_1 = require("../quality/llm_output_validator");
|
|
41
|
+
const PROMPT_VERSIONS = {
|
|
42
|
+
write_gate: "write-gate.v1.2.0",
|
|
43
|
+
session_end_write: "session-end-write.v1.2.0",
|
|
44
|
+
read_fusion: "read-fusion.v1.2.0",
|
|
45
|
+
};
|
|
39
46
|
function createTsEngine(deps) {
|
|
47
|
+
const graphSchema = (0, ontology_1.loadGraphSchema)(deps.projectRoot);
|
|
48
|
+
const sessionMessageBuffer = new Map();
|
|
49
|
+
const maxMessagesPerSession = 500;
|
|
50
|
+
const maxBufferedSessions = 500;
|
|
51
|
+
function pushSessionMessage(sessionId, message) {
|
|
52
|
+
const current = sessionMessageBuffer.get(sessionId) || [];
|
|
53
|
+
current.push({
|
|
54
|
+
id: `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
55
|
+
session_id: sessionId,
|
|
56
|
+
role: message.role,
|
|
57
|
+
content: message.text,
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
});
|
|
60
|
+
if (current.length > maxMessagesPerSession) {
|
|
61
|
+
sessionMessageBuffer.set(sessionId, current.slice(current.length - maxMessagesPerSession));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
sessionMessageBuffer.set(sessionId, current);
|
|
65
|
+
}
|
|
66
|
+
if (sessionMessageBuffer.size > maxBufferedSessions) {
|
|
67
|
+
const first = sessionMessageBuffer.keys().next().value;
|
|
68
|
+
if (first) {
|
|
69
|
+
sessionMessageBuffer.delete(first);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
40
73
|
function asRecord(value) {
|
|
41
74
|
if (typeof value === "object" && value !== null) {
|
|
42
75
|
return value;
|
|
@@ -71,25 +104,454 @@ function createTsEngine(deps) {
|
|
|
71
104
|
archivePath: path.join(deps.memoryRoot, "sessions", "archive", "sessions.jsonl"),
|
|
72
105
|
};
|
|
73
106
|
}
|
|
107
|
+
function parseJsonFile(filePath) {
|
|
108
|
+
try {
|
|
109
|
+
if (!fs.existsSync(filePath)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const raw = fs.readFileSync(filePath, "utf-8").trim();
|
|
113
|
+
if (!raw) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return JSON.parse(raw);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function embeddingStats(records) {
|
|
123
|
+
let ok = 0;
|
|
124
|
+
let failed = 0;
|
|
125
|
+
let pending = 0;
|
|
126
|
+
for (const record of records) {
|
|
127
|
+
const explicit = typeof record.embedding_status === "string" ? record.embedding_status.trim() : "";
|
|
128
|
+
const hasEmbedding = Array.isArray(record.embedding) && record.embedding.length > 0;
|
|
129
|
+
if (explicit === "ok" || hasEmbedding) {
|
|
130
|
+
ok += 1;
|
|
131
|
+
}
|
|
132
|
+
else if (explicit === "failed") {
|
|
133
|
+
failed += 1;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
pending += 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const total = records.length;
|
|
140
|
+
const coverage = total > 0 ? Number((ok / total).toFixed(4)) : 0;
|
|
141
|
+
return { total, ok, failed, pending, coverage };
|
|
142
|
+
}
|
|
143
|
+
function normalizeBaseUrl(value) {
|
|
144
|
+
if (!value)
|
|
145
|
+
return "";
|
|
146
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
147
|
+
}
|
|
148
|
+
function estimateTokenCount(text) {
|
|
149
|
+
const parts = text
|
|
150
|
+
.split(/[\s,.;:!?,。;:!?、()()[\]{}"'`~]+/)
|
|
151
|
+
.map(part => part.trim())
|
|
152
|
+
.filter(Boolean);
|
|
153
|
+
return parts.length;
|
|
154
|
+
}
|
|
155
|
+
function buildVectorSourceText(record, layer) {
|
|
156
|
+
if (layer === "active") {
|
|
157
|
+
const content = typeof record.content === "string" && record.content.trim()
|
|
158
|
+
? record.content.trim()
|
|
159
|
+
: (typeof record.text === "string" ? record.text.trim() : "");
|
|
160
|
+
return content;
|
|
161
|
+
}
|
|
162
|
+
const summary = typeof record.summary === "string" ? record.summary.trim() : "";
|
|
163
|
+
const eventType = typeof record.event_type === "string" ? record.event_type.trim() : "insight";
|
|
164
|
+
const outcome = typeof record.outcome === "string" ? record.outcome.trim() : "";
|
|
165
|
+
const sourceFile = typeof record.source_file === "string" ? record.source_file.trim() : "";
|
|
166
|
+
const actor = typeof record.actor === "string" ? record.actor.trim() : "";
|
|
167
|
+
const entities = Array.isArray(record.entities)
|
|
168
|
+
? record.entities.filter(v => typeof v === "string").map(v => String(v).trim()).filter(Boolean)
|
|
169
|
+
: [];
|
|
170
|
+
const relations = Array.isArray(record.relations)
|
|
171
|
+
? record.relations
|
|
172
|
+
.map(v => {
|
|
173
|
+
if (!v || typeof v !== "object")
|
|
174
|
+
return "";
|
|
175
|
+
const relation = v;
|
|
176
|
+
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
177
|
+
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
178
|
+
const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
|
|
179
|
+
if (!source || !target)
|
|
180
|
+
return "";
|
|
181
|
+
return `${source} -[${type}]-> ${target}`;
|
|
182
|
+
})
|
|
183
|
+
.filter(Boolean)
|
|
184
|
+
: [];
|
|
185
|
+
const lines = [
|
|
186
|
+
`event_type: ${eventType}`,
|
|
187
|
+
`summary: ${summary}`,
|
|
188
|
+
`outcome: ${outcome}`,
|
|
189
|
+
`entities: ${entities.join(", ")}`,
|
|
190
|
+
`source_file: ${sourceFile}`,
|
|
191
|
+
`actor: ${actor}`,
|
|
192
|
+
relations.length > 0 ? `relations: ${relations.join(" ; ")}` : "",
|
|
193
|
+
].filter(Boolean);
|
|
194
|
+
return lines.join("\n").trim();
|
|
195
|
+
}
|
|
196
|
+
function splitTextChunks(text, chunkSize, chunkOverlap) {
|
|
197
|
+
const normalizedSize = Number.isFinite(chunkSize) && chunkSize >= 200 ? Math.floor(chunkSize) : 600;
|
|
198
|
+
const normalizedOverlap = Number.isFinite(chunkOverlap) && chunkOverlap >= 0
|
|
199
|
+
? Math.floor(chunkOverlap)
|
|
200
|
+
: 100;
|
|
201
|
+
const overlap = Math.min(normalizedOverlap, Math.max(0, normalizedSize - 50));
|
|
202
|
+
const output = [];
|
|
203
|
+
let cursor = 0;
|
|
204
|
+
let index = 0;
|
|
205
|
+
const punctuationSet = new Set(["。", "!", "?", ".", "!", "?", "\n", ";", ";"]);
|
|
206
|
+
while (cursor < text.length) {
|
|
207
|
+
const rawEnd = Math.min(text.length, cursor + normalizedSize);
|
|
208
|
+
let end = rawEnd;
|
|
209
|
+
if (rawEnd < text.length) {
|
|
210
|
+
const backwardStart = Math.max(cursor + Math.floor(normalizedSize * 0.45), cursor + 1);
|
|
211
|
+
let found = -1;
|
|
212
|
+
for (let i = rawEnd - 1; i >= backwardStart; i -= 1) {
|
|
213
|
+
if (punctuationSet.has(text[i])) {
|
|
214
|
+
found = i + 1;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (found < 0) {
|
|
219
|
+
const forwardEnd = Math.min(text.length, rawEnd + Math.floor(normalizedSize * 0.2));
|
|
220
|
+
for (let i = rawEnd; i < forwardEnd; i += 1) {
|
|
221
|
+
if (punctuationSet.has(text[i])) {
|
|
222
|
+
found = i + 1;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (found > cursor) {
|
|
228
|
+
end = found;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (end <= cursor) {
|
|
232
|
+
end = Math.min(text.length, cursor + normalizedSize);
|
|
233
|
+
}
|
|
234
|
+
const chunkText = text.slice(cursor, end).trim();
|
|
235
|
+
if (chunkText) {
|
|
236
|
+
output.push({ index, start: cursor, end, text: chunkText });
|
|
237
|
+
index += 1;
|
|
238
|
+
}
|
|
239
|
+
if (end >= text.length) {
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
const nextCursor = Math.max(cursor + 1, end - overlap);
|
|
243
|
+
cursor = nextCursor <= cursor ? end : nextCursor;
|
|
244
|
+
}
|
|
245
|
+
return output;
|
|
246
|
+
}
|
|
247
|
+
function upsertJsonFile(filePath, patch) {
|
|
248
|
+
const current = parseJsonFile(filePath) || {};
|
|
249
|
+
const next = { ...current, ...patch };
|
|
250
|
+
const dir = path.dirname(filePath);
|
|
251
|
+
if (!fs.existsSync(dir)) {
|
|
252
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
253
|
+
}
|
|
254
|
+
fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf-8");
|
|
255
|
+
}
|
|
256
|
+
async function probeModelConnection(args) {
|
|
257
|
+
const defaultTimeoutMs = args.kind === "llm" ? 30000 : 15000;
|
|
258
|
+
const timeoutMs = typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs) && args.timeoutMs >= 1000
|
|
259
|
+
? Math.floor(args.timeoutMs)
|
|
260
|
+
: defaultTimeoutMs;
|
|
261
|
+
if (!args.model || !args.apiKey || !args.baseUrl) {
|
|
262
|
+
return {
|
|
263
|
+
configured: false,
|
|
264
|
+
connected: false,
|
|
265
|
+
model: args.model || "",
|
|
266
|
+
base_url: args.baseUrl || "",
|
|
267
|
+
error: "not_configured",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
let endpoint = args.baseUrl;
|
|
271
|
+
let payload = {};
|
|
272
|
+
if (args.kind === "embedding") {
|
|
273
|
+
endpoint = args.baseUrl.endsWith("/embeddings") ? args.baseUrl : `${args.baseUrl}/embeddings`;
|
|
274
|
+
payload = {
|
|
275
|
+
model: args.model,
|
|
276
|
+
input: "diagnostics connectivity probe",
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
else if (args.kind === "llm") {
|
|
280
|
+
endpoint = args.baseUrl.endsWith("/chat/completions") ? args.baseUrl : `${args.baseUrl}/chat/completions`;
|
|
281
|
+
payload = {
|
|
282
|
+
model: args.model,
|
|
283
|
+
messages: [{ role: "user", content: "ping" }],
|
|
284
|
+
max_tokens: 4,
|
|
285
|
+
temperature: 0,
|
|
286
|
+
stream: false,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
endpoint = args.baseUrl.endsWith("/rerank") ? args.baseUrl : `${args.baseUrl}/rerank`;
|
|
291
|
+
payload = {
|
|
292
|
+
model: args.model,
|
|
293
|
+
query: "diagnostics",
|
|
294
|
+
documents: ["diagnostics connectivity probe"],
|
|
295
|
+
top_n: 1,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
let lastError = "unknown_error";
|
|
299
|
+
const maxAttempts = args.kind === "llm" ? 3 : 1;
|
|
300
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
301
|
+
const controller = new AbortController();
|
|
302
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
303
|
+
try {
|
|
304
|
+
const response = await fetch(endpoint, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers: {
|
|
307
|
+
accept: "application/json",
|
|
308
|
+
"content-type": "application/json",
|
|
309
|
+
authorization: `Bearer ${args.apiKey}`,
|
|
310
|
+
},
|
|
311
|
+
body: JSON.stringify(payload),
|
|
312
|
+
signal: controller.signal,
|
|
313
|
+
});
|
|
314
|
+
clearTimeout(timeoutId);
|
|
315
|
+
if (response.ok) {
|
|
316
|
+
return {
|
|
317
|
+
configured: true,
|
|
318
|
+
connected: true,
|
|
319
|
+
model: args.model,
|
|
320
|
+
base_url: args.baseUrl,
|
|
321
|
+
error: "",
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
let details = "";
|
|
325
|
+
try {
|
|
326
|
+
const text = (await response.text()).trim();
|
|
327
|
+
if (text) {
|
|
328
|
+
details = text.slice(0, 180);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
details = "";
|
|
333
|
+
}
|
|
334
|
+
lastError = details ? `http_${response.status}:${details}` : `http_${response.status}`;
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
338
|
+
if (error?.name === "AbortError" || /aborted/i.test(raw)) {
|
|
339
|
+
lastError = `timeout_${timeoutMs}ms`;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
lastError = raw;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
clearTimeout(timeoutId);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
configured: true,
|
|
351
|
+
connected: false,
|
|
352
|
+
model: args.model,
|
|
353
|
+
base_url: args.baseUrl,
|
|
354
|
+
error: lastError,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async function requestEmbedding(args) {
|
|
358
|
+
const endpoint = args.baseUrl.endsWith("/embeddings") ? args.baseUrl : `${args.baseUrl}/embeddings`;
|
|
359
|
+
const body = {
|
|
360
|
+
input: args.text,
|
|
361
|
+
model: args.model,
|
|
362
|
+
};
|
|
363
|
+
if (typeof args.dimensions === "number" && Number.isFinite(args.dimensions) && args.dimensions > 0) {
|
|
364
|
+
body.dimensions = args.dimensions;
|
|
365
|
+
}
|
|
366
|
+
const timeoutMs = typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs) && args.timeoutMs >= 1000
|
|
367
|
+
? Math.floor(args.timeoutMs)
|
|
368
|
+
: 20000;
|
|
369
|
+
const maxRetries = typeof args.maxRetries === "number" && Number.isFinite(args.maxRetries) && args.maxRetries >= 1
|
|
370
|
+
? Math.min(8, Math.floor(args.maxRetries))
|
|
371
|
+
: 4;
|
|
372
|
+
let lastError = null;
|
|
373
|
+
for (let attempt = 0; attempt < maxRetries; attempt += 1) {
|
|
374
|
+
const controller = new AbortController();
|
|
375
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
376
|
+
try {
|
|
377
|
+
const response = await fetch(endpoint, {
|
|
378
|
+
method: "POST",
|
|
379
|
+
headers: {
|
|
380
|
+
"content-type": "application/json",
|
|
381
|
+
authorization: `Bearer ${args.apiKey}`,
|
|
382
|
+
},
|
|
383
|
+
body: JSON.stringify(body),
|
|
384
|
+
signal: controller.signal,
|
|
385
|
+
});
|
|
386
|
+
clearTimeout(timeoutId);
|
|
387
|
+
if (!response.ok) {
|
|
388
|
+
lastError = new Error(`embedding_http_${response.status}`);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const json = await response.json();
|
|
392
|
+
const embedding = json?.data?.[0]?.embedding;
|
|
393
|
+
if (Array.isArray(embedding) && embedding.length > 0) {
|
|
394
|
+
return embedding.filter(item => Number.isFinite(item));
|
|
395
|
+
}
|
|
396
|
+
lastError = new Error("embedding_empty");
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
clearTimeout(timeoutId);
|
|
400
|
+
lastError = error;
|
|
401
|
+
}
|
|
402
|
+
if (attempt < maxRetries - 1) {
|
|
403
|
+
await new Promise(resolve => setTimeout(resolve, 300 * Math.pow(2, attempt)));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError || "embedding_failed"));
|
|
407
|
+
}
|
|
74
408
|
async function storeEvent(args, _context) {
|
|
75
409
|
try {
|
|
76
|
-
|
|
410
|
+
const rawArgs = args;
|
|
411
|
+
const summaryCandidate = typeof rawArgs?.summary === "string"
|
|
412
|
+
? rawArgs.summary
|
|
413
|
+
: typeof rawArgs?.input?.summary === "string"
|
|
414
|
+
? String(rawArgs.input.summary)
|
|
415
|
+
: typeof rawArgs?.event?.summary === "string"
|
|
416
|
+
? String(rawArgs.event.summary)
|
|
417
|
+
: "";
|
|
418
|
+
const normalizedSummary = summaryCandidate.trim();
|
|
419
|
+
if (!normalizedSummary) {
|
|
77
420
|
return { success: false, error: "Invalid input provided. Missing 'summary' parameter." };
|
|
78
421
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
422
|
+
const entityInput = Array.isArray(rawArgs.entities)
|
|
423
|
+
? rawArgs.entities
|
|
424
|
+
: Array.isArray(rawArgs.input?.entities)
|
|
425
|
+
? rawArgs.input.entities
|
|
426
|
+
: Array.isArray(rawArgs.event?.entities)
|
|
427
|
+
? rawArgs.event.entities
|
|
428
|
+
: [];
|
|
429
|
+
const entities = Array.isArray(entityInput)
|
|
430
|
+
? entityInput.map(item => {
|
|
431
|
+
if (typeof item === "string") {
|
|
432
|
+
return item.trim();
|
|
433
|
+
}
|
|
434
|
+
if (item && typeof item === "object") {
|
|
435
|
+
const value = (item.name || item.id || "");
|
|
436
|
+
return typeof value === "string" ? value.trim() : "";
|
|
437
|
+
}
|
|
438
|
+
return "";
|
|
439
|
+
}).filter(Boolean)
|
|
440
|
+
: [];
|
|
441
|
+
const entityTypesFromEntities = {};
|
|
442
|
+
if (Array.isArray(entityInput)) {
|
|
443
|
+
for (const item of entityInput) {
|
|
444
|
+
if (!item || typeof item !== "object") {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const entityObj = item;
|
|
448
|
+
const entityName = typeof entityObj.name === "string" && entityObj.name.trim()
|
|
449
|
+
? entityObj.name.trim()
|
|
450
|
+
: (typeof entityObj.id === "string" ? entityObj.id.trim() : "");
|
|
451
|
+
const entityType = typeof entityObj.type === "string" ? entityObj.type.trim() : "";
|
|
452
|
+
if (entityName && entityType) {
|
|
453
|
+
entityTypesFromEntities[entityName] = entityType;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const relationInput = Array.isArray(rawArgs.relations)
|
|
458
|
+
? rawArgs.relations
|
|
459
|
+
: Array.isArray(rawArgs.input?.relations)
|
|
460
|
+
? rawArgs.input.relations
|
|
461
|
+
: Array.isArray(rawArgs.event?.relations)
|
|
462
|
+
? rawArgs.event.relations
|
|
463
|
+
: [];
|
|
464
|
+
const relations = Array.isArray(relationInput)
|
|
465
|
+
? relationInput
|
|
466
|
+
.map(item => {
|
|
467
|
+
if (typeof item === "string") {
|
|
468
|
+
const [sourceRaw, typeRaw, targetRaw] = item.split("|");
|
|
469
|
+
const source = (sourceRaw || "").trim();
|
|
470
|
+
const target = (targetRaw || "").trim();
|
|
471
|
+
const type = (0, ontology_1.normalizeRelationType)((typeRaw || "related_to").trim(), graphSchema);
|
|
472
|
+
if (!source || !target)
|
|
473
|
+
return null;
|
|
474
|
+
return { source, target, type };
|
|
475
|
+
}
|
|
476
|
+
if (!item || typeof item !== "object")
|
|
477
|
+
return null;
|
|
478
|
+
const relation = item;
|
|
479
|
+
if (!relation.source || !relation.target)
|
|
480
|
+
return null;
|
|
481
|
+
return {
|
|
482
|
+
source: relation.source.trim(),
|
|
483
|
+
target: relation.target.trim(),
|
|
484
|
+
type: (0, ontology_1.normalizeRelationType)(relation.type || "related_to", graphSchema),
|
|
485
|
+
evidence_span: typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : undefined,
|
|
486
|
+
confidence: typeof relation.confidence === "number" ? Math.max(0, Math.min(1, relation.confidence)) : undefined,
|
|
487
|
+
};
|
|
488
|
+
})
|
|
489
|
+
.filter((item) => Boolean(item))
|
|
490
|
+
: [];
|
|
491
|
+
const outcomeValue = typeof rawArgs.outcome === "string"
|
|
492
|
+
? rawArgs.outcome
|
|
493
|
+
: typeof rawArgs.input?.outcome === "string"
|
|
494
|
+
? String(rawArgs.input.outcome)
|
|
495
|
+
: typeof rawArgs.event?.outcome === "string"
|
|
496
|
+
? String(rawArgs.event.outcome)
|
|
497
|
+
: "";
|
|
498
|
+
const entityTypesInput = typeof rawArgs.entity_types === "object" && rawArgs.entity_types !== null
|
|
499
|
+
? rawArgs.entity_types
|
|
500
|
+
: typeof rawArgs.input?.entity_types === "object"
|
|
501
|
+
? rawArgs.input.entity_types
|
|
502
|
+
: typeof rawArgs.event?.entity_types === "object"
|
|
503
|
+
? rawArgs.event.entity_types
|
|
504
|
+
: {};
|
|
505
|
+
const entityTypes = {};
|
|
506
|
+
for (const [key, value] of Object.entries(entityTypesInput)) {
|
|
507
|
+
if (typeof value === "string") {
|
|
508
|
+
entityTypes[key.trim()] = value.trim();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
for (const [key, value] of Object.entries(entityTypesFromEntities)) {
|
|
512
|
+
if (!entityTypes[key] && value) {
|
|
513
|
+
entityTypes[key] = value;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const result = await deps.archiveStore.storeEvents([
|
|
517
|
+
{
|
|
518
|
+
event_type: "manual_event",
|
|
519
|
+
summary: normalizedSummary,
|
|
520
|
+
entities,
|
|
521
|
+
relations,
|
|
522
|
+
entity_types: entityTypes,
|
|
523
|
+
outcome: outcomeValue,
|
|
524
|
+
session_id: "manual",
|
|
525
|
+
source_file: "ts_store_event",
|
|
526
|
+
confidence: 1,
|
|
527
|
+
source_event_id: `manual:${Date.now().toString(36)}`,
|
|
528
|
+
actor: "manual_tool",
|
|
529
|
+
},
|
|
530
|
+
]);
|
|
531
|
+
if (result.stored.length === 0) {
|
|
532
|
+
return { success: false, error: result.skipped[0]?.reason || "store_event_skipped" };
|
|
533
|
+
}
|
|
534
|
+
const storedId = result.stored[0].id;
|
|
535
|
+
if (deps.graphMemoryStore && entities.length > 0 && Object.keys(entityTypes).length > 0 && relations.length > 0) {
|
|
536
|
+
const graphResult = await deps.graphMemoryStore.append({
|
|
537
|
+
sourceEventId: storedId,
|
|
538
|
+
sourceLayer: "archive_event",
|
|
539
|
+
archiveEventId: storedId,
|
|
540
|
+
sessionId: "manual",
|
|
541
|
+
sourceFile: "ts_store_event",
|
|
542
|
+
eventType: "manual_event",
|
|
543
|
+
entities,
|
|
544
|
+
entity_types: entityTypes,
|
|
545
|
+
relations,
|
|
546
|
+
gateSource: "manual",
|
|
547
|
+
confidence: 1,
|
|
548
|
+
sourceText: normalizedSummary,
|
|
549
|
+
});
|
|
550
|
+
if (!graphResult.success) {
|
|
551
|
+
deps.logger.info(`store_event graph_skip_reason=${graphResult.reason} source_event_id=${storedId}`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return { success: true, data: { event_id: storedId } };
|
|
93
555
|
}
|
|
94
556
|
catch (error) {
|
|
95
557
|
return { success: false, error: String(error) };
|
|
@@ -100,13 +562,95 @@ function createTsEngine(deps) {
|
|
|
100
562
|
if (!entity) {
|
|
101
563
|
return { success: false, error: "Invalid input provided. Missing 'entity' parameter." };
|
|
102
564
|
}
|
|
103
|
-
const
|
|
104
|
-
|
|
565
|
+
const relFilter = typeof args.rel === "string" && args.rel.trim()
|
|
566
|
+
? (0, ontology_1.normalizeRelationType)(args.rel, graphSchema)
|
|
567
|
+
: "";
|
|
568
|
+
const direction = args.dir === "incoming" || args.dir === "outgoing" || args.dir === "both"
|
|
569
|
+
? args.dir
|
|
570
|
+
: "both";
|
|
571
|
+
const pathTo = typeof args.path_to === "string" && args.path_to.trim() ? args.path_to.trim() : "";
|
|
572
|
+
const maxDepth = Math.max(2, Math.min(4, typeof args.max_depth === "number" ? Math.floor(args.max_depth) : 3));
|
|
573
|
+
const graphMemoryPath = path.join(deps.memoryRoot, "graph", "memory.jsonl");
|
|
105
574
|
const nodes = new Map();
|
|
106
575
|
const edges = [];
|
|
107
|
-
|
|
576
|
+
const adjacency = new Map();
|
|
577
|
+
const pathAdjacency = new Map();
|
|
578
|
+
const relationTypeDistribution = new Map();
|
|
579
|
+
const edgeKeySet = new Set();
|
|
580
|
+
function pushEdge(source, target, type) {
|
|
581
|
+
const key = `${source}|${type}|${target}`;
|
|
582
|
+
if (edgeKeySet.has(key)) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
edgeKeySet.add(key);
|
|
586
|
+
edges.push({ source, target, type });
|
|
587
|
+
relationTypeDistribution.set(type, (relationTypeDistribution.get(type) || 0) + 1);
|
|
588
|
+
if (!adjacency.has(source)) {
|
|
589
|
+
adjacency.set(source, []);
|
|
590
|
+
}
|
|
591
|
+
adjacency.get(source)?.push({ next: target, edge: { source, target, type } });
|
|
592
|
+
if (!adjacency.has(target)) {
|
|
593
|
+
adjacency.set(target, []);
|
|
594
|
+
}
|
|
595
|
+
adjacency.get(target)?.push({ next: source, edge: { source, target, type } });
|
|
596
|
+
}
|
|
597
|
+
function pushPathEdge(source, target, type) {
|
|
598
|
+
if (!pathAdjacency.has(source)) {
|
|
599
|
+
pathAdjacency.set(source, []);
|
|
600
|
+
}
|
|
601
|
+
if (!pathAdjacency.has(target)) {
|
|
602
|
+
pathAdjacency.set(target, []);
|
|
603
|
+
}
|
|
604
|
+
if (direction === "incoming") {
|
|
605
|
+
pathAdjacency.get(target)?.push({ next: source, edge: { source, target, type } });
|
|
606
|
+
}
|
|
607
|
+
else if (direction === "outgoing") {
|
|
608
|
+
pathAdjacency.get(source)?.push({ next: target, edge: { source, target, type } });
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
pathAdjacency.get(source)?.push({ next: target, edge: { source, target, type } });
|
|
612
|
+
pathAdjacency.get(target)?.push({ next: source, edge: { source, target, type } });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const graphRecords = fs.existsSync(graphMemoryPath) ? readJsonl(graphMemoryPath) : [];
|
|
616
|
+
for (const record of graphRecords) {
|
|
108
617
|
const entities = Array.isArray(record.entities) ? record.entities : [];
|
|
109
|
-
const named = entities.map(e => (typeof e === "string" ? e.trim() : "")).filter(Boolean);
|
|
618
|
+
const named = entities.map((e) => (typeof e === "string" ? e.trim() : "")).filter(Boolean);
|
|
619
|
+
const relations = Array.isArray(record.relations) ? record.relations : [];
|
|
620
|
+
let explicitMatched = false;
|
|
621
|
+
for (const relationRaw of relations) {
|
|
622
|
+
if (typeof relationRaw !== "object" || relationRaw === null) {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
const relation = relationRaw;
|
|
626
|
+
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
627
|
+
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
628
|
+
const type = (0, ontology_1.normalizeRelationType)(typeof relation.type === "string" && relation.type.trim() ? relation.type.trim() : "related_to", graphSchema);
|
|
629
|
+
if (!source || !target) {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (relFilter && type !== relFilter) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
pushPathEdge(source, target, type);
|
|
636
|
+
const outgoingMatch = source === entity;
|
|
637
|
+
const incomingMatch = target === entity;
|
|
638
|
+
const directionMatched = direction === "both" ? (outgoingMatch || incomingMatch)
|
|
639
|
+
: direction === "outgoing" ? outgoingMatch
|
|
640
|
+
: incomingMatch;
|
|
641
|
+
if (!directionMatched) {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
explicitMatched = true;
|
|
645
|
+
if (!nodes.has(source))
|
|
646
|
+
nodes.set(source, { id: source, type: "entity" });
|
|
647
|
+
if (!nodes.has(target))
|
|
648
|
+
nodes.set(target, { id: target, type: "entity" });
|
|
649
|
+
pushEdge(source, target, type);
|
|
650
|
+
}
|
|
651
|
+
if (explicitMatched) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
110
654
|
if (!named.includes(entity)) {
|
|
111
655
|
continue;
|
|
112
656
|
}
|
|
@@ -117,7 +661,40 @@ function createTsEngine(deps) {
|
|
|
117
661
|
}
|
|
118
662
|
for (const name of named) {
|
|
119
663
|
if (name !== entity) {
|
|
120
|
-
|
|
664
|
+
if (!relFilter || relFilter === "co_occurrence") {
|
|
665
|
+
pushEdge(entity, name, "co_occurrence");
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
let graphPath = [];
|
|
671
|
+
if (pathTo) {
|
|
672
|
+
const visited = new Set();
|
|
673
|
+
const queue = [
|
|
674
|
+
{ node: entity, depth: 0, pathEdges: [] },
|
|
675
|
+
];
|
|
676
|
+
while (queue.length > 0) {
|
|
677
|
+
const current = queue.shift();
|
|
678
|
+
if (!current)
|
|
679
|
+
break;
|
|
680
|
+
if (current.node === pathTo) {
|
|
681
|
+
graphPath = current.pathEdges;
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
if (current.depth >= maxDepth) {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
const visitKey = `${current.node}:${current.depth}`;
|
|
688
|
+
if (visited.has(visitKey)) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
visited.add(visitKey);
|
|
692
|
+
for (const next of pathAdjacency.get(current.node) || []) {
|
|
693
|
+
queue.push({
|
|
694
|
+
node: next.next,
|
|
695
|
+
depth: current.depth + 1,
|
|
696
|
+
pathEdges: [...current.pathEdges, next.edge],
|
|
697
|
+
});
|
|
121
698
|
}
|
|
122
699
|
}
|
|
123
700
|
}
|
|
@@ -125,8 +702,14 @@ function createTsEngine(deps) {
|
|
|
125
702
|
success: true,
|
|
126
703
|
data: {
|
|
127
704
|
entity,
|
|
705
|
+
rel: relFilter || "",
|
|
706
|
+
dir: direction,
|
|
128
707
|
nodes: [...nodes.values()],
|
|
129
708
|
edges,
|
|
709
|
+
path_to: pathTo || "",
|
|
710
|
+
max_depth: maxDepth,
|
|
711
|
+
path: graphPath,
|
|
712
|
+
relation_type_distribution: [...relationTypeDistribution.entries()].map(([type, count]) => ({ type, count })),
|
|
130
713
|
},
|
|
131
714
|
};
|
|
132
715
|
}
|
|
@@ -216,33 +799,580 @@ function createTsEngine(deps) {
|
|
|
216
799
|
}
|
|
217
800
|
return { success: true, data: { deletedCount } };
|
|
218
801
|
}
|
|
802
|
+
async function backfillEmbeddings(args, _context) {
|
|
803
|
+
const layer = args.layer === "active" || args.layer === "archive" || args.layer === "all" ? args.layer : "all";
|
|
804
|
+
const rebuildMode = args.rebuild_mode === "vector_only" || args.rebuild_mode === "full"
|
|
805
|
+
? args.rebuild_mode
|
|
806
|
+
: "incremental";
|
|
807
|
+
const batchSize = typeof args.batch_size === "number" && Number.isFinite(args.batch_size) && args.batch_size > 0
|
|
808
|
+
? Math.min(500, Math.floor(args.batch_size))
|
|
809
|
+
: 100;
|
|
810
|
+
const maxRetries = typeof args.max_retries === "number" && Number.isFinite(args.max_retries) && args.max_retries >= 1
|
|
811
|
+
? Math.min(10, Math.floor(args.max_retries))
|
|
812
|
+
: 3;
|
|
813
|
+
const retryFailedOnly = args.retry_failed_only === true;
|
|
814
|
+
const forceRebuild = rebuildMode === "vector_only" || rebuildMode === "full";
|
|
815
|
+
const model = deps.embedding?.model || "";
|
|
816
|
+
const apiKey = deps.embedding?.apiKey || "";
|
|
817
|
+
const baseUrl = normalizeBaseUrl(deps.embedding?.baseURL || deps.embedding?.baseUrl);
|
|
818
|
+
if (!model || !apiKey || !baseUrl) {
|
|
819
|
+
return { success: false, error: "Embedding config missing for backfill tool." };
|
|
820
|
+
}
|
|
821
|
+
const statePath = path.join(deps.memoryRoot, ".vector_backfill_state.json");
|
|
822
|
+
const syncStatePath = path.join(deps.memoryRoot, ".sync_state.json");
|
|
823
|
+
const previousState = parseJsonFile(statePath) || {};
|
|
824
|
+
const failureCountState = (typeof previousState.failureCounts === "object" && previousState.failureCounts !== null)
|
|
825
|
+
? previousState.failureCounts
|
|
826
|
+
: {};
|
|
827
|
+
let fullSyncResult = null;
|
|
828
|
+
if (rebuildMode === "full") {
|
|
829
|
+
try {
|
|
830
|
+
fullSyncResult = await deps.sessionSync.syncMemory();
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
deps.logger.warn(`backfill_full_rebuild_sync_failed error=${error}`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const { activePath, archivePath } = memoryFiles();
|
|
837
|
+
const targetFiles = [];
|
|
838
|
+
if (layer === "all" || layer === "active") {
|
|
839
|
+
targetFiles.push({ layer: "active", filePath: activePath });
|
|
840
|
+
}
|
|
841
|
+
if (layer === "all" || layer === "archive") {
|
|
842
|
+
targetFiles.push({ layer: "archive", filePath: archivePath });
|
|
843
|
+
}
|
|
844
|
+
const queue = [];
|
|
845
|
+
const recordsByFile = new Map();
|
|
846
|
+
for (const target of targetFiles) {
|
|
847
|
+
const records = readJsonl(target.filePath);
|
|
848
|
+
recordsByFile.set(target.filePath, records);
|
|
849
|
+
for (let i = 0; i < records.length; i += 1) {
|
|
850
|
+
const record = records[i];
|
|
851
|
+
const id = typeof record.id === "string" ? record.id : "";
|
|
852
|
+
if (!id) {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const status = typeof record.embedding_status === "string" ? record.embedding_status.trim() : "";
|
|
856
|
+
const hasEmbedding = Array.isArray(record.embedding) && record.embedding.length > 0;
|
|
857
|
+
if (forceRebuild) {
|
|
858
|
+
queue.push({ layer: target.layer, filePath: target.filePath, index: i });
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (retryFailedOnly) {
|
|
862
|
+
if (status !== "failed") {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
else if (status === "ok" || hasEmbedding) {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
const failCountRaw = failureCountState[id];
|
|
870
|
+
const failCount = typeof failCountRaw === "number" ? failCountRaw : 0;
|
|
871
|
+
if (failCount >= maxRetries && status === "failed") {
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
queue.push({ layer: target.layer, filePath: target.filePath, index: i });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const totalCandidates = queue.length;
|
|
878
|
+
let success = 0;
|
|
879
|
+
let failed = 0;
|
|
880
|
+
let skipped = 0;
|
|
881
|
+
let processed = 0;
|
|
882
|
+
const failureCounts = {};
|
|
883
|
+
for (const [key, value] of Object.entries(failureCountState)) {
|
|
884
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
885
|
+
failureCounts[key] = value;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
for (let start = 0; start < queue.length; start += batchSize) {
|
|
889
|
+
const batch = queue.slice(start, start + batchSize);
|
|
890
|
+
for (const item of batch) {
|
|
891
|
+
processed += 1;
|
|
892
|
+
const records = recordsByFile.get(item.filePath) || [];
|
|
893
|
+
const record = records[item.index];
|
|
894
|
+
if (!record) {
|
|
895
|
+
skipped += 1;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
const id = typeof record.id === "string" ? record.id : "";
|
|
899
|
+
if (!id) {
|
|
900
|
+
skipped += 1;
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
const text = buildVectorSourceText(record, item.layer);
|
|
904
|
+
if (!text) {
|
|
905
|
+
record.embedding_status = "failed";
|
|
906
|
+
failed += 1;
|
|
907
|
+
failureCounts[id] = (failureCounts[id] || 0) + 1;
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const chunkSize = deps.vectorChunking?.chunkSize ?? 600;
|
|
911
|
+
const chunkOverlap = deps.vectorChunking?.chunkOverlap ?? 100;
|
|
912
|
+
const chunks = splitTextChunks(text, chunkSize, chunkOverlap);
|
|
913
|
+
if (chunks.length === 0) {
|
|
914
|
+
record.embedding_status = "failed";
|
|
915
|
+
failed += 1;
|
|
916
|
+
failureCounts[id] = (failureCounts[id] || 0) + 1;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
if (forceRebuild) {
|
|
921
|
+
record.embedding_status = "pending";
|
|
922
|
+
}
|
|
923
|
+
await deps.vectorStore.deleteBySourceMemory({ layer: item.layer, sourceMemoryId: id });
|
|
924
|
+
let chunkOk = 0;
|
|
925
|
+
for (const chunk of chunks) {
|
|
926
|
+
const embedding = await requestEmbedding({
|
|
927
|
+
text: chunk.text,
|
|
928
|
+
model,
|
|
929
|
+
apiKey,
|
|
930
|
+
baseUrl,
|
|
931
|
+
dimensions: deps.embedding?.dimensions,
|
|
932
|
+
timeoutMs: deps.embedding?.timeoutMs,
|
|
933
|
+
maxRetries: deps.embedding?.maxRetries,
|
|
934
|
+
});
|
|
935
|
+
if (!embedding || embedding.length === 0) {
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
if (!record.embedding) {
|
|
939
|
+
record.embedding = embedding;
|
|
940
|
+
}
|
|
941
|
+
await deps.vectorStore.upsert({
|
|
942
|
+
id: `${id}_c${chunk.index}`,
|
|
943
|
+
session_id: typeof record.session_id === "string" ? record.session_id : "unknown",
|
|
944
|
+
event_type: typeof record.event_type === "string" ? record.event_type : (item.layer === "active" ? "message" : "insight"),
|
|
945
|
+
summary: chunk.text,
|
|
946
|
+
timestamp: typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString(),
|
|
947
|
+
layer: item.layer,
|
|
948
|
+
source_memory_id: id,
|
|
949
|
+
source_memory_canonical_id: typeof record.canonical_id === "string" ? record.canonical_id : id,
|
|
950
|
+
outcome: typeof record.outcome === "string" ? record.outcome : "",
|
|
951
|
+
entities: Array.isArray(record.entities) ? record.entities.filter(v => typeof v === "string") : [],
|
|
952
|
+
relations: Array.isArray(record.relations)
|
|
953
|
+
? record.relations
|
|
954
|
+
.map(v => {
|
|
955
|
+
if (!v || typeof v !== "object")
|
|
956
|
+
return null;
|
|
957
|
+
const relation = v;
|
|
958
|
+
const source = typeof relation.source === "string" ? relation.source : "";
|
|
959
|
+
const target = typeof relation.target === "string" ? relation.target : "";
|
|
960
|
+
const type = typeof relation.type === "string" ? relation.type : "related_to";
|
|
961
|
+
if (!source || !target)
|
|
962
|
+
return null;
|
|
963
|
+
return { source, target, type };
|
|
964
|
+
})
|
|
965
|
+
.filter((v) => Boolean(v))
|
|
966
|
+
: [],
|
|
967
|
+
embedding,
|
|
968
|
+
quality_score: typeof record.quality_score === "number" ? record.quality_score : 0.5,
|
|
969
|
+
char_count: chunk.text.length,
|
|
970
|
+
token_count: estimateTokenCount(chunk.text),
|
|
971
|
+
chunk_index: chunk.index,
|
|
972
|
+
chunk_total: chunks.length,
|
|
973
|
+
chunk_start: chunk.start,
|
|
974
|
+
chunk_end: chunk.end,
|
|
975
|
+
});
|
|
976
|
+
chunkOk += 1;
|
|
977
|
+
}
|
|
978
|
+
record.vector_chunks_total = chunks.length;
|
|
979
|
+
record.vector_chunks_ok = chunkOk;
|
|
980
|
+
record.embedding_status = chunkOk === chunks.length ? "ok" : "failed";
|
|
981
|
+
if (!record.layer) {
|
|
982
|
+
record.layer = item.layer;
|
|
983
|
+
}
|
|
984
|
+
if (typeof record.char_count !== "number") {
|
|
985
|
+
record.char_count = text.length;
|
|
986
|
+
}
|
|
987
|
+
if (typeof record.token_count !== "number") {
|
|
988
|
+
record.token_count = estimateTokenCount(text);
|
|
989
|
+
}
|
|
990
|
+
if (chunkOk === chunks.length) {
|
|
991
|
+
success += 1;
|
|
992
|
+
failureCounts[id] = 0;
|
|
993
|
+
}
|
|
994
|
+
else {
|
|
995
|
+
failed += 1;
|
|
996
|
+
failureCounts[id] = (failureCounts[id] || 0) + 1;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
catch (error) {
|
|
1000
|
+
record.embedding_status = "failed";
|
|
1001
|
+
failed += 1;
|
|
1002
|
+
failureCounts[id] = (failureCounts[id] || 0) + 1;
|
|
1003
|
+
deps.logger.warn(`backfill_embedding_failed id=${id} layer=${item.layer} error=${error}`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
deps.logger.info(`backfill_progress processed=${processed}/${totalCandidates} success=${success} failed=${failed} skipped=${skipped}`);
|
|
1007
|
+
}
|
|
1008
|
+
for (const target of targetFiles) {
|
|
1009
|
+
const records = recordsByFile.get(target.filePath);
|
|
1010
|
+
if (records) {
|
|
1011
|
+
writeJsonl(target.filePath, records);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const summary = {
|
|
1015
|
+
runAt: new Date().toISOString(),
|
|
1016
|
+
layer,
|
|
1017
|
+
rebuild_mode: rebuildMode,
|
|
1018
|
+
candidates: totalCandidates,
|
|
1019
|
+
success,
|
|
1020
|
+
failed,
|
|
1021
|
+
skipped,
|
|
1022
|
+
batch_size: batchSize,
|
|
1023
|
+
max_retries: maxRetries,
|
|
1024
|
+
retry_failed_only: retryFailedOnly,
|
|
1025
|
+
full_sync_result: fullSyncResult,
|
|
1026
|
+
};
|
|
1027
|
+
upsertJsonFile(statePath, {
|
|
1028
|
+
version: "1",
|
|
1029
|
+
lastRun: summary,
|
|
1030
|
+
failureCounts,
|
|
1031
|
+
});
|
|
1032
|
+
upsertJsonFile(syncStatePath, {
|
|
1033
|
+
version: "2",
|
|
1034
|
+
lastVectorBackfill: {
|
|
1035
|
+
runAt: summary.runAt,
|
|
1036
|
+
success,
|
|
1037
|
+
failed,
|
|
1038
|
+
skipped,
|
|
1039
|
+
},
|
|
1040
|
+
});
|
|
1041
|
+
return { success: true, data: summary };
|
|
1042
|
+
}
|
|
219
1043
|
async function runDiagnostics(_args, _context) {
|
|
220
1044
|
const { activePath, archivePath } = memoryFiles();
|
|
1045
|
+
const activeRecords = readJsonl(activePath);
|
|
1046
|
+
const archiveRecords = readJsonl(archivePath);
|
|
1047
|
+
const activeVector = embeddingStats(activeRecords);
|
|
1048
|
+
const archiveVector = embeddingStats(archiveRecords);
|
|
1049
|
+
const vectorJsonlPath = path.join(deps.memoryRoot, "vector", "lancedb_events.jsonl");
|
|
1050
|
+
const vectorJsonlRecords = readJsonl(vectorJsonlPath);
|
|
1051
|
+
const activeVectorRecords = vectorJsonlRecords.filter(record => (record.layer === "active"));
|
|
1052
|
+
const archiveVectorRecords = vectorJsonlRecords.filter(record => (record.layer === "archive"));
|
|
1053
|
+
const lancedbDir = path.join(deps.memoryRoot, "vector", "lancedb");
|
|
1054
|
+
const lancedbExists = fs.existsSync(lancedbDir);
|
|
1055
|
+
let lancedbRecordCount = 0;
|
|
1056
|
+
if (lancedbExists) {
|
|
1057
|
+
try {
|
|
1058
|
+
const lancedbFiles = fs.readdirSync(lancedbDir).filter(f => f.endsWith(".lance") || f.endsWith(".manifest"));
|
|
1059
|
+
lancedbRecordCount = lancedbFiles.length > 0 ? -1 : 0;
|
|
1060
|
+
}
|
|
1061
|
+
catch {
|
|
1062
|
+
lancedbRecordCount = 0;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
const totalVectorRecords = vectorJsonlRecords.length > 0 ? vectorJsonlRecords.length : (lancedbRecordCount === -1 ? -1 : 0);
|
|
1066
|
+
const vectorStorageType = lancedbExists && lancedbRecordCount === -1 ? "lancedb" : (vectorJsonlRecords.length > 0 ? "jsonl" : "none");
|
|
1067
|
+
const syncState = parseJsonFile(path.join(deps.memoryRoot, ".sync_state.json"));
|
|
1068
|
+
const backfillState = parseJsonFile(path.join(deps.memoryRoot, ".vector_backfill_state.json"));
|
|
1069
|
+
const failureCounts = backfillState && typeof backfillState.failureCounts === "object" && backfillState.failureCounts !== null
|
|
1070
|
+
? backfillState.failureCounts
|
|
1071
|
+
: {};
|
|
1072
|
+
const pendingRetry = Object.values(failureCounts).filter(value => typeof value === "number" && Number.isFinite(value) && value > 0).length;
|
|
1073
|
+
const lastVectorBackfill = syncState && typeof syncState.lastVectorBackfill === "object" && syncState.lastVectorBackfill !== null
|
|
1074
|
+
? syncState.lastVectorBackfill
|
|
1075
|
+
: null;
|
|
1076
|
+
const embeddingConnectivity = await probeModelConnection({
|
|
1077
|
+
kind: "embedding",
|
|
1078
|
+
model: deps.embedding?.model || "",
|
|
1079
|
+
apiKey: deps.embedding?.apiKey || "",
|
|
1080
|
+
baseUrl: normalizeBaseUrl(deps.embedding?.baseURL || deps.embedding?.baseUrl),
|
|
1081
|
+
timeoutMs: deps.embedding?.timeoutMs,
|
|
1082
|
+
});
|
|
1083
|
+
const llmConnectivity = await probeModelConnection({
|
|
1084
|
+
kind: "llm",
|
|
1085
|
+
model: deps.llm?.model || "",
|
|
1086
|
+
apiKey: deps.llm?.apiKey || "",
|
|
1087
|
+
baseUrl: normalizeBaseUrl(deps.llm?.baseURL || deps.llm?.baseUrl),
|
|
1088
|
+
timeoutMs: 8000,
|
|
1089
|
+
});
|
|
1090
|
+
const rerankerConnectivity = await probeModelConnection({
|
|
1091
|
+
kind: "reranker",
|
|
1092
|
+
model: deps.reranker?.model || "",
|
|
1093
|
+
apiKey: deps.reranker?.apiKey || "",
|
|
1094
|
+
baseUrl: normalizeBaseUrl(deps.reranker?.baseURL || deps.reranker?.baseUrl),
|
|
1095
|
+
timeoutMs: 8000,
|
|
1096
|
+
});
|
|
221
1097
|
const checks = [
|
|
222
1098
|
{ name: "Engine mode", passed: true, message: "TS engine active" },
|
|
223
1099
|
{ name: "Active sessions store", passed: fs.existsSync(activePath), message: activePath },
|
|
224
1100
|
{ name: "Archive sessions store", passed: fs.existsSync(archivePath), message: archivePath },
|
|
225
1101
|
{ name: "Core rules store", passed: fs.existsSync(path.join(deps.memoryRoot, "CORTEX_RULES.md")), message: "CORTEX_RULES.md checked" },
|
|
1102
|
+
{ name: "Embedding model connectivity", passed: embeddingConnectivity.connected, message: embeddingConnectivity.error || "ok" },
|
|
1103
|
+
{ name: "LLM model connectivity", passed: llmConnectivity.connected, message: llmConnectivity.error || "ok" },
|
|
1104
|
+
{ name: "Reranker model connectivity", passed: rerankerConnectivity.connected, message: rerankerConnectivity.error || "ok" },
|
|
226
1105
|
];
|
|
1106
|
+
const qualityCheck = {
|
|
1107
|
+
active: { total: 0, valid: 0, invalid: 0, issues: [] },
|
|
1108
|
+
archive: { total: 0, valid: 0, invalid: 0, issues: [] },
|
|
1109
|
+
};
|
|
1110
|
+
if (fs.existsSync(activePath)) {
|
|
1111
|
+
const content = fs.readFileSync(activePath, "utf-8");
|
|
1112
|
+
const lines = content.split(/\r?\n/).filter(l => l.trim());
|
|
1113
|
+
qualityCheck.active.total = lines.length;
|
|
1114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1115
|
+
const validation = (0, llm_output_validator_1.validateJsonlLine)(lines[i]);
|
|
1116
|
+
if (validation.valid) {
|
|
1117
|
+
qualityCheck.active.valid++;
|
|
1118
|
+
}
|
|
1119
|
+
else {
|
|
1120
|
+
qualityCheck.active.invalid++;
|
|
1121
|
+
if (qualityCheck.active.issues.length < 5) {
|
|
1122
|
+
qualityCheck.active.issues.push({ line: i + 1, errors: validation.errors });
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
if (fs.existsSync(archivePath)) {
|
|
1128
|
+
const content = fs.readFileSync(archivePath, "utf-8");
|
|
1129
|
+
const lines = content.split(/\r?\n/).filter(l => l.trim());
|
|
1130
|
+
qualityCheck.archive.total = lines.length;
|
|
1131
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1132
|
+
const validation = (0, llm_output_validator_1.validateJsonlLine)(lines[i]);
|
|
1133
|
+
if (validation.valid) {
|
|
1134
|
+
qualityCheck.archive.valid++;
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
qualityCheck.archive.invalid++;
|
|
1138
|
+
if (qualityCheck.archive.issues.length < 5) {
|
|
1139
|
+
qualityCheck.archive.issues.push({ line: i + 1, errors: validation.errors });
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const totalInvalid = qualityCheck.active.invalid + qualityCheck.archive.invalid;
|
|
1145
|
+
if (totalInvalid > 0) {
|
|
1146
|
+
checks.push({ name: "Data integrity", passed: false, message: `${totalInvalid} invalid records found` });
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
checks.push({ name: "Data integrity", passed: true, message: "All records valid" });
|
|
1150
|
+
}
|
|
1151
|
+
const graphMemoryPath = path.join(deps.memoryRoot, "graph", "memory.jsonl");
|
|
1152
|
+
const graphRecords = readJsonl(graphMemoryPath);
|
|
1153
|
+
function stringField(record, key) {
|
|
1154
|
+
const value = record[key];
|
|
1155
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1156
|
+
}
|
|
1157
|
+
const activeFieldIssues = {
|
|
1158
|
+
missing_id: 0,
|
|
1159
|
+
missing_timestamp: 0,
|
|
1160
|
+
missing_layer: 0,
|
|
1161
|
+
missing_text_payload: 0,
|
|
1162
|
+
};
|
|
1163
|
+
for (const record of activeRecords) {
|
|
1164
|
+
if (!stringField(record, "id"))
|
|
1165
|
+
activeFieldIssues.missing_id += 1;
|
|
1166
|
+
if (!stringField(record, "timestamp"))
|
|
1167
|
+
activeFieldIssues.missing_timestamp += 1;
|
|
1168
|
+
if (stringField(record, "layer") !== "active")
|
|
1169
|
+
activeFieldIssues.missing_layer += 1;
|
|
1170
|
+
const hasPayload = [
|
|
1171
|
+
stringField(record, "content"),
|
|
1172
|
+
stringField(record, "summary"),
|
|
1173
|
+
stringField(record, "text"),
|
|
1174
|
+
stringField(record, "message"),
|
|
1175
|
+
].some(Boolean);
|
|
1176
|
+
if (!hasPayload)
|
|
1177
|
+
activeFieldIssues.missing_text_payload += 1;
|
|
1178
|
+
}
|
|
1179
|
+
const archiveFieldIssues = {
|
|
1180
|
+
missing_id: 0,
|
|
1181
|
+
missing_timestamp: 0,
|
|
1182
|
+
missing_layer: 0,
|
|
1183
|
+
missing_summary: 0,
|
|
1184
|
+
missing_source_memory_id: 0,
|
|
1185
|
+
};
|
|
1186
|
+
for (const record of archiveRecords) {
|
|
1187
|
+
if (!stringField(record, "id"))
|
|
1188
|
+
archiveFieldIssues.missing_id += 1;
|
|
1189
|
+
if (!stringField(record, "timestamp"))
|
|
1190
|
+
archiveFieldIssues.missing_timestamp += 1;
|
|
1191
|
+
if (stringField(record, "layer") !== "archive")
|
|
1192
|
+
archiveFieldIssues.missing_layer += 1;
|
|
1193
|
+
if (!stringField(record, "summary"))
|
|
1194
|
+
archiveFieldIssues.missing_summary += 1;
|
|
1195
|
+
if (!stringField(record, "source_memory_id") && !stringField(record, "canonical_id")) {
|
|
1196
|
+
archiveFieldIssues.missing_source_memory_id += 1;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
const vectorFieldIssues = {
|
|
1200
|
+
missing_id: 0,
|
|
1201
|
+
missing_layer: 0,
|
|
1202
|
+
missing_summary: 0,
|
|
1203
|
+
missing_source_memory_id: 0,
|
|
1204
|
+
missing_embedding: 0,
|
|
1205
|
+
};
|
|
1206
|
+
for (const record of vectorJsonlRecords) {
|
|
1207
|
+
if (!stringField(record, "id"))
|
|
1208
|
+
vectorFieldIssues.missing_id += 1;
|
|
1209
|
+
const layer = stringField(record, "layer");
|
|
1210
|
+
if (layer !== "active" && layer !== "archive")
|
|
1211
|
+
vectorFieldIssues.missing_layer += 1;
|
|
1212
|
+
if (!stringField(record, "summary"))
|
|
1213
|
+
vectorFieldIssues.missing_summary += 1;
|
|
1214
|
+
if (!stringField(record, "source_memory_id"))
|
|
1215
|
+
vectorFieldIssues.missing_source_memory_id += 1;
|
|
1216
|
+
const embedding = record.embedding;
|
|
1217
|
+
const vector = record.vector;
|
|
1218
|
+
const hasEmbedding = Array.isArray(embedding) ? embedding.length > 0 : (Array.isArray(vector) ? vector.length > 0 : false);
|
|
1219
|
+
if (!hasEmbedding)
|
|
1220
|
+
vectorFieldIssues.missing_embedding += 1;
|
|
1221
|
+
}
|
|
1222
|
+
const graphFieldIssues = {
|
|
1223
|
+
missing_id: 0,
|
|
1224
|
+
missing_event_ref: 0,
|
|
1225
|
+
missing_layer: 0,
|
|
1226
|
+
malformed_relations: 0,
|
|
1227
|
+
};
|
|
1228
|
+
for (const record of graphRecords) {
|
|
1229
|
+
if (!stringField(record, "id"))
|
|
1230
|
+
graphFieldIssues.missing_id += 1;
|
|
1231
|
+
if (!stringField(record, "source_event_id") && !stringField(record, "archive_event_id")) {
|
|
1232
|
+
graphFieldIssues.missing_event_ref += 1;
|
|
1233
|
+
}
|
|
1234
|
+
const layer = stringField(record, "source_layer");
|
|
1235
|
+
if (layer !== "archive_event" && layer !== "active_only")
|
|
1236
|
+
graphFieldIssues.missing_layer += 1;
|
|
1237
|
+
if (!Array.isArray(record.relations))
|
|
1238
|
+
graphFieldIssues.malformed_relations += 1;
|
|
1239
|
+
}
|
|
1240
|
+
const archiveIdSet = new Set(archiveRecords
|
|
1241
|
+
.map(record => stringField(record, "id"))
|
|
1242
|
+
.filter(Boolean));
|
|
1243
|
+
const vectorLinkedToArchive = vectorJsonlRecords.filter(record => {
|
|
1244
|
+
const sourceMemoryId = stringField(record, "source_memory_id");
|
|
1245
|
+
return !!sourceMemoryId && archiveIdSet.has(sourceMemoryId);
|
|
1246
|
+
}).length;
|
|
1247
|
+
const graphLinkedToArchive = graphRecords.filter(record => {
|
|
1248
|
+
const sourceLayer = stringField(record, "source_layer");
|
|
1249
|
+
const refId = stringField(record, "source_event_id") || stringField(record, "archive_event_id");
|
|
1250
|
+
return sourceLayer === "archive_event" && !!refId && archiveIdSet.has(refId);
|
|
1251
|
+
}).length;
|
|
1252
|
+
const schemaIssueTotal = Object.values(activeFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1253
|
+
+ Object.values(archiveFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1254
|
+
+ Object.values(vectorFieldIssues).reduce((sum, n) => sum + n, 0)
|
|
1255
|
+
+ Object.values(graphFieldIssues).reduce((sum, n) => sum + n, 0);
|
|
1256
|
+
checks.push({
|
|
1257
|
+
name: "Field mapping alignment",
|
|
1258
|
+
passed: schemaIssueTotal === 0,
|
|
1259
|
+
message: schemaIssueTotal === 0
|
|
1260
|
+
? "active/archive/vector/graph field mapping aligned with read path"
|
|
1261
|
+
: `${schemaIssueTotal} field mapping issues detected across four memory stores`,
|
|
1262
|
+
});
|
|
227
1263
|
return {
|
|
228
1264
|
success: true,
|
|
229
1265
|
data: {
|
|
230
1266
|
status: "ok",
|
|
1267
|
+
prompt_versions: PROMPT_VERSIONS,
|
|
231
1268
|
checks,
|
|
232
|
-
|
|
1269
|
+
layers: {
|
|
1270
|
+
active: {
|
|
1271
|
+
records: activeRecords.length,
|
|
1272
|
+
path: activePath,
|
|
1273
|
+
},
|
|
1274
|
+
archive: {
|
|
1275
|
+
records: archiveRecords.length,
|
|
1276
|
+
path: archivePath,
|
|
1277
|
+
},
|
|
1278
|
+
vector: {
|
|
1279
|
+
storage_type: vectorStorageType,
|
|
1280
|
+
lancedb_exists: lancedbExists,
|
|
1281
|
+
active_coverage: activeVector.coverage,
|
|
1282
|
+
archive_coverage: archiveVector.coverage,
|
|
1283
|
+
active_unembedded: activeVector.pending + activeVector.failed,
|
|
1284
|
+
archive_unembedded: archiveVector.pending + archiveVector.failed,
|
|
1285
|
+
chunking: {
|
|
1286
|
+
chunk_size: deps.vectorChunking?.chunkSize ?? 600,
|
|
1287
|
+
chunk_overlap: deps.vectorChunking?.chunkOverlap ?? 100,
|
|
1288
|
+
},
|
|
1289
|
+
vector_jsonl_records: vectorJsonlRecords.length,
|
|
1290
|
+
vector_jsonl_by_layer: {
|
|
1291
|
+
active: activeVectorRecords.length,
|
|
1292
|
+
archive: archiveVectorRecords.length,
|
|
1293
|
+
},
|
|
1294
|
+
total_vector_records: totalVectorRecords,
|
|
1295
|
+
last_backfill_summary: lastVectorBackfill,
|
|
1296
|
+
backfill_state: {
|
|
1297
|
+
pending_retry_records: pendingRetry,
|
|
1298
|
+
has_state_file: fs.existsSync(path.join(deps.memoryRoot, ".vector_backfill_state.json")),
|
|
1299
|
+
},
|
|
1300
|
+
},
|
|
1301
|
+
graph_rules: {
|
|
1302
|
+
graph_mutation_log_exists: fs.existsSync(path.join(deps.memoryRoot, "graph", "mutation_log.jsonl")),
|
|
1303
|
+
rules_exists: fs.existsSync(path.join(deps.memoryRoot, "CORTEX_RULES.md")),
|
|
1304
|
+
},
|
|
1305
|
+
},
|
|
1306
|
+
model_connectivity: {
|
|
1307
|
+
embedding: embeddingConnectivity,
|
|
1308
|
+
llm: llmConnectivity,
|
|
1309
|
+
reranker: rerankerConnectivity,
|
|
1310
|
+
},
|
|
1311
|
+
quality_check: qualityCheck,
|
|
1312
|
+
schema_alignment: {
|
|
1313
|
+
active: {
|
|
1314
|
+
records: activeRecords.length,
|
|
1315
|
+
issues: activeFieldIssues,
|
|
1316
|
+
},
|
|
1317
|
+
archive: {
|
|
1318
|
+
records: archiveRecords.length,
|
|
1319
|
+
issues: archiveFieldIssues,
|
|
1320
|
+
},
|
|
1321
|
+
vector: {
|
|
1322
|
+
records: vectorJsonlRecords.length,
|
|
1323
|
+
issues: vectorFieldIssues,
|
|
1324
|
+
linked_to_archive: vectorLinkedToArchive,
|
|
1325
|
+
},
|
|
1326
|
+
graph: {
|
|
1327
|
+
records: graphRecords.length,
|
|
1328
|
+
issues: graphFieldIssues,
|
|
1329
|
+
linked_archive_events: graphLinkedToArchive,
|
|
1330
|
+
},
|
|
1331
|
+
cross_layer_links: {
|
|
1332
|
+
archive_records: archiveIdSet.size,
|
|
1333
|
+
vector_archive_link_coverage: archiveIdSet.size > 0
|
|
1334
|
+
? Number((vectorLinkedToArchive / archiveIdSet.size).toFixed(4))
|
|
1335
|
+
: 0,
|
|
1336
|
+
graph_archive_link_coverage: archiveIdSet.size > 0
|
|
1337
|
+
? Number((graphLinkedToArchive / archiveIdSet.size).toFixed(4))
|
|
1338
|
+
: 0,
|
|
1339
|
+
},
|
|
1340
|
+
},
|
|
1341
|
+
recommendations: [
|
|
1342
|
+
...(totalInvalid > 0 ? ["Run repair-memory --fix to clean invalid records"] : []),
|
|
1343
|
+
...(schemaIssueTotal > 0 ? ["Run diagnostics output schema_alignment and repair missing cross-layer fields"] : []),
|
|
1344
|
+
],
|
|
233
1345
|
},
|
|
234
1346
|
};
|
|
235
1347
|
}
|
|
236
1348
|
async function searchMemory(args, context) {
|
|
237
|
-
|
|
1349
|
+
const argsRecord = asRecord(args) || {};
|
|
1350
|
+
const argsInput = asRecord(argsRecord.input);
|
|
1351
|
+
const queryCandidate = [
|
|
1352
|
+
typeof args.query === "string" ? args.query : "",
|
|
1353
|
+
typeof argsRecord.query === "string" ? String(argsRecord.query) : "",
|
|
1354
|
+
typeof argsRecord.q === "string" ? String(argsRecord.q) : "",
|
|
1355
|
+
typeof argsRecord.keyword === "string" ? String(argsRecord.keyword) : "",
|
|
1356
|
+
typeof argsInput?.query === "string" ? String(argsInput.query) : "",
|
|
1357
|
+
typeof argsInput?.q === "string" ? String(argsInput.q) : "",
|
|
1358
|
+
].find(item => item.trim());
|
|
1359
|
+
const query = queryCandidate ? queryCandidate.trim() : "";
|
|
1360
|
+
if (!query) {
|
|
238
1361
|
return {
|
|
239
1362
|
success: false,
|
|
240
1363
|
error: "Invalid input provided. Missing 'query' parameter.",
|
|
241
1364
|
};
|
|
242
1365
|
}
|
|
1366
|
+
const topKRaw = [
|
|
1367
|
+
typeof args.top_k === "number" ? args.top_k : undefined,
|
|
1368
|
+
typeof argsRecord.top_k === "number" ? Number(argsRecord.top_k) : undefined,
|
|
1369
|
+
typeof argsRecord.topK === "number" ? Number(argsRecord.topK) : undefined,
|
|
1370
|
+
typeof argsInput?.top_k === "number" ? Number(argsInput.top_k) : undefined,
|
|
1371
|
+
typeof argsInput?.topK === "number" ? Number(argsInput.topK) : undefined,
|
|
1372
|
+
].find(value => typeof value === "number" && Number.isFinite(value));
|
|
243
1373
|
const result = await deps.readStore.searchMemory({
|
|
244
|
-
query
|
|
245
|
-
topK: typeof
|
|
1374
|
+
query,
|
|
1375
|
+
topK: typeof topKRaw === "number" && topKRaw > 0 ? Math.floor(topKRaw) : 3,
|
|
246
1376
|
});
|
|
247
1377
|
return { success: true, data: result.results };
|
|
248
1378
|
}
|
|
@@ -252,10 +1382,19 @@ function createTsEngine(deps) {
|
|
|
252
1382
|
return { success: true, data: result.context };
|
|
253
1383
|
}
|
|
254
1384
|
async function getAutoContext(args, context) {
|
|
255
|
-
const
|
|
1385
|
+
const argsRecord = asRecord(args) || {};
|
|
1386
|
+
const argsInput = asRecord(argsRecord.input);
|
|
1387
|
+
const includeHotRaw = [
|
|
1388
|
+
typeof args.include_hot === "boolean" ? args.include_hot : undefined,
|
|
1389
|
+
typeof argsRecord.include_hot === "boolean" ? Boolean(argsRecord.include_hot) : undefined,
|
|
1390
|
+
typeof argsRecord.includeHot === "boolean" ? Boolean(argsRecord.includeHot) : undefined,
|
|
1391
|
+
typeof argsInput?.include_hot === "boolean" ? Boolean(argsInput.include_hot) : undefined,
|
|
1392
|
+
typeof argsInput?.includeHot === "boolean" ? Boolean(argsInput.includeHot) : undefined,
|
|
1393
|
+
].find(value => typeof value === "boolean");
|
|
1394
|
+
const sessionId = deps.resolveSessionId((context || {}));
|
|
256
1395
|
const cached = deps.getCachedAutoSearch(sessionId);
|
|
257
1396
|
const result = await deps.readStore.getAutoContext({
|
|
258
|
-
includeHot:
|
|
1397
|
+
includeHot: includeHotRaw !== false,
|
|
259
1398
|
sessionId,
|
|
260
1399
|
cachedAutoSearch: cached ?? undefined,
|
|
261
1400
|
});
|
|
@@ -305,12 +1444,15 @@ function createTsEngine(deps) {
|
|
|
305
1444
|
const sessionId = deps.resolveSessionId(context, payload);
|
|
306
1445
|
const syncRecordsRaw = payloadObj?.sync_records;
|
|
307
1446
|
const syncRecords = typeof syncRecordsRaw === "boolean" ? syncRecordsRaw : deps.defaultAutoSync;
|
|
1447
|
+
const bufferedMessages = sessionMessageBuffer.get(sessionId) || [];
|
|
308
1448
|
try {
|
|
309
1449
|
const result = await deps.sessionEnd.onSessionEnd({
|
|
310
1450
|
sessionId,
|
|
311
1451
|
syncRecords,
|
|
1452
|
+
messages: bufferedMessages,
|
|
312
1453
|
});
|
|
313
1454
|
deps.logger.info(`TS session_end completed for ${sessionId}, events=${result.events_generated}`);
|
|
1455
|
+
sessionMessageBuffer.delete(sessionId);
|
|
314
1456
|
}
|
|
315
1457
|
catch (error) {
|
|
316
1458
|
deps.logger.warn(`TS session_end failed for ${sessionId}: ${error}`);
|
|
@@ -323,26 +1465,11 @@ function createTsEngine(deps) {
|
|
|
323
1465
|
}
|
|
324
1466
|
const { text, role, source } = normalized;
|
|
325
1467
|
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
|
-
}
|
|
1468
|
+
pushSessionMessage(sessionId, { role, text });
|
|
1469
|
+
deps.logger.debug(`TS buffered ${role} message for session ${sessionId} source=${source}`);
|
|
343
1470
|
if (role === "user" && text.length > 5) {
|
|
344
1471
|
try {
|
|
345
|
-
const searchResult = await deps.readStore.searchMemory({ query: text, topK: 3 });
|
|
1472
|
+
const searchResult = await deps.readStore.searchMemory({ query: text, topK: 3, mode: "lightweight" });
|
|
346
1473
|
if (searchResult.results.length > 0) {
|
|
347
1474
|
deps.setSessionAutoSearchCache(sessionId, text, searchResult.results);
|
|
348
1475
|
deps.logger.info(`TS auto-search cached ${searchResult.results.length} results for context`);
|
|
@@ -381,6 +1508,7 @@ function createTsEngine(deps) {
|
|
|
381
1508
|
deleteMemory,
|
|
382
1509
|
updateMemory,
|
|
383
1510
|
cleanupMemories,
|
|
1511
|
+
backfillEmbeddings,
|
|
384
1512
|
runDiagnostics,
|
|
385
1513
|
onMessage,
|
|
386
1514
|
onSessionEnd,
|