openclaw-cortex-memory 0.1.0-Alpha.3 → 0.1.0-Alpha.30
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 +263 -204
- package/SKILL.md +77 -268
- package/dist/index.d.ts +92 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1062 -1207
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +384 -15
- 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 +1145 -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/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 +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 +352 -8
- package/dist/src/reflect/reflector.js.map +1 -1
- package/dist/src/rules/rule_store.d.ts.map +1 -1
- package/dist/src/rules/rule_store.js +75 -16
- package/dist/src/rules/rule_store.js.map +1 -1
- package/dist/src/session/session_end.d.ts +33 -0
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +67 -64
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +128 -0
- package/dist/src/store/archive_store.d.ts.map +1 -0
- package/dist/src/store/archive_store.js +475 -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 +1661 -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 +239 -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 +725 -28
- 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 +384 -15
- package/package.json +53 -7
- package/schema/graph.schema.yaml +175 -0
- package/scripts/cli.js +19 -14
- package/scripts/repair-memory.js +321 -0
- package/scripts/uninstall.js +22 -5
- package/index.ts +0 -2142
- package/scripts/install.js +0 -27
|
@@ -37,6 +37,9 @@ exports.createSessionSync = createSessionSync;
|
|
|
37
37
|
const crypto = __importStar(require("crypto"));
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
+
const http_post_1 = require("../net/http_post");
|
|
41
|
+
const llm_output_validator_1 = require("../quality/llm_output_validator");
|
|
42
|
+
const runtime_env_1 = require("../utils/runtime_env");
|
|
40
43
|
function asRecord(value) {
|
|
41
44
|
if (typeof value === "object" && value !== null) {
|
|
42
45
|
return value;
|
|
@@ -51,23 +54,64 @@ function firstString(values) {
|
|
|
51
54
|
}
|
|
52
55
|
return undefined;
|
|
53
56
|
}
|
|
57
|
+
function extractTextFromContent(content) {
|
|
58
|
+
if (typeof content === "string" && content.trim()) {
|
|
59
|
+
return content.trim();
|
|
60
|
+
}
|
|
61
|
+
if (!Array.isArray(content)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const parts = [];
|
|
65
|
+
for (const item of content) {
|
|
66
|
+
if (typeof item === "string" && item.trim()) {
|
|
67
|
+
parts.push(item.trim());
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const obj = asRecord(item);
|
|
71
|
+
if (!obj) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const text = firstString([obj.text, obj.content, obj.summary, obj.message, obj.body]);
|
|
75
|
+
if (text) {
|
|
76
|
+
parts.push(text);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (parts.length === 0) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
return parts.join("\n");
|
|
83
|
+
}
|
|
84
|
+
function extractTextFromMessageRecord(record) {
|
|
85
|
+
const contentText = extractTextFromContent(record.content);
|
|
86
|
+
return firstString([contentText, record.text, record.summary, record.message, record.body]);
|
|
87
|
+
}
|
|
88
|
+
const SYNC_STATE_VERSION = "2";
|
|
89
|
+
function createDefaultState() {
|
|
90
|
+
return { version: SYNC_STATE_VERSION, files: {}, markdowns: {} };
|
|
91
|
+
}
|
|
54
92
|
function readState(filePath) {
|
|
55
93
|
try {
|
|
56
94
|
if (!fs.existsSync(filePath)) {
|
|
57
|
-
return
|
|
95
|
+
return createDefaultState();
|
|
58
96
|
}
|
|
59
97
|
const content = fs.readFileSync(filePath, "utf-8").trim();
|
|
60
98
|
if (!content) {
|
|
61
|
-
return
|
|
99
|
+
return createDefaultState();
|
|
62
100
|
}
|
|
63
101
|
const parsed = JSON.parse(content);
|
|
64
102
|
if (!parsed.files || typeof parsed.files !== "object") {
|
|
65
|
-
return
|
|
103
|
+
return createDefaultState();
|
|
104
|
+
}
|
|
105
|
+
if (!parsed.markdowns || typeof parsed.markdowns !== "object") {
|
|
106
|
+
parsed.markdowns = {};
|
|
66
107
|
}
|
|
108
|
+
parsed.version = typeof parsed.version === "string" && parsed.version.trim()
|
|
109
|
+
? parsed.version
|
|
110
|
+
: SYNC_STATE_VERSION;
|
|
67
111
|
return parsed;
|
|
68
112
|
}
|
|
69
113
|
catch {
|
|
70
|
-
return
|
|
114
|
+
return createDefaultState();
|
|
71
115
|
}
|
|
72
116
|
}
|
|
73
117
|
function writeState(filePath, state) {
|
|
@@ -75,6 +119,7 @@ function writeState(filePath, state) {
|
|
|
75
119
|
if (!fs.existsSync(dir)) {
|
|
76
120
|
fs.mkdirSync(dir, { recursive: true });
|
|
77
121
|
}
|
|
122
|
+
state.version = SYNC_STATE_VERSION;
|
|
78
123
|
fs.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
79
124
|
}
|
|
80
125
|
function gatherSessionFiles(openclawBasePath, memoryRoot) {
|
|
@@ -93,12 +138,37 @@ function gatherSessionFiles(openclawBasePath, memoryRoot) {
|
|
|
93
138
|
}
|
|
94
139
|
return [...results];
|
|
95
140
|
}
|
|
141
|
+
function gatherDailySummaryFiles(openclawBasePath) {
|
|
142
|
+
const summaryDir = path.join(openclawBasePath, "workspace", "memory");
|
|
143
|
+
if (!fs.existsSync(summaryDir) || !fs.statSync(summaryDir).isDirectory()) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const files = [];
|
|
147
|
+
for (const entry of fs.readdirSync(summaryDir)) {
|
|
148
|
+
if (!entry.toLowerCase().endsWith(".md")) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const filePath = path.join(summaryDir, entry);
|
|
152
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
153
|
+
files.push(filePath);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return files;
|
|
157
|
+
}
|
|
96
158
|
function inferOpenclawBasePath(projectRoot) {
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
159
|
+
const configPath = (0, runtime_env_1.getEnvValue)("OPENCLAW_CONFIG_PATH");
|
|
160
|
+
if (configPath && fs.existsSync(configPath)) {
|
|
161
|
+
return path.dirname(configPath);
|
|
162
|
+
}
|
|
163
|
+
const stateDir = (0, runtime_env_1.getEnvValue)("OPENCLAW_STATE_DIR");
|
|
164
|
+
if (stateDir && fs.existsSync(stateDir)) {
|
|
165
|
+
return stateDir;
|
|
166
|
+
}
|
|
167
|
+
const basePath = (0, runtime_env_1.getEnvValue)("OPENCLAW_BASE_PATH");
|
|
168
|
+
if (basePath && fs.existsSync(basePath)) {
|
|
169
|
+
return basePath;
|
|
100
170
|
}
|
|
101
|
-
const home =
|
|
171
|
+
const home = (0, runtime_env_1.getHomeDir)();
|
|
102
172
|
if (home) {
|
|
103
173
|
const defaultPath = path.join(home, ".openclaw");
|
|
104
174
|
if (fs.existsSync(defaultPath)) {
|
|
@@ -118,7 +188,7 @@ function extractMessages(record) {
|
|
|
118
188
|
const obj = asRecord(item);
|
|
119
189
|
if (!obj)
|
|
120
190
|
continue;
|
|
121
|
-
const text =
|
|
191
|
+
const text = extractTextFromMessageRecord(obj);
|
|
122
192
|
if (!text)
|
|
123
193
|
continue;
|
|
124
194
|
const role = firstString([obj.role, obj.senderRole, obj.fromRole]) || "unknown";
|
|
@@ -128,31 +198,615 @@ function extractMessages(record) {
|
|
|
128
198
|
return output;
|
|
129
199
|
}
|
|
130
200
|
}
|
|
131
|
-
const
|
|
201
|
+
const nestedMessage = asRecord(record.message);
|
|
202
|
+
if (nestedMessage) {
|
|
203
|
+
const text = extractTextFromMessageRecord(nestedMessage);
|
|
204
|
+
if (text) {
|
|
205
|
+
return [{ role: firstString([nestedMessage.role, record.role, record.senderRole, record.fromRole]) || "unknown", text }];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const text = extractTextFromMessageRecord(record);
|
|
132
209
|
if (text) {
|
|
133
210
|
return [{ role: firstString([record.role, record.senderRole, record.fromRole]) || "unknown", text }];
|
|
134
211
|
}
|
|
135
212
|
return [];
|
|
136
213
|
}
|
|
137
214
|
function getSessionId(record, fallbackSeed) {
|
|
215
|
+
const sessionObj = asRecord(record.session);
|
|
216
|
+
const type = firstString([record.type])?.toLowerCase();
|
|
217
|
+
const typeScopedId = type === "session" ? firstString([record.id]) : undefined;
|
|
138
218
|
return (firstString([
|
|
139
219
|
record.sessionId,
|
|
140
220
|
record.session_id,
|
|
141
221
|
record.conversationId,
|
|
142
222
|
record.conversation_id,
|
|
143
|
-
|
|
223
|
+
sessionObj?.id,
|
|
224
|
+
sessionObj?.sessionId,
|
|
225
|
+
typeScopedId,
|
|
144
226
|
]) || `sync:${fallbackSeed}`);
|
|
145
227
|
}
|
|
228
|
+
function parseDailySummary(content) {
|
|
229
|
+
const normalized = content
|
|
230
|
+
.replace(/\r\n/g, "\n")
|
|
231
|
+
.split("\n")
|
|
232
|
+
.map(line => line.trim())
|
|
233
|
+
.filter(Boolean)
|
|
234
|
+
.filter(line => !line.startsWith("```"));
|
|
235
|
+
const chunks = [];
|
|
236
|
+
let current = [];
|
|
237
|
+
for (const line of normalized) {
|
|
238
|
+
const isHeader = line.startsWith("#");
|
|
239
|
+
const isBullet = /^[-*]\s+/.test(line);
|
|
240
|
+
if (isHeader && current.length > 0) {
|
|
241
|
+
chunks.push(current.join("\n"));
|
|
242
|
+
current = [];
|
|
243
|
+
}
|
|
244
|
+
current.push(line);
|
|
245
|
+
if (isBullet && current.length >= 6) {
|
|
246
|
+
chunks.push(current.join("\n"));
|
|
247
|
+
current = [];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (current.length > 0) {
|
|
251
|
+
chunks.push(current.join("\n"));
|
|
252
|
+
}
|
|
253
|
+
return chunks.map(chunk => chunk.trim()).filter(chunk => chunk.length >= 10);
|
|
254
|
+
}
|
|
255
|
+
function normalizeBaseUrl(value) {
|
|
256
|
+
if (!value)
|
|
257
|
+
return "";
|
|
258
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
259
|
+
}
|
|
260
|
+
function buildEventSnippet(text) {
|
|
261
|
+
const lines = text
|
|
262
|
+
.split(/\r?\n/)
|
|
263
|
+
.map(line => line.trim())
|
|
264
|
+
.filter(Boolean)
|
|
265
|
+
.filter(line => line.length >= 8);
|
|
266
|
+
const actionPattern = /(决定|完成|修复|阻塞|失败|成功|上线|部署|实现|依赖|owner|blocked|resolved|fixed|depends|decide|complete)/i;
|
|
267
|
+
const picked = lines.filter(line => actionPattern.test(line));
|
|
268
|
+
const use = picked.length > 0 ? picked : lines.slice(-20);
|
|
269
|
+
return use.slice(-30).join("\n").slice(-8000);
|
|
270
|
+
}
|
|
271
|
+
const WRITE_GATE_PROMPT_VERSION = "write-gate.v1.3.0";
|
|
272
|
+
const WRITE_GATE_REGRESSION_SAMPLES = [
|
|
273
|
+
"鏍蜂緥A: 鈥滀粖澶╄璁轰簡涓夌鏂规锛屽皻鏈喅绛栤€?=> active_only",
|
|
274
|
+
"鏍蜂緥B: 鈥滃喅瀹氶噰鐢˙鏂规骞跺畬鎴愪笂绾匡紝閿欒鐜囦笅闄嶅埌0.2%鈥?=> archive_event",
|
|
275
|
+
"鏍蜂緥C: 鈥滃ソ鐨勬敹鍒拌阿璋⑩€?=> skip",
|
|
276
|
+
];
|
|
277
|
+
function parseArchiveEventPayload(value) {
|
|
278
|
+
if (!value || typeof value !== "object") {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const obj = value;
|
|
282
|
+
const eventType = typeof obj.event_type === "string" ? obj.event_type.trim() : "";
|
|
283
|
+
const summary = typeof obj.summary === "string" ? obj.summary.trim() : "";
|
|
284
|
+
if (!eventType || !summary) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
const entities = Array.isArray(obj.entities)
|
|
288
|
+
? obj.entities.map(v => (typeof v === "string" ? v.trim() : "")).filter(Boolean)
|
|
289
|
+
: [];
|
|
290
|
+
const relations = Array.isArray(obj.relations)
|
|
291
|
+
? obj.relations
|
|
292
|
+
.map(valueItem => {
|
|
293
|
+
if (!valueItem || typeof valueItem !== "object")
|
|
294
|
+
return null;
|
|
295
|
+
const relation = valueItem;
|
|
296
|
+
const source = typeof relation.source === "string" ? relation.source.trim() : "";
|
|
297
|
+
const target = typeof relation.target === "string" ? relation.target.trim() : "";
|
|
298
|
+
const type = typeof relation.type === "string" ? relation.type.trim() : "related_to";
|
|
299
|
+
const evidenceSpan = typeof relation.evidence_span === "string" ? relation.evidence_span.trim() : "";
|
|
300
|
+
const confidence = typeof relation.confidence === "number"
|
|
301
|
+
? Math.max(0, Math.min(1, relation.confidence))
|
|
302
|
+
: undefined;
|
|
303
|
+
if (!source || !target)
|
|
304
|
+
return null;
|
|
305
|
+
return { source, target, type, evidence_span: evidenceSpan || undefined, confidence };
|
|
306
|
+
})
|
|
307
|
+
.filter(Boolean)
|
|
308
|
+
: [];
|
|
309
|
+
const entity_types = typeof obj.entity_types === "object" && obj.entity_types !== null && !Array.isArray(obj.entity_types)
|
|
310
|
+
? Object.fromEntries(Object.entries(obj.entity_types)
|
|
311
|
+
.filter(([key, value]) => typeof key === "string" && key.trim().length > 0 && typeof value === "string" && value.trim().length > 0)
|
|
312
|
+
.map(([key, value]) => [key.trim(), value.trim()]))
|
|
313
|
+
: undefined;
|
|
314
|
+
return {
|
|
315
|
+
event_type: eventType,
|
|
316
|
+
summary,
|
|
317
|
+
entities,
|
|
318
|
+
entity_types,
|
|
319
|
+
relations,
|
|
320
|
+
outcome: typeof obj.outcome === "string" ? obj.outcome.trim() : "",
|
|
321
|
+
confidence: typeof obj.confidence === "number" ? Math.max(0, Math.min(1, obj.confidence)) : 0.6,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function parseGraphPayload(value) {
|
|
325
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const obj = value;
|
|
329
|
+
const entities = Array.isArray(obj.entities)
|
|
330
|
+
? obj.entities.map(v => (typeof v === "string" ? v.trim() : "")).filter(Boolean)
|
|
331
|
+
: [];
|
|
332
|
+
const entity_types = typeof obj.entity_types === "object" && obj.entity_types !== null && !Array.isArray(obj.entity_types)
|
|
333
|
+
? Object.fromEntries(Object.entries(obj.entity_types)
|
|
334
|
+
.filter(([key, val]) => typeof key === "string" && key.trim() && typeof val === "string" && val.trim())
|
|
335
|
+
.map(([key, val]) => [key.trim(), val.trim()]))
|
|
336
|
+
: undefined;
|
|
337
|
+
const relations = Array.isArray(obj.relations)
|
|
338
|
+
? obj.relations
|
|
339
|
+
.map(item => {
|
|
340
|
+
if (!item || typeof item !== "object")
|
|
341
|
+
return null;
|
|
342
|
+
const rel = item;
|
|
343
|
+
const source = typeof rel.source === "string" ? rel.source.trim() : "";
|
|
344
|
+
const target = typeof rel.target === "string" ? rel.target.trim() : "";
|
|
345
|
+
const type = typeof rel.type === "string" && rel.type.trim() ? rel.type.trim() : "related_to";
|
|
346
|
+
if (!source || !target)
|
|
347
|
+
return null;
|
|
348
|
+
const evidenceSpan = typeof rel.evidence_span === "string" ? rel.evidence_span.trim() : "";
|
|
349
|
+
const confidence = typeof rel.confidence === "number"
|
|
350
|
+
? Math.max(0, Math.min(1, rel.confidence))
|
|
351
|
+
: undefined;
|
|
352
|
+
return {
|
|
353
|
+
source,
|
|
354
|
+
target,
|
|
355
|
+
type,
|
|
356
|
+
...(evidenceSpan ? { evidence_span: evidenceSpan } : {}),
|
|
357
|
+
...(typeof confidence === "number" ? { confidence } : {}),
|
|
358
|
+
};
|
|
359
|
+
})
|
|
360
|
+
.filter((item) => item !== null)
|
|
361
|
+
: [];
|
|
362
|
+
if (entities.length === 0 || relations.length === 0) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
entities,
|
|
367
|
+
entity_types,
|
|
368
|
+
relations,
|
|
369
|
+
confidence: typeof obj.confidence === "number" ? Math.max(0, Math.min(1, obj.confidence)) : undefined,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function parseLlmGateDecisions(raw, logger) {
|
|
373
|
+
const validation = (0, llm_output_validator_1.validateLlmJsonOutput)(raw);
|
|
374
|
+
if (!validation.valid) {
|
|
375
|
+
if (logger) {
|
|
376
|
+
logger.warn(`quality_gate_decisions_invalid errors=${validation.errors.join("|")}`);
|
|
377
|
+
}
|
|
378
|
+
return [];
|
|
379
|
+
}
|
|
380
|
+
if (validation.warnings.length > 0 && logger) {
|
|
381
|
+
logger.debug(`quality_gate_decisions_warnings warnings=${validation.warnings.join("|")}`);
|
|
382
|
+
}
|
|
383
|
+
const root = validation.data;
|
|
384
|
+
const parsed = Array.isArray(root) ? root : root;
|
|
385
|
+
const output = [];
|
|
386
|
+
const pushDecision = (obj, target) => {
|
|
387
|
+
let event = null;
|
|
388
|
+
if (target === "archive_event") {
|
|
389
|
+
const eventValidation = (0, llm_output_validator_1.validateArchiveEvent)(obj.event || obj);
|
|
390
|
+
if (eventValidation.valid && eventValidation.cleaned) {
|
|
391
|
+
event = {
|
|
392
|
+
event_type: eventValidation.cleaned.event_type || "insight",
|
|
393
|
+
summary: eventValidation.cleaned.summary,
|
|
394
|
+
entities: eventValidation.cleaned.entities,
|
|
395
|
+
entity_types: eventValidation.cleaned.entity_types,
|
|
396
|
+
relations: eventValidation.cleaned.relations,
|
|
397
|
+
outcome: eventValidation.cleaned.outcome || "",
|
|
398
|
+
confidence: eventValidation.cleaned.confidence,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
if (logger) {
|
|
403
|
+
logger.warn(`quality_event_invalid errors=${eventValidation.errors.join("|")}`);
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
output.push({
|
|
409
|
+
candidate_id: typeof obj.candidate_id === "string" ? obj.candidate_id.trim() : "",
|
|
410
|
+
target_layer: target,
|
|
411
|
+
active_text: typeof obj.active_text === "string" ? obj.active_text.trim() : "",
|
|
412
|
+
event: event || undefined,
|
|
413
|
+
graph: parseGraphPayload(obj.graph) || undefined,
|
|
414
|
+
reason: typeof obj.reason === "string" ? obj.reason.trim() : "",
|
|
415
|
+
});
|
|
416
|
+
};
|
|
417
|
+
if (Array.isArray(parsed)) {
|
|
418
|
+
if (logger) {
|
|
419
|
+
logger.warn("quality_gate_decisions_invalid format=array_not_supported_require_routing_plan");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else if (parsed && typeof parsed === "object") {
|
|
423
|
+
const rootObj = parsed;
|
|
424
|
+
const routingPlan = (typeof rootObj.routing_plan === "object" && rootObj.routing_plan !== null)
|
|
425
|
+
? rootObj.routing_plan
|
|
426
|
+
: null;
|
|
427
|
+
if (routingPlan) {
|
|
428
|
+
const buckets = [
|
|
429
|
+
{ key: "archive_event", items: routingPlan.archive_event },
|
|
430
|
+
{ key: "active_only", items: routingPlan.active_only },
|
|
431
|
+
{ key: "skip", items: routingPlan.skip },
|
|
432
|
+
];
|
|
433
|
+
for (const bucket of buckets) {
|
|
434
|
+
if (!Array.isArray(bucket.items))
|
|
435
|
+
continue;
|
|
436
|
+
for (const item of bucket.items) {
|
|
437
|
+
if (!item || typeof item !== "object")
|
|
438
|
+
continue;
|
|
439
|
+
pushDecision(item, bucket.key);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else if (logger) {
|
|
444
|
+
logger.warn("quality_gate_decisions_invalid missing_routing_plan");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (output.length === 0 && logger) {
|
|
448
|
+
logger.warn("quality_gate_decisions_empty");
|
|
449
|
+
}
|
|
450
|
+
const deduped = [];
|
|
451
|
+
const seen = new Set();
|
|
452
|
+
for (const item of output) {
|
|
453
|
+
const key = `${item.target_layer}|${item.event?.summary || item.active_text || item.reason || ""}`;
|
|
454
|
+
if (seen.has(key))
|
|
455
|
+
continue;
|
|
456
|
+
seen.add(key);
|
|
457
|
+
deduped.push(item);
|
|
458
|
+
}
|
|
459
|
+
return deduped;
|
|
460
|
+
}
|
|
461
|
+
async function extractGateDecisionsWithLlm(args) {
|
|
462
|
+
const endpoint = args.llm.baseUrl.endsWith("/chat/completions")
|
|
463
|
+
? args.llm.baseUrl
|
|
464
|
+
: `${args.llm.baseUrl}/chat/completions`;
|
|
465
|
+
const body = {
|
|
466
|
+
model: args.llm.model,
|
|
467
|
+
temperature: 0.1,
|
|
468
|
+
response_format: { type: "json_object" },
|
|
469
|
+
messages: [
|
|
470
|
+
{ role: "system", content: "You are a memory write-gate router. Output JSON only." },
|
|
471
|
+
{
|
|
472
|
+
role: "user",
|
|
473
|
+
content: [
|
|
474
|
+
`prompt_version=${WRITE_GATE_PROMPT_VERSION}`,
|
|
475
|
+
"Execute in 3 stages:",
|
|
476
|
+
"Stage1) Split transcript into candidate_events[]. One candidate should contain one principal event only.",
|
|
477
|
+
"Stage2) Route each candidate_event into target_layer: active_only | archive_event | skip.",
|
|
478
|
+
"Stage3) Output routing plan only. Do NOT claim data has been written.",
|
|
479
|
+
"Classification:",
|
|
480
|
+
"A) active_only: process context, ongoing discussion, temporary status, no stable conclusion.",
|
|
481
|
+
"B) archive_event: reusable event with clear subject + action/decision + outcome/phase conclusion.",
|
|
482
|
+
"C) skip: noise/repetition/chitchat/no clear business value.",
|
|
483
|
+
"Constraints:",
|
|
484
|
+
"- For archive_event: if confidence < 0.35, prefer skip.",
|
|
485
|
+
"- Relations must be grounded in source text. Do not fabricate.",
|
|
486
|
+
"- For active_only: active_text is required.",
|
|
487
|
+
"- Optional graph for active_only: graph={entities[],entity_types,relations[],confidence}.",
|
|
488
|
+
"- For relations: each relation should include source,target,type,evidence_span,confidence.",
|
|
489
|
+
"- If evidence_span or confidence is missing, do not output that relation.",
|
|
490
|
+
"Output JSON schema:",
|
|
491
|
+
"{\"candidate_events\":[{\"candidate_id\":\"c1\",\"span\":\"...\",\"normalized_text\":\"...\"}],\"routing_plan\":{\"archive_event\":[{\"candidate_id\":\"c1\",\"event\":{\"event_type\":\"decision\",\"summary\":\"...\",\"entities\":[\"A\"],\"entity_types\":{\"A\":\"Project\"},\"relations\":[],\"outcome\":\"...\",\"confidence\":0.82}}],\"active_only\":[],\"skip\":[]}}",
|
|
492
|
+
"routing_plan.archive_event[] item: {candidate_id,event}",
|
|
493
|
+
"routing_plan.active_only[] item: {candidate_id,active_text,graph}",
|
|
494
|
+
"routing_plan.skip[] item: {candidate_id,reason}",
|
|
495
|
+
...WRITE_GATE_REGRESSION_SAMPLES,
|
|
496
|
+
"Output JSON only.",
|
|
497
|
+
"",
|
|
498
|
+
buildEventSnippet(args.transcript),
|
|
499
|
+
].join("\n"),
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
};
|
|
503
|
+
let lastError = null;
|
|
504
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
505
|
+
const response = await (0, http_post_1.postJsonWithTimeout)({
|
|
506
|
+
endpoint,
|
|
507
|
+
apiKey: args.llm.apiKey,
|
|
508
|
+
body,
|
|
509
|
+
timeoutMs: 25000,
|
|
510
|
+
});
|
|
511
|
+
if (!response.ok) {
|
|
512
|
+
lastError = new Error(response.status > 0 ? `sync_llm_http_${response.status}` : (response.error || "sync_llm_network_error"));
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
const json = (response.json || {});
|
|
517
|
+
const content = json?.choices?.[0]?.message?.content || "";
|
|
518
|
+
if (!content.trim()) {
|
|
519
|
+
lastError = new Error("sync_llm_empty");
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
return parseLlmGateDecisions(content, args.logger);
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
lastError = error;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
args.logger.warn(`Sync LLM extraction failed: ${String(lastError || "unknown")}`);
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
146
531
|
function createSessionSync(options) {
|
|
147
532
|
const memoryRoot = options.dbPath ? path.resolve(options.dbPath) : path.join(options.projectRoot, "data", "memory");
|
|
148
533
|
const statePath = path.join(memoryRoot, ".sync_state.json");
|
|
149
534
|
const openclawBasePath = inferOpenclawBasePath(options.projectRoot);
|
|
535
|
+
const llmModel = options.llm?.model || "";
|
|
536
|
+
const llmApiKey = options.llm?.apiKey || "";
|
|
537
|
+
const llmBaseUrl = normalizeBaseUrl(options.llm?.baseURL || options.llm?.baseUrl);
|
|
538
|
+
const requireLlmForWrite = options.requireLlmForWrite !== false;
|
|
539
|
+
options.logger.info(`sync_gate_prompt_version=${WRITE_GATE_PROMPT_VERSION}`);
|
|
540
|
+
if (!fs.existsSync(statePath)) {
|
|
541
|
+
options.logger.warn("sync_state_missing: deleting state file triggers full re-import");
|
|
542
|
+
}
|
|
543
|
+
async function storeFromTranscript(args) {
|
|
544
|
+
const skipReasons = {};
|
|
545
|
+
const activeTextMaxChars = typeof options.writePolicy?.activeTextMaxChars === "number"
|
|
546
|
+
? Math.max(500, Math.min(20000, Math.floor(options.writePolicy.activeTextMaxChars)))
|
|
547
|
+
: 4000;
|
|
548
|
+
const archiveSourceTextMaxChars = typeof options.writePolicy?.archiveSourceTextMaxChars === "number"
|
|
549
|
+
? Math.max(1000, Math.min(30000, Math.floor(options.writePolicy.archiveSourceTextMaxChars)))
|
|
550
|
+
: 8000;
|
|
551
|
+
function bumpReason(reason) {
|
|
552
|
+
const key = reason || "unknown";
|
|
553
|
+
skipReasons[key] = (skipReasons[key] || 0) + 1;
|
|
554
|
+
}
|
|
555
|
+
if (!args.transcript.trim()) {
|
|
556
|
+
options.logger.info(`sync_skip reason=no_active_records session=${args.sessionId}`);
|
|
557
|
+
bumpReason("no_active_records");
|
|
558
|
+
return { imported: 0, skipped: 1, ok: true, llmDecisions: 0, activeOnly: 0, archiveEvent: 0, skipReasons };
|
|
559
|
+
}
|
|
560
|
+
if (!llmModel || !llmApiKey || !llmBaseUrl) {
|
|
561
|
+
if (requireLlmForWrite) {
|
|
562
|
+
options.logger.warn(`sync_skip reason=llm_not_configured session=${args.sessionId}`);
|
|
563
|
+
bumpReason("llm_not_configured");
|
|
564
|
+
return { imported: 0, skipped: 1, ok: false, llmDecisions: 0, activeOnly: 0, archiveEvent: 0, skipReasons };
|
|
565
|
+
}
|
|
566
|
+
options.logger.warn(`Sync gate degraded to active_only for ${args.sessionId}: llm_not_configured`);
|
|
567
|
+
const fallbackWrite = await options.writeStore.writeMemory({
|
|
568
|
+
text: args.transcript.slice(-activeTextMaxChars),
|
|
569
|
+
role: "system",
|
|
570
|
+
source: `sync_gate_fallback:${args.sourceFile}`,
|
|
571
|
+
sessionId: args.sessionId,
|
|
572
|
+
});
|
|
573
|
+
if (fallbackWrite.status === "ok") {
|
|
574
|
+
return { imported: 1, skipped: 0, ok: true, llmDecisions: 1, activeOnly: 1, archiveEvent: 0, skipReasons };
|
|
575
|
+
}
|
|
576
|
+
bumpReason(fallbackWrite.reason || "active_only_fallback_failed");
|
|
577
|
+
return { imported: 0, skipped: 1, ok: false, llmDecisions: 1, activeOnly: 0, archiveEvent: 0, skipReasons };
|
|
578
|
+
}
|
|
579
|
+
const decisions = await extractGateDecisionsWithLlm({
|
|
580
|
+
llm: { model: llmModel, apiKey: llmApiKey, baseUrl: llmBaseUrl },
|
|
581
|
+
transcript: args.transcript,
|
|
582
|
+
logger: options.logger,
|
|
583
|
+
});
|
|
584
|
+
if (decisions.length === 0) {
|
|
585
|
+
options.logger.info(`sync_skip reason=llm_extract_empty session=${args.sessionId}`);
|
|
586
|
+
bumpReason("llm_extract_empty");
|
|
587
|
+
return { imported: 0, skipped: 1, ok: true, llmDecisions: 0, activeOnly: 0, archiveEvent: 0, skipReasons };
|
|
588
|
+
}
|
|
589
|
+
let llmDecisions = 0;
|
|
590
|
+
let imported = 0;
|
|
591
|
+
let skipped = 0;
|
|
592
|
+
let activeOnly = 0;
|
|
593
|
+
let archiveEvent = 0;
|
|
594
|
+
let activeAttempted = 0;
|
|
595
|
+
let archiveAttempted = 0;
|
|
596
|
+
let graphAttempted = 0;
|
|
597
|
+
let graphStored = 0;
|
|
598
|
+
let graphSkipped = 0;
|
|
599
|
+
const archiveInputs = [];
|
|
600
|
+
for (const decision of decisions) {
|
|
601
|
+
llmDecisions += 1;
|
|
602
|
+
if (decision.target_layer === "skip") {
|
|
603
|
+
skipped += 1;
|
|
604
|
+
bumpReason(decision.reason || "llm_gate_skip");
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
if (decision.target_layer === "active_only") {
|
|
608
|
+
activeAttempted += 1;
|
|
609
|
+
const activeText = (decision.active_text || args.transcript).trim().slice(-activeTextMaxChars);
|
|
610
|
+
if (!activeText) {
|
|
611
|
+
skipped += 1;
|
|
612
|
+
bumpReason("active_only_empty");
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
const writeResult = await options.writeStore.writeMemory({
|
|
616
|
+
text: activeText,
|
|
617
|
+
role: "system",
|
|
618
|
+
source: `sync_gate_active:${args.sourceFile}`,
|
|
619
|
+
sessionId: args.sessionId,
|
|
620
|
+
});
|
|
621
|
+
if (writeResult.status === "ok") {
|
|
622
|
+
imported += 1;
|
|
623
|
+
activeOnly += 1;
|
|
624
|
+
if (options.graphMemoryStore && decision.graph) {
|
|
625
|
+
graphAttempted += 1;
|
|
626
|
+
const relationFingerprint = (decision.graph.relations || [])
|
|
627
|
+
.map(rel => `${rel.source}|${rel.type}|${rel.target}|${rel.evidence_span || ""}`)
|
|
628
|
+
.sort()
|
|
629
|
+
.join("||");
|
|
630
|
+
const activeSourceEventId = `active:${args.sessionId}:${crypto.createHash("sha1").update(relationFingerprint || activeText).digest("hex").slice(0, 16)}`;
|
|
631
|
+
const graphResult = await options.graphMemoryStore.append({
|
|
632
|
+
sourceEventId: activeSourceEventId,
|
|
633
|
+
sourceLayer: "active_only",
|
|
634
|
+
sessionId: args.sessionId,
|
|
635
|
+
sourceFile: args.sourceFile,
|
|
636
|
+
eventType: "insight",
|
|
637
|
+
entities: decision.graph.entities,
|
|
638
|
+
entity_types: decision.graph.entity_types,
|
|
639
|
+
relations: decision.graph.relations,
|
|
640
|
+
gateSource: "sync",
|
|
641
|
+
confidence: decision.graph.confidence,
|
|
642
|
+
sourceText: activeText,
|
|
643
|
+
});
|
|
644
|
+
if (!graphResult.success) {
|
|
645
|
+
graphSkipped += 1;
|
|
646
|
+
options.logger.info(`graph_skip_reason=${graphResult.reason} source_event_id=${activeSourceEventId}`);
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
graphStored += 1;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
skipped += 1;
|
|
655
|
+
bumpReason(writeResult.reason || "active_only_write_skipped");
|
|
656
|
+
}
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (decision.target_layer === "archive_event") {
|
|
660
|
+
archiveAttempted += 1;
|
|
661
|
+
if (!decision.event) {
|
|
662
|
+
skipped += 1;
|
|
663
|
+
bumpReason("archive_event_missing_payload");
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
archiveInputs.push({
|
|
667
|
+
event_type: decision.event.event_type,
|
|
668
|
+
summary: decision.event.summary,
|
|
669
|
+
entities: decision.event.entities,
|
|
670
|
+
relations: decision.event.relations,
|
|
671
|
+
entity_types: decision.event.entity_types,
|
|
672
|
+
outcome: decision.event.outcome,
|
|
673
|
+
confidence: decision.event.confidence,
|
|
674
|
+
session_id: args.sessionId,
|
|
675
|
+
source_file: args.sourceFile,
|
|
676
|
+
source_text: args.transcript.slice(-archiveSourceTextMaxChars),
|
|
677
|
+
source_event_id: decision.candidate_id
|
|
678
|
+
? `candidate:${args.sessionId}:${decision.candidate_id}`
|
|
679
|
+
: `candidate:${args.sessionId}:${crypto.createHash("sha1").update(decision.event.summary).digest("hex").slice(0, 16)}`,
|
|
680
|
+
actor: "sync_llm_gate",
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (archiveInputs.length > 0) {
|
|
685
|
+
let archivedSuccess = 0;
|
|
686
|
+
let archivedSkipped = 0;
|
|
687
|
+
for (const inputRecord of archiveInputs) {
|
|
688
|
+
const archiveResult = await options.archiveStore.storeEvents([inputRecord]);
|
|
689
|
+
imported += archiveResult.stored.length;
|
|
690
|
+
skipped += archiveResult.skipped.length;
|
|
691
|
+
archiveEvent += archiveResult.stored.length;
|
|
692
|
+
archivedSuccess += archiveResult.stored.length;
|
|
693
|
+
archivedSkipped += archiveResult.skipped.length;
|
|
694
|
+
for (const skip of archiveResult.skipped) {
|
|
695
|
+
bumpReason(skip.reason || "archive_store_skipped");
|
|
696
|
+
}
|
|
697
|
+
const archiveRecord = archiveResult.stored[0];
|
|
698
|
+
if (!archiveRecord) {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (!options.graphMemoryStore) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
graphAttempted += 1;
|
|
705
|
+
const graphResult = await options.graphMemoryStore.append({
|
|
706
|
+
// Graph trace points to persisted archive record id for stable lookup.
|
|
707
|
+
sourceEventId: archiveRecord.id,
|
|
708
|
+
sourceLayer: "archive_event",
|
|
709
|
+
archiveEventId: archiveRecord.id,
|
|
710
|
+
sessionId: args.sessionId,
|
|
711
|
+
sourceFile: args.sourceFile,
|
|
712
|
+
eventType: inputRecord.event_type,
|
|
713
|
+
entities: inputRecord.entities,
|
|
714
|
+
entity_types: inputRecord.entity_types,
|
|
715
|
+
relations: inputRecord.relations,
|
|
716
|
+
gateSource: "sync",
|
|
717
|
+
confidence: inputRecord.confidence,
|
|
718
|
+
sourceText: args.transcript,
|
|
719
|
+
});
|
|
720
|
+
if (!graphResult.success) {
|
|
721
|
+
graphSkipped += 1;
|
|
722
|
+
options.logger.info(`graph_skip_reason=${graphResult.reason} source_event_id=${archiveRecord.id}`);
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
graphStored += 1;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
options.logger.info(`sync_archive_result session=${args.sessionId} archived_success=${archivedSuccess} skipped=${archivedSkipped}`);
|
|
729
|
+
}
|
|
730
|
+
options.logger.info(`sync_gate_result session=${args.sessionId} llm_decisions=${llmDecisions} active_only=${activeOnly} archive_event=${archiveEvent} skipped=${skipped}`);
|
|
731
|
+
options.logger.info(`sync_gate_metrics session=${args.sessionId} active_attempted=${activeAttempted} archive_attempted=${archiveAttempted} graph_attempted=${graphAttempted} graph_stored=${graphStored} graph_skipped=${graphSkipped} skip_reason_kinds=${Object.keys(skipReasons).length}`);
|
|
732
|
+
return {
|
|
733
|
+
imported,
|
|
734
|
+
skipped,
|
|
735
|
+
ok: true,
|
|
736
|
+
llmDecisions,
|
|
737
|
+
activeOnly,
|
|
738
|
+
archiveEvent,
|
|
739
|
+
skipReasons,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
async function syncDailySummaries() {
|
|
743
|
+
const files = gatherDailySummaryFiles(openclawBasePath);
|
|
744
|
+
const state = readState(statePath);
|
|
745
|
+
if (!state.markdowns || typeof state.markdowns !== "object") {
|
|
746
|
+
state.markdowns = {};
|
|
747
|
+
}
|
|
748
|
+
let imported = 0;
|
|
749
|
+
let skipped = 0;
|
|
750
|
+
let filesProcessed = 0;
|
|
751
|
+
let llmDecisions = 0;
|
|
752
|
+
let activeOnly = 0;
|
|
753
|
+
let archiveEvent = 0;
|
|
754
|
+
const skipReasons = {};
|
|
755
|
+
for (const filePath of files) {
|
|
756
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
760
|
+
const digest = crypto.createHash("sha1").update(content).digest("hex");
|
|
761
|
+
const prev = state.markdowns[filePath];
|
|
762
|
+
if (prev && prev.digest === digest) {
|
|
763
|
+
skipped += 1;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
const chunks = parseDailySummary(content);
|
|
767
|
+
if (chunks.length === 0) {
|
|
768
|
+
state.markdowns[filePath] = { digest, importedAt: new Date().toISOString() };
|
|
769
|
+
skipped += 1;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
const summarySessionId = `daily_summary:${path.basename(filePath)}`;
|
|
773
|
+
const transcript = chunks.join("\n");
|
|
774
|
+
const result = await storeFromTranscript({
|
|
775
|
+
sessionId: summarySessionId,
|
|
776
|
+
sourceFile: `daily_summary_sync:${path.basename(filePath)}`,
|
|
777
|
+
transcript,
|
|
778
|
+
});
|
|
779
|
+
imported += result.imported;
|
|
780
|
+
skipped += result.skipped;
|
|
781
|
+
llmDecisions += result.llmDecisions;
|
|
782
|
+
activeOnly += result.activeOnly;
|
|
783
|
+
archiveEvent += result.archiveEvent;
|
|
784
|
+
for (const [key, count] of Object.entries(result.skipReasons)) {
|
|
785
|
+
skipReasons[key] = (skipReasons[key] || 0) + count;
|
|
786
|
+
}
|
|
787
|
+
if (!result.ok) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
state.markdowns[filePath] = { digest, importedAt: new Date().toISOString() };
|
|
791
|
+
filesProcessed += 1;
|
|
792
|
+
}
|
|
793
|
+
writeState(statePath, state);
|
|
794
|
+
options.logger.info(`TS daily summary sync completed: imported=${imported}, skipped=${skipped}, files=${filesProcessed}`);
|
|
795
|
+
return { imported, skipped, filesProcessed, llmDecisions, activeOnly, archiveEvent, skipReasons };
|
|
796
|
+
}
|
|
150
797
|
async function syncMemory() {
|
|
151
798
|
const files = gatherSessionFiles(openclawBasePath, memoryRoot);
|
|
799
|
+
if (files.length === 0) {
|
|
800
|
+
options.logger.info("sync_skip reason=no_active_records");
|
|
801
|
+
}
|
|
152
802
|
const state = readState(statePath);
|
|
153
803
|
let imported = 0;
|
|
154
804
|
let skipped = 0;
|
|
155
805
|
let filesProcessed = 0;
|
|
806
|
+
let llmDecisions = 0;
|
|
807
|
+
let activeOnly = 0;
|
|
808
|
+
let archiveEvent = 0;
|
|
809
|
+
const skipReasons = {};
|
|
156
810
|
for (const filePath of files) {
|
|
157
811
|
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
158
812
|
continue;
|
|
@@ -169,32 +823,34 @@ function createSessionSync(options) {
|
|
|
169
823
|
state.files[filePath] = { size: stat.size, lineCount: lines.length };
|
|
170
824
|
continue;
|
|
171
825
|
}
|
|
826
|
+
const bySession = new Map();
|
|
827
|
+
let fileHasFailure = false;
|
|
828
|
+
const fileSessionSeed = path.basename(filePath, path.extname(filePath));
|
|
829
|
+
let fileSessionId;
|
|
172
830
|
for (let i = startIndex; i < lines.length; i++) {
|
|
173
831
|
const line = lines[i].trim();
|
|
174
832
|
if (!line)
|
|
175
833
|
continue;
|
|
176
|
-
const hash = crypto.createHash("sha1").update(line).digest("hex").slice(0, 12);
|
|
177
834
|
try {
|
|
178
835
|
const record = JSON.parse(line);
|
|
836
|
+
if (!fileSessionId) {
|
|
837
|
+
const inferred = getSessionId(record, fileSessionSeed);
|
|
838
|
+
if (inferred && inferred !== `sync:${fileSessionSeed}`) {
|
|
839
|
+
fileSessionId = inferred;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
179
842
|
const messages = extractMessages(record);
|
|
180
843
|
if (messages.length === 0) {
|
|
181
844
|
skipped += 1;
|
|
182
845
|
continue;
|
|
183
846
|
}
|
|
184
|
-
const
|
|
847
|
+
const fallbackSession = fileSessionId || fileSessionSeed;
|
|
848
|
+
const sessionId = getSessionId(record, fallbackSession);
|
|
185
849
|
for (const msg of messages) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
role: msg.role,
|
|
189
|
-
source: "sync",
|
|
190
|
-
sessionId,
|
|
191
|
-
});
|
|
192
|
-
if (result.status === "ok") {
|
|
193
|
-
imported += 1;
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
skipped += 1;
|
|
850
|
+
if (!bySession.has(sessionId)) {
|
|
851
|
+
bySession.set(sessionId, []);
|
|
197
852
|
}
|
|
853
|
+
bySession.get(sessionId)?.push(`[${msg.role}] ${msg.text}`);
|
|
198
854
|
}
|
|
199
855
|
}
|
|
200
856
|
catch (error) {
|
|
@@ -202,13 +858,54 @@ function createSessionSync(options) {
|
|
|
202
858
|
skipped += 1;
|
|
203
859
|
}
|
|
204
860
|
}
|
|
861
|
+
for (const [sessionId, messages] of bySession.entries()) {
|
|
862
|
+
const transcript = messages.join("\n");
|
|
863
|
+
const result = await storeFromTranscript({
|
|
864
|
+
sessionId,
|
|
865
|
+
sourceFile: `sync:${path.basename(filePath)}`,
|
|
866
|
+
transcript,
|
|
867
|
+
});
|
|
868
|
+
imported += result.imported;
|
|
869
|
+
skipped += result.skipped;
|
|
870
|
+
llmDecisions += result.llmDecisions;
|
|
871
|
+
activeOnly += result.activeOnly;
|
|
872
|
+
archiveEvent += result.archiveEvent;
|
|
873
|
+
for (const [key, count] of Object.entries(result.skipReasons)) {
|
|
874
|
+
skipReasons[key] = (skipReasons[key] || 0) + count;
|
|
875
|
+
}
|
|
876
|
+
if (!result.ok) {
|
|
877
|
+
fileHasFailure = true;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
205
880
|
filesProcessed += 1;
|
|
206
|
-
|
|
881
|
+
if (!fileHasFailure) {
|
|
882
|
+
state.files[filePath] = { size: stat.size, lineCount: lines.length };
|
|
883
|
+
}
|
|
207
884
|
}
|
|
208
885
|
writeState(statePath, state);
|
|
209
|
-
|
|
210
|
-
|
|
886
|
+
const summary = await syncDailySummaries();
|
|
887
|
+
llmDecisions += summary.llmDecisions;
|
|
888
|
+
activeOnly += summary.activeOnly;
|
|
889
|
+
archiveEvent += summary.archiveEvent;
|
|
890
|
+
for (const [key, count] of Object.entries(summary.skipReasons)) {
|
|
891
|
+
skipReasons[key] = (skipReasons[key] || 0) + count;
|
|
892
|
+
}
|
|
893
|
+
options.logger.info(`TS sync completed: imported=${imported}, skipped=${skipped}, files=${filesProcessed}, summaryImported=${summary.imported}, summarySkipped=${summary.skipped}, llmDecisions=${llmDecisions}, activeOnly=${activeOnly}, archiveEvent=${archiveEvent}`);
|
|
894
|
+
return {
|
|
895
|
+
imported,
|
|
896
|
+
skipped,
|
|
897
|
+
filesProcessed,
|
|
898
|
+
summaryImported: summary.imported,
|
|
899
|
+
summarySkipped: summary.skipped,
|
|
900
|
+
llmDecisions,
|
|
901
|
+
activeOnly,
|
|
902
|
+
archiveEvent,
|
|
903
|
+
skipReasons,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
async function routeTranscript(args) {
|
|
907
|
+
return storeFromTranscript(args);
|
|
211
908
|
}
|
|
212
|
-
return { syncMemory };
|
|
909
|
+
return { syncMemory, syncDailySummaries, routeTranscript };
|
|
213
910
|
}
|
|
214
911
|
//# sourceMappingURL=session_sync.js.map
|