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