openclaw-cortex-memory 0.1.0-Alpha.2 → 0.1.0-Alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -203
- package/SKILL.md +71 -268
- package/dist/index.d.ts +88 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +859 -1189
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +362 -14
- package/dist/src/dedup/three_stage_deduplicator.d.ts +25 -0
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -0
- package/dist/src/dedup/three_stage_deduplicator.js +224 -0
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -0
- package/dist/src/engine/memory_engine.d.ts +2 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +126 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +1172 -44
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +12 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +103 -0
- package/dist/src/graph/ontology.d.ts.map +1 -0
- package/dist/src/graph/ontology.js +564 -0
- package/dist/src/graph/ontology.js.map +1 -0
- package/dist/src/quality/llm_output_validator.d.ts +48 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +404 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts +7 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +358 -8
- package/dist/src/reflect/reflector.js.map +1 -1
- package/dist/src/rules/rule_store.d.ts.map +1 -1
- package/dist/src/rules/rule_store.js +75 -16
- package/dist/src/rules/rule_store.js.map +1 -1
- package/dist/src/session/session_end.d.ts +33 -0
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +67 -64
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +128 -0
- package/dist/src/store/archive_store.d.ts.map +1 -0
- package/dist/src/store/archive_store.js +481 -0
- package/dist/src/store/archive_store.js.map +1 -0
- package/dist/src/store/embedding_utils.d.ts +32 -0
- package/dist/src/store/embedding_utils.d.ts.map +1 -0
- package/dist/src/store/embedding_utils.js +173 -0
- package/dist/src/store/embedding_utils.js.map +1 -0
- package/dist/src/store/graph_memory_store.d.ts +44 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +168 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +86 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1681 -25
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +44 -0
- package/dist/src/store/vector_store.d.ts.map +1 -0
- package/dist/src/store/vector_store.js +201 -0
- package/dist/src/store/vector_store.js.map +1 -0
- package/dist/src/store/write_store.d.ts +52 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +245 -3
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +100 -2
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +673 -22
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/utils/runtime_env.d.ts +4 -0
- package/dist/src/utils/runtime_env.d.ts.map +1 -0
- package/dist/src/utils/runtime_env.js +51 -0
- package/dist/src/utils/runtime_env.js.map +1 -0
- package/openclaw.plugin.json +362 -14
- package/package.json +23 -6
- package/scripts/cli.js +19 -14
- package/scripts/uninstall.js +22 -5
- package/index.ts +0 -2092
- package/scripts/install.js +0 -27
package/dist/index.js
CHANGED
|
@@ -39,28 +39,21 @@ exports.getStatus = getStatus;
|
|
|
39
39
|
exports.unregister = unregister;
|
|
40
40
|
exports.register = register;
|
|
41
41
|
/// <reference types="node" />
|
|
42
|
-
const child_process_1 = require("child_process");
|
|
43
42
|
const path = __importStar(require("path"));
|
|
44
43
|
const fs = __importStar(require("fs"));
|
|
45
|
-
const net = __importStar(require("net"));
|
|
46
44
|
const ts_engine_1 = require("./src/engine/ts_engine");
|
|
47
45
|
const read_store_1 = require("./src/store/read_store");
|
|
48
46
|
const write_store_1 = require("./src/store/write_store");
|
|
47
|
+
const archive_store_1 = require("./src/store/archive_store");
|
|
48
|
+
const vector_store_1 = require("./src/store/vector_store");
|
|
49
|
+
const graph_memory_store_1 = require("./src/store/graph_memory_store");
|
|
49
50
|
const session_sync_1 = require("./src/sync/session_sync");
|
|
50
51
|
const session_end_1 = require("./src/session/session_end");
|
|
51
52
|
const rule_store_1 = require("./src/rules/rule_store");
|
|
52
53
|
const reflector_1 = require("./src/reflect/reflector");
|
|
54
|
+
const three_stage_deduplicator_1 = require("./src/dedup/three_stage_deduplicator");
|
|
55
|
+
const runtime_env_1 = require("./src/utils/runtime_env");
|
|
53
56
|
const ERROR_CODES = {
|
|
54
|
-
CONNECTION_REFUSED: {
|
|
55
|
-
code: "E001",
|
|
56
|
-
message: "Cannot connect to the memory service",
|
|
57
|
-
suggestion: "The Python backend may not be running. Try restarting the OpenClaw gateway."
|
|
58
|
-
},
|
|
59
|
-
TIMEOUT: {
|
|
60
|
-
code: "E002",
|
|
61
|
-
message: "The memory service is not responding",
|
|
62
|
-
suggestion: "The service may be overloaded. Wait a moment and try again."
|
|
63
|
-
},
|
|
64
57
|
NOT_FOUND: {
|
|
65
58
|
code: "E003",
|
|
66
59
|
message: "Memory not found",
|
|
@@ -71,11 +64,6 @@ const ERROR_CODES = {
|
|
|
71
64
|
message: "Invalid input provided",
|
|
72
65
|
suggestion: "Please check your input parameters and try again."
|
|
73
66
|
},
|
|
74
|
-
SERVICE_ERROR: {
|
|
75
|
-
code: "E005",
|
|
76
|
-
message: "The memory service encountered an error",
|
|
77
|
-
suggestion: "Check the service logs for details or try restarting the gateway."
|
|
78
|
-
},
|
|
79
67
|
PLUGIN_DISABLED: {
|
|
80
68
|
code: "E006",
|
|
81
69
|
message: "Cortex Memory plugin is disabled",
|
|
@@ -88,23 +76,118 @@ const MIN_OPENCLAW_VERSION = "2026.3.8";
|
|
|
88
76
|
const MAX_OPENCLAW_VERSION = "2027.0.0";
|
|
89
77
|
const defaultConfig = {
|
|
90
78
|
autoSync: true,
|
|
79
|
+
llmRequiredForWrite: true,
|
|
91
80
|
autoReflect: false,
|
|
81
|
+
autoReflectIntervalMinutes: 30,
|
|
82
|
+
graphQualityMode: "warn",
|
|
83
|
+
readFusion: {
|
|
84
|
+
enabled: true,
|
|
85
|
+
maxCandidates: 10,
|
|
86
|
+
authoritative: true,
|
|
87
|
+
channelWeights: {
|
|
88
|
+
rules: 1,
|
|
89
|
+
archive: 1.15,
|
|
90
|
+
vector: 1.2,
|
|
91
|
+
graph: 1,
|
|
92
|
+
},
|
|
93
|
+
channelTopK: {
|
|
94
|
+
rules: 8,
|
|
95
|
+
archive: 20,
|
|
96
|
+
vector: 20,
|
|
97
|
+
graph: 12,
|
|
98
|
+
},
|
|
99
|
+
minLexicalHits: 1,
|
|
100
|
+
minSemanticHits: 1,
|
|
101
|
+
lengthNorm: {
|
|
102
|
+
enabled: true,
|
|
103
|
+
pivotChars: 1200,
|
|
104
|
+
strength: 0.75,
|
|
105
|
+
minFactor: 0.45,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
vectorChunking: {
|
|
109
|
+
chunkSize: 600,
|
|
110
|
+
chunkOverlap: 100,
|
|
111
|
+
evidenceMaxChunks: 2,
|
|
112
|
+
},
|
|
113
|
+
writePolicy: {
|
|
114
|
+
archiveMinConfidence: 0.35,
|
|
115
|
+
archiveMinQualityScore: 0.4,
|
|
116
|
+
activeMinQualityScore: 0.45,
|
|
117
|
+
activeDedupTailLines: 200,
|
|
118
|
+
activeTextMaxChars: 4000,
|
|
119
|
+
archiveSourceTextMaxChars: 8000,
|
|
120
|
+
},
|
|
121
|
+
memoryDecay: {
|
|
122
|
+
enabled: true,
|
|
123
|
+
minFloor: 0.15,
|
|
124
|
+
defaultHalfLifeDays: 90,
|
|
125
|
+
antiDecay: {
|
|
126
|
+
enabled: true,
|
|
127
|
+
maxBoost: 1.6,
|
|
128
|
+
hitWeight: 0.08,
|
|
129
|
+
recentWindowDays: 30,
|
|
130
|
+
},
|
|
131
|
+
halfLifeByEventType: {
|
|
132
|
+
issue: 30,
|
|
133
|
+
fix: 30,
|
|
134
|
+
action_item: 30,
|
|
135
|
+
blocker: 30,
|
|
136
|
+
plan: 60,
|
|
137
|
+
milestone: 60,
|
|
138
|
+
follow_up: 60,
|
|
139
|
+
decision: 120,
|
|
140
|
+
insight: 120,
|
|
141
|
+
retrospective: 120,
|
|
142
|
+
preference: 240,
|
|
143
|
+
constraint: 240,
|
|
144
|
+
requirement: 240,
|
|
145
|
+
dependency: 240,
|
|
146
|
+
assumption: 240,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
readTuning: {
|
|
150
|
+
scoring: {
|
|
151
|
+
lexicalWeight: 0.2,
|
|
152
|
+
bm25Scale: 2,
|
|
153
|
+
semanticWeight: 0.3,
|
|
154
|
+
recencyWeight: 0.1,
|
|
155
|
+
qualityWeight: 0.15,
|
|
156
|
+
typeMatchWeight: 0.15,
|
|
157
|
+
graphMatchWeight: 0.1,
|
|
158
|
+
},
|
|
159
|
+
rrf: {
|
|
160
|
+
k: 60,
|
|
161
|
+
weight: 1.5,
|
|
162
|
+
},
|
|
163
|
+
recency: {
|
|
164
|
+
buckets: [
|
|
165
|
+
{ maxAgeHours: 12, score: 1, bonus: 0.6 },
|
|
166
|
+
{ maxAgeHours: 24, score: 0.8, bonus: 0.6 },
|
|
167
|
+
{ maxAgeHours: 72, score: 0.6, bonus: 0.3 },
|
|
168
|
+
{ maxAgeHours: 168, score: 0.4, bonus: 0.3 },
|
|
169
|
+
{ maxAgeHours: 720, score: 0.2, bonus: 0 },
|
|
170
|
+
{ maxAgeHours: Number.POSITIVE_INFINITY, score: 0.05, bonus: 0 },
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
autoContext: {
|
|
174
|
+
queryMaxChars: 80,
|
|
175
|
+
lightweightSearch: true,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
92
178
|
enabled: true,
|
|
93
|
-
fallbackToBuiltin: true,
|
|
94
|
-
engineMode: "ts",
|
|
95
179
|
};
|
|
96
180
|
let autoSearchCacheBySession = new Map();
|
|
97
181
|
const AUTO_SEARCH_CACHE_TTL = 60000;
|
|
98
182
|
const MAX_AUTO_SEARCH_CACHE_SESSIONS = 200;
|
|
183
|
+
const HOOK_GUARD_TIMEOUT_MS = 2000;
|
|
99
184
|
let config = null;
|
|
100
185
|
let logger;
|
|
101
|
-
let pythonProcess = null;
|
|
102
186
|
let isShuttingDown = false;
|
|
103
187
|
let isInitializing = false;
|
|
104
188
|
let isRegistered = false;
|
|
105
189
|
let isEnabled = false;
|
|
106
190
|
let api = null;
|
|
107
|
-
let builtinMemory = null;
|
|
108
191
|
let registeredTools = [];
|
|
109
192
|
let registeredHooks = [];
|
|
110
193
|
let registeredFallbackTools = [];
|
|
@@ -114,13 +197,9 @@ let autoReflectInterval = null;
|
|
|
114
197
|
let lastAutoReflectArchiveMarker = "";
|
|
115
198
|
let lastAutoReflectRunAt = 0;
|
|
116
199
|
let configPath = null;
|
|
117
|
-
let pythonStartPromise = null;
|
|
118
200
|
let processHandlersRegistered = false;
|
|
119
|
-
let pythonPidFilePath = null;
|
|
120
201
|
let memoryEngine = null;
|
|
121
|
-
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
202
|
+
let builtinMemory = null;
|
|
124
203
|
function getMemoryRoot() {
|
|
125
204
|
const projectRoot = findProjectRoot();
|
|
126
205
|
return config?.dbPath ? path.resolve(config.dbPath) : path.join(projectRoot, "data", "memory");
|
|
@@ -150,12 +229,38 @@ function getSessionCachedAutoSearch(sessionId) {
|
|
|
150
229
|
ageSeconds: Math.floor((Date.now() - cache.timestamp) / 1000),
|
|
151
230
|
};
|
|
152
231
|
}
|
|
232
|
+
function isInternalSession(sessionId) {
|
|
233
|
+
if (!sessionId)
|
|
234
|
+
return false;
|
|
235
|
+
return sessionId.startsWith("slug-generator-") || sessionId.startsWith("fallback:");
|
|
236
|
+
}
|
|
237
|
+
async function runWithTimeout(task, timeoutMs, label) {
|
|
238
|
+
let timeoutHandle = null;
|
|
239
|
+
try {
|
|
240
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
241
|
+
timeoutHandle = setTimeout(() => resolve(null), timeoutMs);
|
|
242
|
+
});
|
|
243
|
+
const result = await Promise.race([task, timeoutPromise]);
|
|
244
|
+
if (result === null) {
|
|
245
|
+
logger.warn(`${label} timed out after ${timeoutMs}ms; skipped to protect gateway responsiveness`);
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
logger.warn(`${label} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
if (timeoutHandle) {
|
|
255
|
+
clearTimeout(timeoutHandle);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
153
259
|
function resolveEngine() {
|
|
154
260
|
if (!config) {
|
|
155
261
|
throw new Error("Configuration not loaded");
|
|
156
262
|
}
|
|
157
|
-
|
|
158
|
-
if (memoryEngine && memoryEngine.mode === "ts") {
|
|
263
|
+
if (memoryEngine) {
|
|
159
264
|
return memoryEngine;
|
|
160
265
|
}
|
|
161
266
|
const projectRoot = findProjectRoot();
|
|
@@ -164,16 +269,56 @@ function resolveEngine() {
|
|
|
164
269
|
projectRoot,
|
|
165
270
|
dbPath: config.dbPath,
|
|
166
271
|
logger,
|
|
272
|
+
embedding: config.embedding,
|
|
273
|
+
reranker: config.reranker,
|
|
274
|
+
llm: config.llm,
|
|
275
|
+
fusion: config.readFusion,
|
|
276
|
+
memoryDecay: config.memoryDecay,
|
|
277
|
+
readTuning: config.readTuning,
|
|
278
|
+
});
|
|
279
|
+
const vectorStore = (0, vector_store_1.createVectorStore)({
|
|
280
|
+
memoryRoot,
|
|
281
|
+
logger,
|
|
167
282
|
});
|
|
168
283
|
const writeStore = (0, write_store_1.createWriteStore)({
|
|
169
284
|
projectRoot,
|
|
170
285
|
dbPath: config.dbPath,
|
|
171
286
|
logger,
|
|
287
|
+
embedding: config.embedding,
|
|
288
|
+
vectorChunking: config.vectorChunking,
|
|
289
|
+
writePolicy: config.writePolicy,
|
|
290
|
+
vectorStore,
|
|
291
|
+
});
|
|
292
|
+
const deduplicator = (0, three_stage_deduplicator_1.createThreeStageDeduplicator)({
|
|
293
|
+
memoryRoot,
|
|
294
|
+
logger,
|
|
295
|
+
});
|
|
296
|
+
const archiveStore = (0, archive_store_1.createArchiveStore)({
|
|
297
|
+
projectRoot,
|
|
298
|
+
memoryRoot,
|
|
299
|
+
logger,
|
|
300
|
+
embedding: config.embedding,
|
|
301
|
+
vectorChunking: config.vectorChunking,
|
|
302
|
+
writePolicy: config.writePolicy,
|
|
303
|
+
deduplicator,
|
|
304
|
+
vectorStore,
|
|
305
|
+
});
|
|
306
|
+
const graphMemoryStore = (0, graph_memory_store_1.createGraphMemoryStore)({
|
|
307
|
+
projectRoot,
|
|
308
|
+
memoryRoot,
|
|
309
|
+
logger,
|
|
310
|
+
qualityMode: config.graphQualityMode || "warn",
|
|
172
311
|
});
|
|
173
312
|
const sessionSync = (0, session_sync_1.createSessionSync)({
|
|
174
313
|
projectRoot,
|
|
175
314
|
dbPath: config.dbPath,
|
|
176
315
|
logger,
|
|
316
|
+
llm: config.llm,
|
|
317
|
+
graphQualityMode: config.graphQualityMode || "warn",
|
|
318
|
+
requireLlmForWrite: config.llmRequiredForWrite ?? true,
|
|
319
|
+
writePolicy: config.writePolicy,
|
|
320
|
+
archiveStore,
|
|
321
|
+
graphMemoryStore,
|
|
177
322
|
writeStore,
|
|
178
323
|
});
|
|
179
324
|
const sessionEnd = (0, session_end_1.createSessionEnd)({
|
|
@@ -181,6 +326,8 @@ function resolveEngine() {
|
|
|
181
326
|
dbPath: config.dbPath,
|
|
182
327
|
logger,
|
|
183
328
|
syncMemory: sessionSync.syncMemory,
|
|
329
|
+
syncDailySummaries: sessionSync.syncDailySummaries,
|
|
330
|
+
routeTranscript: sessionSync.routeTranscript,
|
|
184
331
|
});
|
|
185
332
|
const ruleStore = (0, rule_store_1.createRuleStore)({
|
|
186
333
|
projectRoot,
|
|
@@ -192,14 +339,23 @@ function resolveEngine() {
|
|
|
192
339
|
dbPath: config.dbPath,
|
|
193
340
|
logger,
|
|
194
341
|
ruleStore,
|
|
342
|
+
llm: config.llm,
|
|
195
343
|
});
|
|
196
344
|
memoryEngine = (0, ts_engine_1.createTsEngine)({
|
|
197
345
|
readStore,
|
|
198
346
|
writeStore,
|
|
347
|
+
vectorStore,
|
|
348
|
+
archiveStore,
|
|
349
|
+
graphMemoryStore,
|
|
199
350
|
sessionSync,
|
|
200
351
|
sessionEnd,
|
|
201
352
|
reflector,
|
|
202
353
|
memoryRoot,
|
|
354
|
+
projectRoot,
|
|
355
|
+
embedding: config.embedding,
|
|
356
|
+
llm: config.llm,
|
|
357
|
+
reranker: config.reranker,
|
|
358
|
+
vectorChunking: config.vectorChunking,
|
|
203
359
|
getCachedAutoSearch: getSessionCachedAutoSearch,
|
|
204
360
|
resolveSessionId: (context, payload) => resolveSessionId(context, payload),
|
|
205
361
|
normalizeIncomingMessage,
|
|
@@ -242,1133 +398,368 @@ function createConsoleLogger() {
|
|
|
242
398
|
};
|
|
243
399
|
}
|
|
244
400
|
function sanitizeForLogging(obj) {
|
|
245
|
-
const
|
|
401
|
+
const result = {};
|
|
246
402
|
for (const [key, value] of Object.entries(obj)) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
sanitized[key] = "***REDACTED***";
|
|
403
|
+
if (SENSITIVE_KEYS.some(k => key.toUpperCase().includes(k))) {
|
|
404
|
+
result[key] = "***REDACTED***";
|
|
250
405
|
}
|
|
251
|
-
else if (typeof value === "object" && value !== null) {
|
|
252
|
-
|
|
406
|
+
else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
407
|
+
result[key] = sanitizeForLogging(value);
|
|
253
408
|
}
|
|
254
409
|
else {
|
|
255
|
-
|
|
410
|
+
result[key] = value;
|
|
256
411
|
}
|
|
257
412
|
}
|
|
258
|
-
return
|
|
413
|
+
return result;
|
|
259
414
|
}
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
|
|
415
|
+
function findProjectRoot() {
|
|
416
|
+
let dir = __dirname;
|
|
417
|
+
while (dir !== path.dirname(dir)) {
|
|
418
|
+
const pkgPath = path.join(dir, "package.json");
|
|
419
|
+
if (fs.existsSync(pkgPath)) {
|
|
420
|
+
return dir;
|
|
421
|
+
}
|
|
422
|
+
dir = path.dirname(dir);
|
|
263
423
|
}
|
|
264
|
-
return
|
|
424
|
+
return process.cwd();
|
|
265
425
|
}
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
426
|
+
function resolveSessionId(context, payload) {
|
|
427
|
+
const contextObj = (context || {});
|
|
428
|
+
const payloadObj = (payload || {});
|
|
429
|
+
const candidates = [
|
|
430
|
+
contextObj.sessionId,
|
|
431
|
+
contextObj.session_id,
|
|
432
|
+
payloadObj.sessionId,
|
|
433
|
+
payloadObj.session_id,
|
|
434
|
+
payloadObj.id,
|
|
435
|
+
];
|
|
436
|
+
for (const c of candidates) {
|
|
437
|
+
if (typeof c === "string" && c.trim()) {
|
|
438
|
+
return c.trim();
|
|
270
439
|
}
|
|
271
440
|
}
|
|
272
|
-
return
|
|
441
|
+
return `fallback:${Date.now().toString(36)}`;
|
|
273
442
|
}
|
|
274
443
|
function normalizeIncomingMessage(payload) {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
const message = asRecord(data.message);
|
|
280
|
-
const eventData = asRecord(data.data);
|
|
281
|
-
const update = asRecord(data.update);
|
|
282
|
-
const updateMessage = update ? asRecord(update.message) : null;
|
|
283
|
-
const role = firstString([
|
|
284
|
-
data.role,
|
|
285
|
-
data.fromRole,
|
|
286
|
-
data.senderRole,
|
|
287
|
-
message?.role,
|
|
288
|
-
eventData?.role,
|
|
289
|
-
updateMessage?.role,
|
|
290
|
-
]) || "user";
|
|
291
|
-
const source = firstString([
|
|
292
|
-
data.source,
|
|
293
|
-
data.platform,
|
|
294
|
-
data.channel,
|
|
295
|
-
data.provider,
|
|
296
|
-
message?.source,
|
|
297
|
-
eventData?.source,
|
|
298
|
-
]) || "message";
|
|
299
|
-
let text = firstString([
|
|
300
|
-
data.content,
|
|
301
|
-
data.text,
|
|
302
|
-
data.body,
|
|
303
|
-
data.prompt,
|
|
304
|
-
data.message,
|
|
305
|
-
message?.content,
|
|
306
|
-
message?.text,
|
|
307
|
-
message?.body,
|
|
308
|
-
eventData?.content,
|
|
309
|
-
eventData?.text,
|
|
310
|
-
updateMessage?.text,
|
|
311
|
-
updateMessage?.caption,
|
|
312
|
-
]);
|
|
313
|
-
if (!text && Array.isArray(data.messages)) {
|
|
314
|
-
const merged = data.messages
|
|
315
|
-
.map(item => {
|
|
316
|
-
if (typeof item === "string")
|
|
317
|
-
return item;
|
|
318
|
-
const msgObj = asRecord(item);
|
|
319
|
-
if (!msgObj)
|
|
320
|
-
return "";
|
|
321
|
-
return firstString([msgObj.content, msgObj.text, msgObj.body]) || "";
|
|
322
|
-
})
|
|
323
|
-
.filter(Boolean)
|
|
324
|
-
.join("\n");
|
|
325
|
-
text = merged.trim() || undefined;
|
|
326
|
-
}
|
|
327
|
-
if (!text) {
|
|
444
|
+
const p = payload;
|
|
445
|
+
const text = typeof p.text === "string" ? p.text : (typeof p.content === "string" ? p.content : "");
|
|
446
|
+
if (!text)
|
|
328
447
|
return null;
|
|
329
|
-
|
|
448
|
+
const role = typeof p.role === "string" ? p.role : "unknown";
|
|
449
|
+
const source = typeof p.source === "string" ? p.source : "unknown";
|
|
330
450
|
return { text, role, source };
|
|
331
451
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return fromContext;
|
|
336
|
-
const data = asRecord(payload);
|
|
337
|
-
const dataChat = data ? asRecord(data.chat) : null;
|
|
338
|
-
const message = data ? asRecord(data.message) : null;
|
|
339
|
-
const messageChat = message ? asRecord(message.chat) : null;
|
|
340
|
-
const eventData = data ? asRecord(data.data) : null;
|
|
341
|
-
const eventChat = eventData ? asRecord(eventData.chat) : null;
|
|
342
|
-
const update = data ? asRecord(data.update) : null;
|
|
343
|
-
const updateMessage = update ? asRecord(update.message) : null;
|
|
344
|
-
const updateChat = updateMessage ? asRecord(updateMessage.chat) : null;
|
|
345
|
-
const direct = firstString([
|
|
346
|
-
data?.sessionId,
|
|
347
|
-
data?.session_id,
|
|
348
|
-
data?.conversationId,
|
|
349
|
-
data?.conversation_id,
|
|
350
|
-
data?.threadId,
|
|
351
|
-
data?.thread_id,
|
|
352
|
-
message?.sessionId,
|
|
353
|
-
eventData?.sessionId,
|
|
354
|
-
]);
|
|
355
|
-
if (direct)
|
|
356
|
-
return direct;
|
|
357
|
-
const chatId = firstString([
|
|
358
|
-
data?.chatId,
|
|
359
|
-
data?.chat_id,
|
|
360
|
-
dataChat?.id,
|
|
361
|
-
messageChat?.id,
|
|
362
|
-
eventChat?.id,
|
|
363
|
-
updateChat?.id,
|
|
364
|
-
]);
|
|
365
|
-
if (chatId)
|
|
366
|
-
return `chat:${chatId}`;
|
|
367
|
-
return `fallback:${context.workspaceId || "default"}:${context.agentId || "agent"}`;
|
|
368
|
-
}
|
|
369
|
-
function compareVersions(a, b) {
|
|
370
|
-
const partsA = a.split(".").map(Number);
|
|
371
|
-
const partsB = b.split(".").map(Number);
|
|
372
|
-
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
373
|
-
const valA = partsA[i] || 0;
|
|
374
|
-
const valB = partsB[i] || 0;
|
|
375
|
-
if (valA < valB)
|
|
376
|
-
return -1;
|
|
377
|
-
if (valA > valB)
|
|
378
|
-
return 1;
|
|
379
|
-
}
|
|
380
|
-
return 0;
|
|
381
|
-
}
|
|
382
|
-
async function checkOpenClawVersion() {
|
|
383
|
-
try {
|
|
384
|
-
const version = process.env.OPENCLAW_VERSION;
|
|
385
|
-
if (version) {
|
|
386
|
-
if (compareVersions(version, MIN_OPENCLAW_VERSION) < 0) {
|
|
387
|
-
throw new Error(`Incompatible OpenClaw version: ${version}. Minimum required: ${MIN_OPENCLAW_VERSION}`);
|
|
388
|
-
}
|
|
389
|
-
if (compareVersions(version, MAX_OPENCLAW_VERSION) >= 0) {
|
|
390
|
-
throw new Error(`Incompatible OpenClaw version: ${version}. Maximum supported: <${MAX_OPENCLAW_VERSION}`);
|
|
391
|
-
}
|
|
392
|
-
logger.info(`OpenClaw version check passed: ${version}`);
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
logger.warn("Could not determine OpenClaw version, proceeding with caution");
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
catch (e) {
|
|
399
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
400
|
-
logger.warn(`Version check warning: ${message}`);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
function findProjectRoot() {
|
|
404
|
-
if (api && api.rootDir) {
|
|
405
|
-
return api.rootDir;
|
|
406
|
-
}
|
|
407
|
-
let current = process.cwd();
|
|
408
|
-
while (current !== path.dirname(current)) {
|
|
409
|
-
if (fs.existsSync(path.join(current, "openclaw.plugin.json")) &&
|
|
410
|
-
fs.existsSync(path.join(current, "package.json"))) {
|
|
411
|
-
return current;
|
|
412
|
-
}
|
|
413
|
-
current = path.dirname(current);
|
|
414
|
-
}
|
|
415
|
-
throw new Error("Cannot find project root directory");
|
|
416
|
-
}
|
|
417
|
-
function findOpenClawConfig() {
|
|
418
|
-
const possiblePaths = [
|
|
419
|
-
path.join(process.cwd(), "openclaw.json"),
|
|
420
|
-
path.join(process.env.USERPROFILE || process.env.HOME || "", ".openclaw", "openclaw.json"),
|
|
421
|
-
path.join(process.env.OPENCLAW_BASE_PATH || "", "openclaw.json"),
|
|
422
|
-
];
|
|
423
|
-
for (const p of possiblePaths) {
|
|
424
|
-
if (fs.existsSync(p)) {
|
|
425
|
-
return p;
|
|
426
|
-
}
|
|
452
|
+
function asRecord(value) {
|
|
453
|
+
if (typeof value === "object" && value !== null) {
|
|
454
|
+
return value;
|
|
427
455
|
}
|
|
428
456
|
return null;
|
|
429
457
|
}
|
|
430
|
-
function
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
try {
|
|
435
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
436
|
-
const openclawConfig = JSON.parse(content);
|
|
437
|
-
const pluginEntry = openclawConfig?.plugins?.entries?.[PLUGIN_ID];
|
|
438
|
-
if (pluginEntry && typeof pluginEntry === "object") {
|
|
439
|
-
return pluginEntry.enabled !== false;
|
|
440
|
-
}
|
|
441
|
-
const legacyPluginConfig = openclawConfig?.plugins?.["cortex-memory"];
|
|
442
|
-
return legacyPluginConfig?.enabled !== false;
|
|
443
|
-
}
|
|
444
|
-
catch (e) {
|
|
445
|
-
logger.warn(`Failed to load config state: ${e}`);
|
|
446
|
-
return true;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
function startConfigWatcher() {
|
|
450
|
-
if (configWatchInterval) {
|
|
451
|
-
clearInterval(configWatchInterval);
|
|
452
|
-
}
|
|
453
|
-
let lastEnabledState = isEnabled;
|
|
454
|
-
configWatchInterval = setInterval(() => {
|
|
455
|
-
const newState = loadPluginEnabledState();
|
|
456
|
-
if (newState !== lastEnabledState) {
|
|
457
|
-
lastEnabledState = newState;
|
|
458
|
-
if (newState && !isEnabled) {
|
|
459
|
-
logger.info("Detected config change: enabling Cortex Memory plugin");
|
|
460
|
-
enable();
|
|
461
|
-
}
|
|
462
|
-
else if (!newState && isEnabled) {
|
|
463
|
-
logger.info("Detected config change: disabling Cortex Memory plugin");
|
|
464
|
-
disable();
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}, 5000);
|
|
468
|
-
}
|
|
469
|
-
function stopConfigWatcher() {
|
|
470
|
-
if (configWatchInterval) {
|
|
471
|
-
clearInterval(configWatchInterval);
|
|
472
|
-
configWatchInterval = null;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
function startAutoReflectScheduler() {
|
|
476
|
-
if (!config?.autoReflect || autoReflectInterval) {
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
autoReflectInterval = setInterval(() => {
|
|
480
|
-
if (!isEnabled) {
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
const schedulerContext = {
|
|
484
|
-
agentId: "cortex-memory-scheduler",
|
|
485
|
-
workspaceId: "system",
|
|
486
|
-
};
|
|
487
|
-
const marker = getArchiveMarker();
|
|
488
|
-
const now = Date.now();
|
|
489
|
-
if (marker === "missing" || marker === "error") {
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
if (marker === lastAutoReflectArchiveMarker) {
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
if (now - lastAutoReflectRunAt < 5 * 60 * 1000) {
|
|
496
|
-
return;
|
|
458
|
+
function firstString(values) {
|
|
459
|
+
for (const v of values) {
|
|
460
|
+
if (typeof v === "string" && v.trim()) {
|
|
461
|
+
return v.trim();
|
|
497
462
|
}
|
|
498
|
-
resolveEngine().reflectMemory({}, schedulerContext)
|
|
499
|
-
.then(result => {
|
|
500
|
-
if (result.success) {
|
|
501
|
-
lastAutoReflectArchiveMarker = marker;
|
|
502
|
-
lastAutoReflectRunAt = now;
|
|
503
|
-
logger.info("Scheduled reflection complete");
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
logger.warn(`Auto-reflect failed: ${result.error ?? "unknown_error"}`);
|
|
507
|
-
}
|
|
508
|
-
})
|
|
509
|
-
.catch(error => logger.warn(`Auto-reflect failed: ${String(error)}`));
|
|
510
|
-
}, 5 * 60 * 1000);
|
|
511
|
-
}
|
|
512
|
-
function stopAutoReflectScheduler() {
|
|
513
|
-
if (autoReflectInterval) {
|
|
514
|
-
clearInterval(autoReflectInterval);
|
|
515
|
-
autoReflectInterval = null;
|
|
516
463
|
}
|
|
464
|
+
return undefined;
|
|
517
465
|
}
|
|
518
466
|
function validateConfig(cfg) {
|
|
519
467
|
const errors = [];
|
|
520
|
-
if (!cfg.embedding?.provider
|
|
521
|
-
errors.push("embedding.provider
|
|
468
|
+
if (!cfg.embedding?.provider) {
|
|
469
|
+
errors.push("embedding.provider is required.");
|
|
522
470
|
}
|
|
523
|
-
if (!cfg.embedding?.
|
|
524
|
-
errors.push("embedding.
|
|
471
|
+
if (!cfg.embedding?.model) {
|
|
472
|
+
errors.push("embedding.model is required.");
|
|
525
473
|
}
|
|
526
|
-
if (!cfg.llm?.provider
|
|
527
|
-
errors.push("llm.provider
|
|
474
|
+
if (!cfg.llm?.provider) {
|
|
475
|
+
errors.push("llm.provider is required.");
|
|
528
476
|
}
|
|
529
|
-
if (!cfg.llm?.
|
|
530
|
-
errors.push("llm.
|
|
477
|
+
if (!cfg.llm?.model) {
|
|
478
|
+
errors.push("llm.model is required.");
|
|
531
479
|
}
|
|
532
|
-
if (
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (!cfg.reranker?.apiKey || !cfg.reranker?.baseURL) {
|
|
536
|
-
errors.push("reranker.apiKey and reranker.baseURL are required. Please configure third-party reranker endpoint credentials.");
|
|
537
|
-
}
|
|
538
|
-
return errors;
|
|
539
|
-
}
|
|
540
|
-
function getApiHostAndPort() {
|
|
541
|
-
const parsed = new URL(getBaseUrl());
|
|
542
|
-
const host = parsed.hostname || "127.0.0.1";
|
|
543
|
-
const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
|
|
544
|
-
return { host, port };
|
|
545
|
-
}
|
|
546
|
-
function sleep(ms) {
|
|
547
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
548
|
-
}
|
|
549
|
-
function isPortListening(host, port, timeoutMs = 700) {
|
|
550
|
-
return new Promise(resolve => {
|
|
551
|
-
const socket = new net.Socket();
|
|
552
|
-
let settled = false;
|
|
553
|
-
const finalize = (value) => {
|
|
554
|
-
if (settled)
|
|
555
|
-
return;
|
|
556
|
-
settled = true;
|
|
557
|
-
socket.destroy();
|
|
558
|
-
resolve(value);
|
|
559
|
-
};
|
|
560
|
-
socket.setTimeout(timeoutMs);
|
|
561
|
-
socket.once("connect", () => finalize(true));
|
|
562
|
-
socket.once("timeout", () => finalize(false));
|
|
563
|
-
socket.once("error", () => finalize(false));
|
|
564
|
-
socket.connect(port, host);
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
function writePythonPid(pid) {
|
|
568
|
-
if (!pythonPidFilePath)
|
|
569
|
-
return;
|
|
570
|
-
try {
|
|
571
|
-
fs.writeFileSync(pythonPidFilePath, String(pid), "utf-8");
|
|
572
|
-
}
|
|
573
|
-
catch (e) {
|
|
574
|
-
logger.warn(`Failed to write Python pid file: ${e instanceof Error ? e.message : String(e)}`);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
function clearPythonPidFile() {
|
|
578
|
-
if (!pythonPidFilePath)
|
|
579
|
-
return;
|
|
580
|
-
try {
|
|
581
|
-
if (fs.existsSync(pythonPidFilePath)) {
|
|
582
|
-
fs.unlinkSync(pythonPidFilePath);
|
|
480
|
+
if (cfg.autoReflectIntervalMinutes !== undefined) {
|
|
481
|
+
if (!Number.isFinite(cfg.autoReflectIntervalMinutes) || cfg.autoReflectIntervalMinutes < 5) {
|
|
482
|
+
errors.push("autoReflectIntervalMinutes must be >= 5.");
|
|
583
483
|
}
|
|
584
484
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
function readPythonPid() {
|
|
590
|
-
if (!pythonPidFilePath || !fs.existsSync(pythonPidFilePath))
|
|
591
|
-
return null;
|
|
592
|
-
try {
|
|
593
|
-
const raw = fs.readFileSync(pythonPidFilePath, "utf-8").trim();
|
|
594
|
-
const pid = Number(raw);
|
|
595
|
-
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
596
|
-
}
|
|
597
|
-
catch {
|
|
598
|
-
return null;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
function killProcessByPid(pid) {
|
|
602
|
-
if (!pid)
|
|
603
|
-
return;
|
|
604
|
-
try {
|
|
605
|
-
if (process.platform === "win32") {
|
|
606
|
-
(0, child_process_1.execSync)(`taskkill /pid ${pid} /f /t`, { stdio: "ignore" });
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
try {
|
|
610
|
-
process.kill(pid, "SIGTERM");
|
|
485
|
+
if (cfg.graphQualityMode !== undefined) {
|
|
486
|
+
if (!["off", "warn", "strict"].includes(cfg.graphQualityMode)) {
|
|
487
|
+
errors.push("graphQualityMode must be one of: off, warn, strict.");
|
|
611
488
|
}
|
|
612
|
-
catch { }
|
|
613
|
-
try {
|
|
614
|
-
process.kill(-pid, "SIGTERM");
|
|
615
|
-
}
|
|
616
|
-
catch { }
|
|
617
|
-
setTimeout(() => {
|
|
618
|
-
try {
|
|
619
|
-
process.kill(pid, "SIGKILL");
|
|
620
|
-
}
|
|
621
|
-
catch { }
|
|
622
|
-
try {
|
|
623
|
-
process.kill(-pid, "SIGKILL");
|
|
624
|
-
}
|
|
625
|
-
catch { }
|
|
626
|
-
}, 2000);
|
|
627
489
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
try {
|
|
634
|
-
if (process.platform === "win32") {
|
|
635
|
-
const output = (0, child_process_1.execSync)(`netstat -ano | findstr :${port}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] });
|
|
636
|
-
const pids = output
|
|
637
|
-
.split(/\r?\n/)
|
|
638
|
-
.filter(line => line.includes("LISTENING"))
|
|
639
|
-
.map(line => {
|
|
640
|
-
const parts = line.trim().split(/\s+/);
|
|
641
|
-
return Number(parts[parts.length - 1]);
|
|
642
|
-
})
|
|
643
|
-
.filter(pid => Number.isInteger(pid) && pid > 0);
|
|
644
|
-
for (const pid of pids) {
|
|
645
|
-
killProcessByPid(pid);
|
|
490
|
+
if (cfg.readFusion?.channelWeights) {
|
|
491
|
+
const weights = cfg.readFusion.channelWeights;
|
|
492
|
+
for (const [key, value] of Object.entries(weights)) {
|
|
493
|
+
if (typeof value === "number" && (!Number.isFinite(value) || value < 0)) {
|
|
494
|
+
errors.push(`readFusion.channelWeights.${key} must be >= 0.`);
|
|
646
495
|
}
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
const output = (0, child_process_1.execSync)(`sh -lc "lsof -ti tcp:${port} 2>/dev/null || true"`, { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] });
|
|
650
|
-
const pids = output
|
|
651
|
-
.split(/\r?\n/)
|
|
652
|
-
.map(line => Number(line.trim()))
|
|
653
|
-
.filter(pid => Number.isInteger(pid) && pid > 0);
|
|
654
|
-
for (const pid of pids) {
|
|
655
|
-
killProcessByPid(pid);
|
|
656
496
|
}
|
|
657
497
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const apiUrl = getBaseUrl();
|
|
664
|
-
try {
|
|
665
|
-
const response = await fetch(`${apiUrl}/health`, { signal: AbortSignal.timeout(1000) });
|
|
666
|
-
return response.ok;
|
|
667
|
-
}
|
|
668
|
-
catch {
|
|
669
|
-
return false;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
async function startPythonService() {
|
|
673
|
-
if (pythonStartPromise) {
|
|
674
|
-
return pythonStartPromise;
|
|
675
|
-
}
|
|
676
|
-
pythonStartPromise = startPythonServiceInternal().finally(() => {
|
|
677
|
-
pythonStartPromise = null;
|
|
678
|
-
});
|
|
679
|
-
return pythonStartPromise;
|
|
680
|
-
}
|
|
681
|
-
async function startPythonServiceInternal() {
|
|
682
|
-
if (!config) {
|
|
683
|
-
throw new Error("Configuration not loaded");
|
|
684
|
-
}
|
|
685
|
-
const projectRoot = findProjectRoot();
|
|
686
|
-
pythonPidFilePath = path.join(projectRoot, ".cortex-memory-python.pid");
|
|
687
|
-
const { host, port } = getApiHostAndPort();
|
|
688
|
-
const stalePid = readPythonPid();
|
|
689
|
-
if (stalePid && (!pythonProcess || pythonProcess.pid !== stalePid)) {
|
|
690
|
-
logger.info(`Found stale Python pid ${stalePid}, trying to stop it...`);
|
|
691
|
-
killProcessByPid(stalePid);
|
|
692
|
-
await sleep(800);
|
|
693
|
-
clearPythonPidFile();
|
|
694
|
-
}
|
|
695
|
-
const healthyRunning = await checkPortInUse();
|
|
696
|
-
if (healthyRunning) {
|
|
697
|
-
logger.info("Python service already running, shutting down old instance...");
|
|
698
|
-
await shutdownPythonApi();
|
|
699
|
-
await sleep(1000);
|
|
700
|
-
}
|
|
701
|
-
const occupied = await isPortListening(host, port);
|
|
702
|
-
if (occupied) {
|
|
703
|
-
logger.warn(`Port ${port} is still occupied after graceful shutdown, forcing cleanup...`);
|
|
704
|
-
freePortWithSystemTools(port);
|
|
705
|
-
await sleep(1000);
|
|
706
|
-
}
|
|
707
|
-
if (await isPortListening(host, port)) {
|
|
708
|
-
throw new Error(`Port ${port} is already in use by another process. Please stop that process and retry.`);
|
|
709
|
-
}
|
|
710
|
-
const venvDir = path.join(projectRoot, "venv");
|
|
711
|
-
const pythonCmd = process.platform === "win32"
|
|
712
|
-
? path.join(venvDir, "Scripts", "python.exe")
|
|
713
|
-
: path.join(venvDir, "bin", "python");
|
|
714
|
-
if (!fs.existsSync(pythonCmd)) {
|
|
715
|
-
throw new Error("Python environment not found. Please run 'npm install' first.");
|
|
716
|
-
}
|
|
717
|
-
const errors = validateConfig(config);
|
|
718
|
-
if (errors.length > 0) {
|
|
719
|
-
throw new Error(`Configuration errors:\n${errors.join("\n")}`);
|
|
720
|
-
}
|
|
721
|
-
logger.info("Starting Cortex Memory Python service...");
|
|
722
|
-
const env = {
|
|
723
|
-
...process.env,
|
|
724
|
-
CORTEX_MEMORY_EMBEDDING_PROVIDER: config.embedding.provider,
|
|
725
|
-
CORTEX_MEMORY_EMBEDDING_MODEL: config.embedding.model,
|
|
726
|
-
CORTEX_MEMORY_LLM_PROVIDER: config.llm.provider,
|
|
727
|
-
CORTEX_MEMORY_LLM_MODEL: config.llm.model,
|
|
728
|
-
CORTEX_MEMORY_RERANKER_PROVIDER: config.reranker.provider || "",
|
|
729
|
-
CORTEX_MEMORY_RERANKER_MODEL: config.reranker.model,
|
|
730
|
-
CORTEX_MEMORY_DB_PATH: config.dbPath || path.join(process.env.USERPROFILE || process.env.HOME || "", ".openclaw", "agents", "main", "lancedb_store"),
|
|
731
|
-
};
|
|
732
|
-
if (config.embedding.apiKey) {
|
|
733
|
-
env.CORTEX_MEMORY_EMBEDDING_API_KEY = config.embedding.apiKey;
|
|
734
|
-
}
|
|
735
|
-
if (config.embedding.baseURL) {
|
|
736
|
-
env.CORTEX_MEMORY_EMBEDDING_BASE_URL = config.embedding.baseURL;
|
|
737
|
-
}
|
|
738
|
-
if (config.embedding.dimensions) {
|
|
739
|
-
env.CORTEX_MEMORY_EMBEDDING_DIMENSIONS = String(config.embedding.dimensions);
|
|
740
|
-
}
|
|
741
|
-
if (config.llm.apiKey) {
|
|
742
|
-
env.CORTEX_MEMORY_LLM_API_KEY = config.llm.apiKey;
|
|
743
|
-
}
|
|
744
|
-
if (config.llm.baseURL) {
|
|
745
|
-
env.CORTEX_MEMORY_LLM_BASE_URL = config.llm.baseURL;
|
|
746
|
-
}
|
|
747
|
-
if (config.reranker.apiKey) {
|
|
748
|
-
env.CORTEX_MEMORY_RERANKER_API_KEY = config.reranker.apiKey;
|
|
749
|
-
}
|
|
750
|
-
if (config.reranker.baseURL) {
|
|
751
|
-
env.CORTEX_MEMORY_RERANKER_ENDPOINT = config.reranker.baseURL;
|
|
752
|
-
}
|
|
753
|
-
return new Promise((resolve, reject) => {
|
|
754
|
-
pythonProcess = (0, child_process_1.spawn)(pythonCmd, ["-m", "api.server"], {
|
|
755
|
-
cwd: projectRoot,
|
|
756
|
-
detached: false,
|
|
757
|
-
windowsHide: true,
|
|
758
|
-
env: { ...env, PYTHONWARNINGS: "ignore::RuntimeWarning" },
|
|
759
|
-
});
|
|
760
|
-
if (pythonProcess.pid) {
|
|
761
|
-
writePythonPid(pythonProcess.pid);
|
|
762
|
-
}
|
|
763
|
-
let started = false;
|
|
764
|
-
let stderrBuffer = "";
|
|
765
|
-
let settled = false;
|
|
766
|
-
let startupTimeout = null;
|
|
767
|
-
const resolveOnce = () => {
|
|
768
|
-
if (settled)
|
|
769
|
-
return;
|
|
770
|
-
settled = true;
|
|
771
|
-
started = true;
|
|
772
|
-
if (startupTimeout) {
|
|
773
|
-
clearTimeout(startupTimeout);
|
|
774
|
-
startupTimeout = null;
|
|
775
|
-
}
|
|
776
|
-
resolve();
|
|
777
|
-
};
|
|
778
|
-
const rejectOnce = (error) => {
|
|
779
|
-
if (settled)
|
|
780
|
-
return;
|
|
781
|
-
settled = true;
|
|
782
|
-
if (startupTimeout) {
|
|
783
|
-
clearTimeout(startupTimeout);
|
|
784
|
-
startupTimeout = null;
|
|
785
|
-
}
|
|
786
|
-
reject(error);
|
|
787
|
-
};
|
|
788
|
-
pythonProcess.stdout?.on("data", (data) => {
|
|
789
|
-
const output = data.toString();
|
|
790
|
-
if (!output.toLowerCase().includes("key") && !output.toLowerCase().includes("token")) {
|
|
791
|
-
logger.info(`[Python] ${output.trim()}`);
|
|
498
|
+
if (cfg.readFusion?.channelTopK) {
|
|
499
|
+
const topK = cfg.readFusion.channelTopK;
|
|
500
|
+
for (const [key, value] of Object.entries(topK)) {
|
|
501
|
+
if (typeof value === "number" && (!Number.isFinite(value) || value < 1)) {
|
|
502
|
+
errors.push(`readFusion.channelTopK.${key} must be >= 1.`);
|
|
792
503
|
}
|
|
793
|
-
if (output.includes("Cortex Memory API started") || output.includes("Application startup complete")) {
|
|
794
|
-
resolveOnce();
|
|
795
|
-
}
|
|
796
|
-
});
|
|
797
|
-
pythonProcess.stderr?.on("data", (data) => {
|
|
798
|
-
const output = data.toString();
|
|
799
|
-
stderrBuffer += output;
|
|
800
|
-
if (!output.toLowerCase().includes("key") && !output.toLowerCase().includes("token")) {
|
|
801
|
-
logger.warn(`[Python] ${output.trim()}`);
|
|
802
|
-
}
|
|
803
|
-
if (output.includes("Cortex Memory API started") ||
|
|
804
|
-
output.includes("Application startup complete") ||
|
|
805
|
-
output.includes("Uvicorn running on")) {
|
|
806
|
-
resolveOnce();
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
pythonProcess.on("error", (error) => {
|
|
810
|
-
logger.error("Failed to start Python service:", error.message);
|
|
811
|
-
rejectOnce(error);
|
|
812
|
-
});
|
|
813
|
-
pythonProcess.on("exit", (code) => {
|
|
814
|
-
clearPythonPidFile();
|
|
815
|
-
pythonProcess = null;
|
|
816
|
-
if (!started && code !== 0 && !isShuttingDown) {
|
|
817
|
-
rejectOnce(new Error(`Python service exited with code ${code}. Stderr: ${stderrBuffer.slice(-500)}`));
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
startupTimeout = setTimeout(() => {
|
|
821
|
-
if (!started) {
|
|
822
|
-
const tail = stderrBuffer ? `\nLast stderr: ${stderrBuffer.slice(-500)}` : "";
|
|
823
|
-
killPythonProcess();
|
|
824
|
-
rejectOnce(new Error(`Timeout waiting for Python service to start (300s)${tail}`));
|
|
825
|
-
}
|
|
826
|
-
}, 300000);
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
async function shutdownPythonApi() {
|
|
830
|
-
const apiUrl = getBaseUrl();
|
|
831
|
-
try {
|
|
832
|
-
await fetch(`${apiUrl}/shutdown`, { method: "POST", signal: AbortSignal.timeout(2000) });
|
|
833
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
834
|
-
}
|
|
835
|
-
catch {
|
|
836
|
-
// ignore
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
function killPythonProcess() {
|
|
840
|
-
const directPid = pythonProcess?.pid ?? null;
|
|
841
|
-
const pidFromFile = readPythonPid();
|
|
842
|
-
const pid = directPid || pidFromFile;
|
|
843
|
-
if (!pid)
|
|
844
|
-
return;
|
|
845
|
-
try {
|
|
846
|
-
killProcessByPid(pid);
|
|
847
|
-
}
|
|
848
|
-
catch (e) {
|
|
849
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
850
|
-
logger.warn(`Failed to kill Python process: ${message}`);
|
|
851
|
-
}
|
|
852
|
-
finally {
|
|
853
|
-
pythonProcess = null;
|
|
854
|
-
clearPythonPidFile();
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
async function stopPythonServiceAsync() {
|
|
858
|
-
if (pythonStartPromise) {
|
|
859
|
-
try {
|
|
860
|
-
await pythonStartPromise;
|
|
861
|
-
}
|
|
862
|
-
catch {
|
|
863
|
-
// ignore
|
|
864
504
|
}
|
|
865
505
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
function stopPythonService() {
|
|
870
|
-
stopPythonServiceAsync();
|
|
871
|
-
}
|
|
872
|
-
function getBaseUrl() {
|
|
873
|
-
return config?.apiUrl ?? "http://127.0.0.1:8765";
|
|
874
|
-
}
|
|
875
|
-
async function waitForService(maxAttempts = 30) {
|
|
876
|
-
const apiUrl = getBaseUrl();
|
|
877
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
878
|
-
try {
|
|
879
|
-
const response = await fetch(`${apiUrl}/health`, { signal: AbortSignal.timeout(1000) });
|
|
880
|
-
if (response.ok)
|
|
881
|
-
return;
|
|
506
|
+
if (cfg.vectorChunking?.chunkSize !== undefined) {
|
|
507
|
+
if (!Number.isFinite(cfg.vectorChunking.chunkSize) || cfg.vectorChunking.chunkSize < 100) {
|
|
508
|
+
errors.push("vectorChunking.chunkSize must be >= 100.");
|
|
882
509
|
}
|
|
883
|
-
catch { }
|
|
884
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
885
510
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
890
|
-
const lower = message.toLowerCase();
|
|
891
|
-
if (lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("fetch failed")) {
|
|
892
|
-
const err = ERROR_CODES.CONNECTION_REFUSED;
|
|
893
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
894
|
-
}
|
|
895
|
-
if (lower.includes("abort") || lower.includes("timed out")) {
|
|
896
|
-
const err = ERROR_CODES.TIMEOUT;
|
|
897
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
898
|
-
}
|
|
899
|
-
if (lower.includes("404") || lower.includes("not found")) {
|
|
900
|
-
const err = ERROR_CODES.NOT_FOUND;
|
|
901
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
902
|
-
}
|
|
903
|
-
if (lower.includes("400") || lower.includes("invalid")) {
|
|
904
|
-
const err = ERROR_CODES.INVALID_INPUT;
|
|
905
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
906
|
-
}
|
|
907
|
-
const err = ERROR_CODES.SERVICE_ERROR;
|
|
908
|
-
return `${err.message} (${err.code}). Details: ${message}`;
|
|
909
|
-
}
|
|
910
|
-
const pendingRequests = new Map();
|
|
911
|
-
const requestDebounceMs = 100;
|
|
912
|
-
function getRequestKey(endpoint, method, body) {
|
|
913
|
-
const bodyHash = body ? JSON.stringify(body).slice(0, 100) : "";
|
|
914
|
-
return `${method}:${endpoint}:${bodyHash}`;
|
|
915
|
-
}
|
|
916
|
-
async function apiCallWithRetry(endpoint, method = "GET", body, options) {
|
|
917
|
-
const { maxRetries = 3, baseDelay = 1000, timeout = 30000, skipDebounce = false } = options || {};
|
|
918
|
-
if (!skipDebounce) {
|
|
919
|
-
const requestKey = getRequestKey(endpoint, method, body);
|
|
920
|
-
const pending = pendingRequests.get(requestKey);
|
|
921
|
-
if (pending) {
|
|
922
|
-
logger.debug(`Reusing pending request for ${endpoint}`);
|
|
923
|
-
return pending.promise;
|
|
511
|
+
if (cfg.vectorChunking?.chunkOverlap !== undefined) {
|
|
512
|
+
if (!Number.isFinite(cfg.vectorChunking.chunkOverlap) || cfg.vectorChunking.chunkOverlap < 0) {
|
|
513
|
+
errors.push("vectorChunking.chunkOverlap must be >= 0.");
|
|
924
514
|
}
|
|
925
|
-
const requestPromise = apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout)
|
|
926
|
-
.finally(() => {
|
|
927
|
-
setTimeout(() => pendingRequests.delete(requestKey), requestDebounceMs);
|
|
928
|
-
});
|
|
929
|
-
pendingRequests.set(requestKey, {
|
|
930
|
-
promise: requestPromise
|
|
931
|
-
});
|
|
932
|
-
return await requestPromise;
|
|
933
515
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
let lastError = null;
|
|
938
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
939
|
-
try {
|
|
940
|
-
return await apiCall(endpoint, method, body, timeout);
|
|
941
|
-
}
|
|
942
|
-
catch (error) {
|
|
943
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
944
|
-
const isRetryable = lastError.message.includes("E001") ||
|
|
945
|
-
lastError.message.includes("E002") ||
|
|
946
|
-
lastError.message.includes("timeout") ||
|
|
947
|
-
lastError.message.includes("ECONNREFUSED") ||
|
|
948
|
-
lastError.message.includes("ENOTFOUND");
|
|
949
|
-
if (attempt < maxRetries - 1 && isRetryable) {
|
|
950
|
-
const delay = baseDelay * Math.pow(2, attempt);
|
|
951
|
-
logger.warn(`API call failed (attempt ${attempt + 1}/${maxRetries}), retrying in ${delay}ms: ${lastError.message.split(".")[0]}`);
|
|
952
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
953
|
-
}
|
|
516
|
+
if (cfg.vectorChunking?.evidenceMaxChunks !== undefined) {
|
|
517
|
+
if (!Number.isFinite(cfg.vectorChunking.evidenceMaxChunks) || cfg.vectorChunking.evidenceMaxChunks < 0) {
|
|
518
|
+
errors.push("vectorChunking.evidenceMaxChunks must be >= 0.");
|
|
954
519
|
}
|
|
955
520
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
method,
|
|
964
|
-
headers: { "Content-Type": "application/json" },
|
|
965
|
-
signal: controller.signal,
|
|
966
|
-
};
|
|
967
|
-
if (body)
|
|
968
|
-
options.body = JSON.stringify(body);
|
|
969
|
-
try {
|
|
970
|
-
const response = await fetch(url, options);
|
|
971
|
-
const text = await response.text();
|
|
972
|
-
if (!response.ok) {
|
|
973
|
-
try {
|
|
974
|
-
const errorData = JSON.parse(text);
|
|
975
|
-
throw new Error(errorData.error || errorData.detail || `HTTP ${response.status}`);
|
|
976
|
-
}
|
|
977
|
-
catch {
|
|
978
|
-
throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
|
|
521
|
+
if (cfg.writePolicy) {
|
|
522
|
+
const wp = cfg.writePolicy;
|
|
523
|
+
const bounded01 = ["archiveMinConfidence", "archiveMinQualityScore", "activeMinQualityScore"];
|
|
524
|
+
for (const key of bounded01) {
|
|
525
|
+
const value = wp[key];
|
|
526
|
+
if (value !== undefined && (!Number.isFinite(value) || value < 0 || value > 1)) {
|
|
527
|
+
errors.push(`writePolicy.${key} must be between 0 and 1.`);
|
|
979
528
|
}
|
|
980
529
|
}
|
|
981
|
-
if (!
|
|
982
|
-
|
|
983
|
-
try {
|
|
984
|
-
return JSON.parse(text);
|
|
530
|
+
if (wp.activeDedupTailLines !== undefined && (!Number.isFinite(wp.activeDedupTailLines) || wp.activeDedupTailLines < 20)) {
|
|
531
|
+
errors.push("writePolicy.activeDedupTailLines must be >= 20.");
|
|
985
532
|
}
|
|
986
|
-
|
|
987
|
-
|
|
533
|
+
if (wp.activeTextMaxChars !== undefined && (!Number.isFinite(wp.activeTextMaxChars) || wp.activeTextMaxChars < 500)) {
|
|
534
|
+
errors.push("writePolicy.activeTextMaxChars must be >= 500.");
|
|
535
|
+
}
|
|
536
|
+
if (wp.archiveSourceTextMaxChars !== undefined && (!Number.isFinite(wp.archiveSourceTextMaxChars) || wp.archiveSourceTextMaxChars < 1000)) {
|
|
537
|
+
errors.push("writePolicy.archiveSourceTextMaxChars must be >= 1000.");
|
|
988
538
|
}
|
|
989
539
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
}
|
|
1002
|
-
if (!isEnabled) {
|
|
1003
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1004
|
-
logger.info("Using builtin memory (plugin disabled)");
|
|
1005
|
-
try {
|
|
1006
|
-
const results = await builtinMemory.search(args.query, args.top_k || 3);
|
|
1007
|
-
return { success: true, data: results };
|
|
540
|
+
if (cfg.memoryDecay) {
|
|
541
|
+
if (typeof cfg.memoryDecay.minFloor === "number" && (!Number.isFinite(cfg.memoryDecay.minFloor) || cfg.memoryDecay.minFloor < 0 || cfg.memoryDecay.minFloor > 1)) {
|
|
542
|
+
errors.push("memoryDecay.minFloor must be between 0 and 1.");
|
|
543
|
+
}
|
|
544
|
+
if (typeof cfg.memoryDecay.defaultHalfLifeDays === "number" && (!Number.isFinite(cfg.memoryDecay.defaultHalfLifeDays) || cfg.memoryDecay.defaultHalfLifeDays <= 0)) {
|
|
545
|
+
errors.push("memoryDecay.defaultHalfLifeDays must be > 0.");
|
|
546
|
+
}
|
|
547
|
+
if (cfg.memoryDecay.antiDecay) {
|
|
548
|
+
const anti = cfg.memoryDecay.antiDecay;
|
|
549
|
+
if (typeof anti.maxBoost === "number" && (!Number.isFinite(anti.maxBoost) || anti.maxBoost < 1)) {
|
|
550
|
+
errors.push("memoryDecay.antiDecay.maxBoost must be >= 1.");
|
|
1008
551
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
552
|
+
if (typeof anti.hitWeight === "number" && (!Number.isFinite(anti.hitWeight) || anti.hitWeight < 0)) {
|
|
553
|
+
errors.push("memoryDecay.antiDecay.hitWeight must be >= 0.");
|
|
554
|
+
}
|
|
555
|
+
if (typeof anti.recentWindowDays === "number" && (!Number.isFinite(anti.recentWindowDays) || anti.recentWindowDays <= 0)) {
|
|
556
|
+
errors.push("memoryDecay.antiDecay.recentWindowDays must be > 0.");
|
|
1012
557
|
}
|
|
1013
558
|
}
|
|
1014
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1015
559
|
}
|
|
1016
|
-
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
logger.info("Using builtin memory (plugin disabled)");
|
|
1033
|
-
try {
|
|
1034
|
-
const id = await builtinMemory.store(args.summary, {
|
|
1035
|
-
entities: args.entities,
|
|
1036
|
-
outcome: args.outcome,
|
|
1037
|
-
relations: args.relations
|
|
1038
|
-
});
|
|
1039
|
-
return { success: true, data: { event_id: id } };
|
|
560
|
+
if (cfg.readTuning) {
|
|
561
|
+
const numericReadTuningFields = [
|
|
562
|
+
["readTuning.scoring.lexicalWeight", cfg.readTuning.scoring?.lexicalWeight, 0],
|
|
563
|
+
["readTuning.scoring.bm25Scale", cfg.readTuning.scoring?.bm25Scale, 0],
|
|
564
|
+
["readTuning.scoring.semanticWeight", cfg.readTuning.scoring?.semanticWeight, 0],
|
|
565
|
+
["readTuning.scoring.recencyWeight", cfg.readTuning.scoring?.recencyWeight, 0],
|
|
566
|
+
["readTuning.scoring.qualityWeight", cfg.readTuning.scoring?.qualityWeight, 0],
|
|
567
|
+
["readTuning.scoring.typeMatchWeight", cfg.readTuning.scoring?.typeMatchWeight, 0],
|
|
568
|
+
["readTuning.scoring.graphMatchWeight", cfg.readTuning.scoring?.graphMatchWeight, 0],
|
|
569
|
+
["readTuning.rrf.k", cfg.readTuning.rrf?.k, 1],
|
|
570
|
+
["readTuning.rrf.weight", cfg.readTuning.rrf?.weight, 0],
|
|
571
|
+
["readTuning.autoContext.queryMaxChars", cfg.readTuning.autoContext?.queryMaxChars, 20],
|
|
572
|
+
];
|
|
573
|
+
for (const [name, value, min] of numericReadTuningFields) {
|
|
574
|
+
if (typeof value === "number" && (!Number.isFinite(value) || value < min)) {
|
|
575
|
+
errors.push(`${name} must be >= ${min}.`);
|
|
1040
576
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
577
|
+
}
|
|
578
|
+
if (Array.isArray(cfg.readTuning.recency?.buckets)) {
|
|
579
|
+
const buckets = cfg.readTuning.recency.buckets;
|
|
580
|
+
for (let i = 0; i < buckets.length; i += 1) {
|
|
581
|
+
const bucket = buckets[i];
|
|
582
|
+
if (!Number.isFinite(bucket.maxAgeHours) || bucket.maxAgeHours <= 0) {
|
|
583
|
+
errors.push(`readTuning.recency.buckets[${i}].maxAgeHours must be > 0.`);
|
|
584
|
+
}
|
|
585
|
+
if (!Number.isFinite(bucket.score) || bucket.score < 0) {
|
|
586
|
+
errors.push(`readTuning.recency.buckets[${i}].score must be >= 0.`);
|
|
587
|
+
}
|
|
588
|
+
if (!Number.isFinite(bucket.bonus) || bucket.bonus < 0) {
|
|
589
|
+
errors.push(`readTuning.recency.buckets[${i}].bonus must be >= 0.`);
|
|
590
|
+
}
|
|
1044
591
|
}
|
|
1045
592
|
}
|
|
1046
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1047
|
-
}
|
|
1048
|
-
try {
|
|
1049
|
-
const result = await apiCallWithRetry("/event", "POST", {
|
|
1050
|
-
summary: args.summary,
|
|
1051
|
-
entities: args.entities,
|
|
1052
|
-
outcome: args.outcome,
|
|
1053
|
-
relations: args.relations,
|
|
1054
|
-
});
|
|
1055
|
-
return { success: true, data: result };
|
|
1056
|
-
}
|
|
1057
|
-
catch (error) {
|
|
1058
|
-
const message = formatApiError(error);
|
|
1059
|
-
logger.error(`store_event failed: ${message}`);
|
|
1060
|
-
return { success: false, error: message };
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
async function queryGraph(args, _context) {
|
|
1064
|
-
if (!args || !args.entity) {
|
|
1065
|
-
logger.error(`query_graph called with invalid args: ${JSON.stringify(args)}`);
|
|
1066
|
-
return { success: false, error: ERROR_CODES.INVALID_INPUT.message + " Missing 'entity' parameter.", errorCode: ERROR_CODES.INVALID_INPUT.code };
|
|
1067
|
-
}
|
|
1068
|
-
if (!isEnabled) {
|
|
1069
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1070
|
-
}
|
|
1071
|
-
try {
|
|
1072
|
-
const result = await apiCallWithRetry("/graph/query", "POST", { entity: args.entity });
|
|
1073
|
-
return { success: true, data: result.graph };
|
|
1074
|
-
}
|
|
1075
|
-
catch (error) {
|
|
1076
|
-
const message = formatApiError(error);
|
|
1077
|
-
logger.error(`query_graph failed: ${message}`);
|
|
1078
|
-
return { success: false, error: message };
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
async function getHotContext(args, _context) {
|
|
1082
|
-
if (!isEnabled) {
|
|
1083
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1084
|
-
}
|
|
1085
|
-
try {
|
|
1086
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 20;
|
|
1087
|
-
const result = await apiCallWithRetry(`/hot-context?limit=${limit}`, "GET");
|
|
1088
|
-
return { success: true, data: result.context };
|
|
1089
|
-
}
|
|
1090
|
-
catch (error) {
|
|
1091
|
-
const message = formatApiError(error);
|
|
1092
|
-
logger.error(`get_hot_context failed: ${message}`);
|
|
1093
|
-
return { success: false, error: message };
|
|
1094
593
|
}
|
|
594
|
+
return errors;
|
|
1095
595
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1099
|
-
}
|
|
1100
|
-
const now = Date.now();
|
|
1101
|
-
const result = {};
|
|
1102
|
-
const sessionId = resolveSessionId(context);
|
|
1103
|
-
clearStaleAutoSearchCache(now);
|
|
1104
|
-
const sessionCache = autoSearchCacheBySession.get(sessionId);
|
|
1105
|
-
if (sessionCache) {
|
|
1106
|
-
result.auto_search = {
|
|
1107
|
-
query: sessionCache.query,
|
|
1108
|
-
results: sessionCache.results,
|
|
1109
|
-
age_seconds: Math.floor((now - sessionCache.timestamp) / 1000),
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
if (args.include_hot !== false) {
|
|
596
|
+
function checkOpenClawVersion() {
|
|
597
|
+
return new Promise((resolve) => {
|
|
1113
598
|
try {
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
if (!result.auto_search && !result.hot_context) {
|
|
1122
|
-
return {
|
|
1123
|
-
success: true,
|
|
1124
|
-
data: {
|
|
1125
|
-
message: "No session-scoped auto-search results cached and hot context unavailable",
|
|
1126
|
-
suggestion: "Send a user message in this session or call get_hot_context."
|
|
599
|
+
const version = api.openclawVersion || api.version;
|
|
600
|
+
if (!version) {
|
|
601
|
+
logger.warn("Could not determine OpenClaw version");
|
|
602
|
+
resolve();
|
|
603
|
+
return;
|
|
1127
604
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
});
|
|
1141
|
-
return { success: true };
|
|
1142
|
-
}
|
|
1143
|
-
catch (error) {
|
|
1144
|
-
const message = formatApiError(error);
|
|
1145
|
-
logger.error(`reflect_memory failed: ${message}`);
|
|
1146
|
-
return { success: false, error: message };
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
async function syncMemory(_args, _context) {
|
|
1150
|
-
if (!isEnabled) {
|
|
1151
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1152
|
-
}
|
|
1153
|
-
try {
|
|
1154
|
-
await apiCallWithRetry("/sync", "POST", undefined, {
|
|
1155
|
-
timeout: 300000,
|
|
1156
|
-
maxRetries: 2,
|
|
1157
|
-
});
|
|
1158
|
-
return { success: true };
|
|
1159
|
-
}
|
|
1160
|
-
catch (error) {
|
|
1161
|
-
const message = formatApiError(error);
|
|
1162
|
-
logger.error(`sync_memory failed: ${message}`);
|
|
1163
|
-
return { success: false, error: message };
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
async function promoteMemory(_args, _context) {
|
|
1167
|
-
if (!isEnabled) {
|
|
1168
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1169
|
-
}
|
|
1170
|
-
try {
|
|
1171
|
-
await apiCallWithRetry("/promote", "POST", undefined, {
|
|
1172
|
-
timeout: 120000,
|
|
1173
|
-
maxRetries: 2,
|
|
1174
|
-
});
|
|
1175
|
-
return { success: true };
|
|
1176
|
-
}
|
|
1177
|
-
catch (error) {
|
|
1178
|
-
const message = formatApiError(error);
|
|
1179
|
-
logger.error(`promote_memory failed: ${message}`);
|
|
1180
|
-
return { success: false, error: message };
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
async function deleteMemory(args, _context) {
|
|
1184
|
-
if (!isEnabled) {
|
|
1185
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1186
|
-
try {
|
|
1187
|
-
const success = await builtinMemory.delete(args.memory_id);
|
|
1188
|
-
return { success };
|
|
605
|
+
const parseVersion = (v) => {
|
|
606
|
+
const parts = v.replace(/[^0-9.]/g, "").split(".").map(Number);
|
|
607
|
+
return parts.length >= 3 ? parts : [...parts, ...Array(3 - parts.length).fill(0)];
|
|
608
|
+
};
|
|
609
|
+
const current = parseVersion(version);
|
|
610
|
+
const min = parseVersion(MIN_OPENCLAW_VERSION);
|
|
611
|
+
const max = parseVersion(MAX_OPENCLAW_VERSION);
|
|
612
|
+
const currentNum = current[0] * 10000 + current[1] * 100 + current[2];
|
|
613
|
+
const minNum = min[0] * 10000 + min[1] * 100 + min[2];
|
|
614
|
+
const maxNum = max[0] * 10000 + max[1] * 100 + max[2];
|
|
615
|
+
if (currentNum < minNum) {
|
|
616
|
+
logger.warn(`OpenClaw version ${version} is below minimum ${MIN_OPENCLAW_VERSION}. Some features may not work.`);
|
|
1189
617
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
return { success: false, error: `Builtin memory error: ${message}` };
|
|
618
|
+
else if (currentNum >= maxNum) {
|
|
619
|
+
logger.warn(`OpenClaw version ${version} may not be fully compatible. Maximum tested version is ${MAX_OPENCLAW_VERSION}.`);
|
|
1193
620
|
}
|
|
1194
621
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
}
|
|
1201
|
-
catch (error) {
|
|
1202
|
-
const message = formatApiError(error);
|
|
1203
|
-
logger.error(`delete_memory failed: ${message}`);
|
|
1204
|
-
return { success: false, error: message };
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
async function updateMemory(args, _context) {
|
|
1208
|
-
if (!isEnabled) {
|
|
1209
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1210
|
-
}
|
|
1211
|
-
try {
|
|
1212
|
-
await apiCallWithRetry(`/memory/${args.memory_id}`, "PATCH", {
|
|
1213
|
-
text: args.text,
|
|
1214
|
-
type: args.type,
|
|
1215
|
-
weight: args.weight,
|
|
1216
|
-
});
|
|
1217
|
-
return { success: true };
|
|
1218
|
-
}
|
|
1219
|
-
catch (error) {
|
|
1220
|
-
const message = formatApiError(error);
|
|
1221
|
-
logger.error(`update_memory failed: ${message}`);
|
|
1222
|
-
return { success: false, error: message };
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
async function cleanupMemories(args, _context) {
|
|
1226
|
-
if (!isEnabled) {
|
|
1227
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1228
|
-
}
|
|
1229
|
-
try {
|
|
1230
|
-
const result = await apiCallWithRetry("/cleanup", "POST", {
|
|
1231
|
-
days_old: args.days_old || 90,
|
|
1232
|
-
memory_type: args.memory_type,
|
|
1233
|
-
});
|
|
1234
|
-
return { success: true, data: { deletedCount: result.deleted_count } };
|
|
1235
|
-
}
|
|
1236
|
-
catch (error) {
|
|
1237
|
-
const message = formatApiError(error);
|
|
1238
|
-
logger.error(`cleanup_memories failed: ${message}`);
|
|
1239
|
-
return { success: false, error: message };
|
|
1240
|
-
}
|
|
622
|
+
catch (e) {
|
|
623
|
+
logger.warn(`Version check failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
624
|
+
}
|
|
625
|
+
resolve();
|
|
626
|
+
});
|
|
1241
627
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
try {
|
|
1254
|
-
const result = await apiCallWithRetry("/doctor", "GET");
|
|
1255
|
-
return { success: true, data: result };
|
|
628
|
+
function findOpenClawConfig() {
|
|
629
|
+
const explicitPath = (0, runtime_env_1.getEnvValue)("OPENCLAW_CONFIG_PATH");
|
|
630
|
+
if (explicitPath && fs.existsSync(explicitPath)) {
|
|
631
|
+
return explicitPath;
|
|
632
|
+
}
|
|
633
|
+
const stateDir = (0, runtime_env_1.getEnvValue)("OPENCLAW_STATE_DIR");
|
|
634
|
+
if (stateDir) {
|
|
635
|
+
const stateConfig = path.join(stateDir, "openclaw.json");
|
|
636
|
+
if (fs.existsSync(stateConfig)) {
|
|
637
|
+
return stateConfig;
|
|
638
|
+
}
|
|
1256
639
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
640
|
+
const basePath = (0, runtime_env_1.getEnvValue)("OPENCLAW_BASE_PATH");
|
|
641
|
+
if (basePath) {
|
|
642
|
+
const baseConfig = path.join(basePath, "openclaw.json");
|
|
643
|
+
if (fs.existsSync(baseConfig)) {
|
|
644
|
+
return baseConfig;
|
|
645
|
+
}
|
|
1261
646
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
builtin_memory_available: builtinMemory !== null,
|
|
1271
|
-
engine_mode: config?.engineMode ?? "python",
|
|
647
|
+
const home = (0, runtime_env_1.getHomeDir)();
|
|
648
|
+
const candidates = [
|
|
649
|
+
path.join(home, ".openclaw", "openclaw.json"),
|
|
650
|
+
path.join(home, ".openclaw", "config.json"),
|
|
651
|
+
];
|
|
652
|
+
for (const c of candidates) {
|
|
653
|
+
if (fs.existsSync(c)) {
|
|
654
|
+
return c;
|
|
1272
655
|
}
|
|
1273
|
-
}
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
1274
658
|
}
|
|
1275
|
-
|
|
1276
|
-
if (!isEnabled)
|
|
1277
|
-
return;
|
|
1278
|
-
const normalized = normalizeIncomingMessage(payload);
|
|
1279
|
-
if (!normalized)
|
|
1280
|
-
return;
|
|
1281
|
-
const { text, role, source } = normalized;
|
|
1282
|
-
const sessionId = resolveSessionId(context, payload);
|
|
659
|
+
function loadPluginEnabledState() {
|
|
1283
660
|
try {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
logger.info(`Stored ${role} message for session ${sessionId}`);
|
|
661
|
+
if (!configPath)
|
|
662
|
+
return true;
|
|
663
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
664
|
+
const cfg = JSON.parse(raw);
|
|
665
|
+
const entry = cfg?.plugins?.entries?.[PLUGIN_ID];
|
|
666
|
+
if (entry && typeof entry.enabled === "boolean") {
|
|
667
|
+
return entry.enabled;
|
|
1292
668
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
669
|
+
const allow = cfg?.plugins?.allow;
|
|
670
|
+
if (Array.isArray(allow)) {
|
|
671
|
+
return allow.includes(PLUGIN_ID);
|
|
1295
672
|
}
|
|
673
|
+
return true;
|
|
1296
674
|
}
|
|
1297
|
-
catch
|
|
1298
|
-
|
|
675
|
+
catch {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function startConfigWatcher() {
|
|
680
|
+
if (configWatchInterval) {
|
|
681
|
+
clearInterval(configWatchInterval);
|
|
1299
682
|
}
|
|
1300
|
-
|
|
683
|
+
configWatchInterval = setInterval(() => {
|
|
1301
684
|
try {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
685
|
+
if (!configPath || !fs.existsSync(configPath))
|
|
686
|
+
return;
|
|
687
|
+
const currentEnabled = loadPluginEnabledState();
|
|
688
|
+
if (currentEnabled !== isEnabled) {
|
|
689
|
+
logger.info(`Plugin enabled state changed from ${isEnabled} to ${currentEnabled}`);
|
|
690
|
+
if (currentEnabled) {
|
|
691
|
+
enable().catch(e => logger.error(`Failed to enable: ${e}`));
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
disable().catch(e => logger.error(`Failed to disable: ${e}`));
|
|
695
|
+
}
|
|
1313
696
|
}
|
|
1314
697
|
}
|
|
1315
|
-
catch (
|
|
1316
|
-
logger.debug(`
|
|
698
|
+
catch (e) {
|
|
699
|
+
logger.debug(`Config watch error: ${e}`);
|
|
1317
700
|
}
|
|
701
|
+
}, 5000);
|
|
702
|
+
}
|
|
703
|
+
function stopConfigWatcher() {
|
|
704
|
+
if (configWatchInterval) {
|
|
705
|
+
clearInterval(configWatchInterval);
|
|
706
|
+
configWatchInterval = null;
|
|
1318
707
|
}
|
|
1319
708
|
}
|
|
1320
|
-
|
|
1321
|
-
if (!
|
|
709
|
+
function startAutoReflectScheduler() {
|
|
710
|
+
if (!config?.autoReflect) {
|
|
1322
711
|
return;
|
|
1323
|
-
const sessionId = resolveSessionId(context, payload);
|
|
1324
|
-
try {
|
|
1325
|
-
const endResult = await apiCallWithRetry("/session-end", "POST", {
|
|
1326
|
-
session_id: sessionId,
|
|
1327
|
-
sync_records: config?.autoSync ?? true,
|
|
1328
|
-
});
|
|
1329
|
-
logger.info(`Session ${sessionId} ended, generated ${endResult.events_generated} events`);
|
|
1330
712
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
713
|
+
const intervalMinutes = Math.max(5, config.autoReflectIntervalMinutes ?? 30);
|
|
714
|
+
if (autoReflectInterval) {
|
|
715
|
+
clearInterval(autoReflectInterval);
|
|
1333
716
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
}
|
|
1355
|
-
else if (action === "promote") {
|
|
1356
|
-
await apiCallWithRetry("/promote", "POST", undefined, {
|
|
1357
|
-
timeout: 120000,
|
|
1358
|
-
maxRetries: 2,
|
|
1359
|
-
});
|
|
1360
|
-
logger.info("Scheduled promotion complete");
|
|
717
|
+
lastAutoReflectArchiveMarker = getArchiveMarker();
|
|
718
|
+
lastAutoReflectRunAt = Date.now();
|
|
719
|
+
autoReflectInterval = setInterval(async () => {
|
|
720
|
+
if (!isEnabled)
|
|
721
|
+
return;
|
|
722
|
+
const currentMarker = getArchiveMarker();
|
|
723
|
+
if (currentMarker !== lastAutoReflectArchiveMarker) {
|
|
724
|
+
lastAutoReflectArchiveMarker = currentMarker;
|
|
725
|
+
try {
|
|
726
|
+
const result = await resolveEngine().reflectMemory({}, { agentId: "scheduler", workspaceId: "default" });
|
|
727
|
+
if (result.success) {
|
|
728
|
+
logger.info("Auto-reflect completed successfully");
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
logger.warn(`Auto-reflect failed: ${result.error}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
catch (e) {
|
|
735
|
+
logger.error(`Auto-reflect error: ${e instanceof Error ? e.message : String(e)}`);
|
|
736
|
+
}
|
|
1361
737
|
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
|
|
738
|
+
}, intervalMinutes * 60 * 1000);
|
|
739
|
+
logger.info(`Auto-reflect scheduler started (interval: ${intervalMinutes} minutes)`);
|
|
740
|
+
}
|
|
741
|
+
function stopAutoReflectScheduler() {
|
|
742
|
+
if (autoReflectInterval) {
|
|
743
|
+
clearInterval(autoReflectInterval);
|
|
744
|
+
autoReflectInterval = null;
|
|
1365
745
|
}
|
|
1366
746
|
}
|
|
747
|
+
function logLifecycle(event, data) {
|
|
748
|
+
logger.info(`[Lifecycle] ${event}${data ? `: ${JSON.stringify(sanitizeForLogging(data))}` : ""}`);
|
|
749
|
+
}
|
|
1367
750
|
async function onMessageHandler(payload, context) {
|
|
1368
|
-
|
|
751
|
+
const sessionId = resolveSessionId(context, payload);
|
|
752
|
+
if (isInternalSession(sessionId)) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
await runWithTimeout(resolveEngine().onMessage(payload, context), HOOK_GUARD_TIMEOUT_MS, "onMessage hook");
|
|
1369
756
|
}
|
|
1370
757
|
async function onSessionEndHandler(payload, context) {
|
|
1371
|
-
|
|
758
|
+
const sessionId = resolveSessionId(context, payload);
|
|
759
|
+
if (isInternalSession(sessionId)) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
await runWithTimeout(resolveEngine().onSessionEnd(payload, context), HOOK_GUARD_TIMEOUT_MS, "onSessionEnd hook");
|
|
1372
763
|
}
|
|
1373
764
|
async function onTimerHandler(payload, context) {
|
|
1374
765
|
await resolveEngine().onTimer(payload, context);
|
|
@@ -1390,7 +781,10 @@ function registerTools() {
|
|
|
1390
781
|
additionalProperties: false,
|
|
1391
782
|
},
|
|
1392
783
|
execute: async (params) => {
|
|
784
|
+
logger.info(`search_memory execute called with params: ${JSON.stringify(params)}`);
|
|
785
|
+
logger.info(`params.args: ${JSON.stringify(params.args)}`);
|
|
1393
786
|
const args = params.args || params;
|
|
787
|
+
logger.info(`args after extraction: ${JSON.stringify(args)}`);
|
|
1394
788
|
return resolveEngine().searchMemory(args, params.context);
|
|
1395
789
|
},
|
|
1396
790
|
},
|
|
@@ -1404,13 +798,47 @@ function registerTools() {
|
|
|
1404
798
|
entities: {
|
|
1405
799
|
type: "array",
|
|
1406
800
|
description: "Involved entities",
|
|
1407
|
-
items: {
|
|
801
|
+
items: {
|
|
802
|
+
oneOf: [
|
|
803
|
+
{ type: "string" },
|
|
804
|
+
{
|
|
805
|
+
type: "object",
|
|
806
|
+
properties: {
|
|
807
|
+
id: { type: "string" },
|
|
808
|
+
name: { type: "string" },
|
|
809
|
+
type: { type: "string" },
|
|
810
|
+
},
|
|
811
|
+
additionalProperties: false,
|
|
812
|
+
},
|
|
813
|
+
],
|
|
814
|
+
},
|
|
815
|
+
},
|
|
816
|
+
entity_types: {
|
|
817
|
+
type: "object",
|
|
818
|
+
description: "Entity type map, key is entity name and value is type",
|
|
819
|
+
additionalProperties: { type: "string" },
|
|
1408
820
|
},
|
|
1409
821
|
outcome: { type: "string", description: "Event outcome" },
|
|
1410
822
|
relations: {
|
|
1411
823
|
type: "array",
|
|
1412
824
|
description: "Entity relationships",
|
|
1413
|
-
items: {
|
|
825
|
+
items: {
|
|
826
|
+
oneOf: [
|
|
827
|
+
{ type: "string" },
|
|
828
|
+
{
|
|
829
|
+
type: "object",
|
|
830
|
+
properties: {
|
|
831
|
+
source: { type: "string" },
|
|
832
|
+
target: { type: "string" },
|
|
833
|
+
type: { type: "string" },
|
|
834
|
+
evidence_span: { type: "string" },
|
|
835
|
+
confidence: { type: "number" },
|
|
836
|
+
},
|
|
837
|
+
required: ["source", "target"],
|
|
838
|
+
additionalProperties: false,
|
|
839
|
+
},
|
|
840
|
+
],
|
|
841
|
+
}
|
|
1414
842
|
},
|
|
1415
843
|
},
|
|
1416
844
|
required: ["summary"],
|
|
@@ -1427,7 +855,15 @@ function registerTools() {
|
|
|
1427
855
|
parameters: {
|
|
1428
856
|
type: "object",
|
|
1429
857
|
properties: {
|
|
1430
|
-
entity: { type: "string", description: "Entity name" }
|
|
858
|
+
entity: { type: "string", description: "Entity name" },
|
|
859
|
+
rel: { type: "string", description: "Optional relation type filter" },
|
|
860
|
+
dir: {
|
|
861
|
+
type: "string",
|
|
862
|
+
description: "Relation direction filter",
|
|
863
|
+
enum: ["incoming", "outgoing", "both"],
|
|
864
|
+
},
|
|
865
|
+
path_to: { type: "string", description: "Find path from entity to this target entity" },
|
|
866
|
+
max_depth: { type: "integer", description: "Path query max depth (2~4)" },
|
|
1431
867
|
},
|
|
1432
868
|
required: ["entity"],
|
|
1433
869
|
additionalProperties: false,
|
|
@@ -1497,6 +933,26 @@ function registerTools() {
|
|
|
1497
933
|
return resolveEngine().syncMemory(args, params.context);
|
|
1498
934
|
},
|
|
1499
935
|
},
|
|
936
|
+
{
|
|
937
|
+
name: "backfill_embeddings",
|
|
938
|
+
description: "Backfill missing embeddings for active/archive records",
|
|
939
|
+
parameters: {
|
|
940
|
+
type: "object",
|
|
941
|
+
properties: {
|
|
942
|
+
layer: { type: "string", enum: ["active", "archive", "all"], description: "Target layer to backfill" },
|
|
943
|
+
batch_size: { type: "integer", description: "Batch size per processing window" },
|
|
944
|
+
max_retries: { type: "integer", description: "Max retry count for failed records" },
|
|
945
|
+
retry_failed_only: { type: "boolean", description: "Only retry failed records" },
|
|
946
|
+
rebuild_mode: { type: "string", enum: ["incremental", "vector_only", "full"], description: "Rebuild mode" },
|
|
947
|
+
},
|
|
948
|
+
required: [],
|
|
949
|
+
additionalProperties: false,
|
|
950
|
+
},
|
|
951
|
+
execute: async (params) => {
|
|
952
|
+
const args = params.args || params;
|
|
953
|
+
return resolveEngine().backfillEmbeddings(args, params.context);
|
|
954
|
+
},
|
|
955
|
+
},
|
|
1500
956
|
{
|
|
1501
957
|
name: "delete_memory",
|
|
1502
958
|
description: "Delete a memory by ID",
|
|
@@ -1533,16 +989,164 @@ function registerTools() {
|
|
|
1533
989
|
registeredTools.push(tool.name);
|
|
1534
990
|
}
|
|
1535
991
|
}
|
|
992
|
+
function sanitizeToolParametersSchemaValue(schema) {
|
|
993
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
994
|
+
return {};
|
|
995
|
+
}
|
|
996
|
+
const source = schema;
|
|
997
|
+
const target = {};
|
|
998
|
+
if (typeof source.type === "string") {
|
|
999
|
+
target.type = source.type;
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof source.description === "string" && source.description.trim()) {
|
|
1002
|
+
target.description = source.description;
|
|
1003
|
+
}
|
|
1004
|
+
if (Array.isArray(source.enum)) {
|
|
1005
|
+
const values = source.enum.filter(item => typeof item === "string" || typeof item === "number" || typeof item === "boolean" || item === null);
|
|
1006
|
+
if (values.length > 0) {
|
|
1007
|
+
target.enum = values;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
if (source.properties && typeof source.properties === "object" && !Array.isArray(source.properties)) {
|
|
1011
|
+
const sanitizedProperties = {};
|
|
1012
|
+
for (const [key, value] of Object.entries(source.properties)) {
|
|
1013
|
+
sanitizedProperties[key] = sanitizeToolParametersSchemaValue(value);
|
|
1014
|
+
}
|
|
1015
|
+
target.properties = sanitizedProperties;
|
|
1016
|
+
}
|
|
1017
|
+
if (Array.isArray(source.required)) {
|
|
1018
|
+
const required = source.required.filter(item => typeof item === "string");
|
|
1019
|
+
if (required.length > 0) {
|
|
1020
|
+
target.required = required;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (source.items && typeof source.items === "object" && !Array.isArray(source.items)) {
|
|
1024
|
+
target.items = sanitizeToolParametersSchemaValue(source.items);
|
|
1025
|
+
}
|
|
1026
|
+
if (typeof source.additionalProperties === "boolean") {
|
|
1027
|
+
target.additionalProperties = source.additionalProperties;
|
|
1028
|
+
}
|
|
1029
|
+
else if (source.additionalProperties && typeof source.additionalProperties === "object") {
|
|
1030
|
+
target.additionalProperties = true;
|
|
1031
|
+
}
|
|
1032
|
+
return target;
|
|
1033
|
+
}
|
|
1034
|
+
function sanitizeToolParametersSchema(schema) {
|
|
1035
|
+
const sanitized = sanitizeToolParametersSchemaValue(schema);
|
|
1036
|
+
if (sanitized.type !== "object") {
|
|
1037
|
+
sanitized.type = "object";
|
|
1038
|
+
}
|
|
1039
|
+
if (!sanitized.properties || typeof sanitized.properties !== "object" || Array.isArray(sanitized.properties)) {
|
|
1040
|
+
sanitized.properties = {};
|
|
1041
|
+
}
|
|
1042
|
+
if (!Array.isArray(sanitized.required)) {
|
|
1043
|
+
sanitized.required = [];
|
|
1044
|
+
}
|
|
1045
|
+
if (typeof sanitized.additionalProperties !== "boolean") {
|
|
1046
|
+
sanitized.additionalProperties = false;
|
|
1047
|
+
}
|
|
1048
|
+
return sanitized;
|
|
1049
|
+
}
|
|
1536
1050
|
function registerToolCompat(tool) {
|
|
1537
1051
|
if (!api)
|
|
1538
1052
|
return;
|
|
1539
|
-
const
|
|
1053
|
+
const normalizeContext = (value) => {
|
|
1054
|
+
const contextObj = asRecord(value) || {};
|
|
1055
|
+
return {
|
|
1056
|
+
agentId: firstString([contextObj.agentId, contextObj.agent_id]) || "unknown-agent",
|
|
1057
|
+
workspaceId: firstString([contextObj.workspaceId, contextObj.workspace_id]) || "default",
|
|
1058
|
+
sessionId: firstString([contextObj.sessionId, contextObj.session_id]) || undefined,
|
|
1059
|
+
};
|
|
1060
|
+
};
|
|
1061
|
+
const normalizeInvocation = (...params) => {
|
|
1062
|
+
logger.info(`normalizeInvocation called with params: ${JSON.stringify(params)}`);
|
|
1063
|
+
if (params.length === 1) {
|
|
1064
|
+
const first = params[0];
|
|
1065
|
+
const firstObj = asRecord(first);
|
|
1066
|
+
if (firstObj && ("context" in firstObj || "args" in firstObj)) {
|
|
1067
|
+
const explicitArgs = asRecord(firstObj.args);
|
|
1068
|
+
if (explicitArgs) {
|
|
1069
|
+
logger.info(`normalizeInvocation: single param with explicit args: ${JSON.stringify(explicitArgs)}`);
|
|
1070
|
+
return {
|
|
1071
|
+
args: explicitArgs,
|
|
1072
|
+
context: normalizeContext(firstObj.context),
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
const directArgs = { ...firstObj };
|
|
1076
|
+
delete directArgs.context;
|
|
1077
|
+
delete directArgs.args;
|
|
1078
|
+
logger.info(`normalizeInvocation: single param with direct args: ${JSON.stringify(directArgs)}`);
|
|
1079
|
+
return {
|
|
1080
|
+
args: directArgs,
|
|
1081
|
+
context: normalizeContext(firstObj.context),
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
if (firstObj) {
|
|
1085
|
+
logger.info(`normalizeInvocation: single param as args: ${JSON.stringify(firstObj)}`);
|
|
1086
|
+
return {
|
|
1087
|
+
args: firstObj,
|
|
1088
|
+
context: normalizeContext(undefined),
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
const first = params[0];
|
|
1093
|
+
const second = params[1];
|
|
1094
|
+
const third = params[2];
|
|
1095
|
+
const firstObj = asRecord(first);
|
|
1096
|
+
const secondObj = asRecord(second);
|
|
1097
|
+
if (typeof first === "string" && secondObj) {
|
|
1098
|
+
logger.info(`normalizeInvocation: first is string (tool call ID), second is args: ${JSON.stringify(secondObj)}`);
|
|
1099
|
+
return {
|
|
1100
|
+
args: secondObj,
|
|
1101
|
+
context: normalizeContext(third),
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
if (firstObj && ("context" in firstObj || "args" in firstObj)) {
|
|
1105
|
+
const explicitArgs = asRecord(firstObj.args);
|
|
1106
|
+
if (explicitArgs) {
|
|
1107
|
+
logger.info(`normalizeInvocation: first has explicit args: ${JSON.stringify(explicitArgs)}`);
|
|
1108
|
+
return {
|
|
1109
|
+
args: explicitArgs,
|
|
1110
|
+
context: normalizeContext(firstObj.context),
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
const directArgs = { ...firstObj };
|
|
1114
|
+
delete directArgs.context;
|
|
1115
|
+
delete directArgs.args;
|
|
1116
|
+
logger.info(`normalizeInvocation: first has direct args: ${JSON.stringify(directArgs)}`);
|
|
1117
|
+
return {
|
|
1118
|
+
args: directArgs,
|
|
1119
|
+
context: normalizeContext(firstObj.context),
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
if (firstObj && Object.keys(firstObj).length > 0) {
|
|
1123
|
+
logger.info(`normalizeInvocation: first is args, second is context: ${JSON.stringify(firstObj)}`);
|
|
1124
|
+
return {
|
|
1125
|
+
args: firstObj,
|
|
1126
|
+
context: normalizeContext(second),
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
logger.info(`normalizeInvocation: fallback to firstObj as args: ${JSON.stringify(firstObj)}`);
|
|
1130
|
+
return {
|
|
1131
|
+
args: firstObj || {},
|
|
1132
|
+
context: normalizeContext(second),
|
|
1133
|
+
};
|
|
1134
|
+
};
|
|
1135
|
+
const invoke = async (...params) => {
|
|
1136
|
+
logger.info(`invoke called for tool ${tool.name} with params: ${JSON.stringify(params)}`);
|
|
1137
|
+
const normalized = normalizeInvocation(...params);
|
|
1138
|
+
logger.info(`invoke: normalized args=${JSON.stringify(normalized.args)}, context=${JSON.stringify(normalized.context)}`);
|
|
1139
|
+
return tool.execute({
|
|
1140
|
+
args: normalized.args,
|
|
1141
|
+
context: normalized.context,
|
|
1142
|
+
});
|
|
1143
|
+
};
|
|
1540
1144
|
api.registerTool({
|
|
1541
1145
|
name: tool.name,
|
|
1542
1146
|
description: tool.description,
|
|
1543
|
-
parameters: tool.parameters,
|
|
1544
|
-
execute,
|
|
1545
|
-
handler:
|
|
1147
|
+
parameters: sanitizeToolParametersSchema(tool.parameters),
|
|
1148
|
+
execute: invoke,
|
|
1149
|
+
handler: invoke,
|
|
1546
1150
|
});
|
|
1547
1151
|
}
|
|
1548
1152
|
function unregisterTools() {
|
|
@@ -1558,6 +1162,108 @@ function unregisterTools() {
|
|
|
1558
1162
|
}
|
|
1559
1163
|
registeredTools = [];
|
|
1560
1164
|
}
|
|
1165
|
+
function registerFallbackTools() {
|
|
1166
|
+
if (!api || !builtinMemory)
|
|
1167
|
+
return;
|
|
1168
|
+
for (const name of ["search_memory", "store_event", "cortex_memory_status"]) {
|
|
1169
|
+
try {
|
|
1170
|
+
if (api.unregisterTool) {
|
|
1171
|
+
api.unregisterTool(name);
|
|
1172
|
+
logger.info(`Unregistered existing tool ${name} before registering fallback`);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
catch (e) {
|
|
1176
|
+
// ignore
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
registerToolCompat({
|
|
1180
|
+
name: "search_memory",
|
|
1181
|
+
description: "Search memory (using builtin system - Cortex Memory disabled)",
|
|
1182
|
+
parameters: {
|
|
1183
|
+
type: "object",
|
|
1184
|
+
properties: {
|
|
1185
|
+
query: { type: "string", description: "Search query" },
|
|
1186
|
+
top_k: { type: "integer", description: "Number of results" },
|
|
1187
|
+
},
|
|
1188
|
+
required: ["query"],
|
|
1189
|
+
additionalProperties: false,
|
|
1190
|
+
},
|
|
1191
|
+
execute: async (params) => {
|
|
1192
|
+
const args = (params.args || params);
|
|
1193
|
+
const query = args.query || "";
|
|
1194
|
+
const topK = args.top_k || 5;
|
|
1195
|
+
try {
|
|
1196
|
+
const results = await builtinMemory.search(query, topK);
|
|
1197
|
+
return { success: true, data: results };
|
|
1198
|
+
}
|
|
1199
|
+
catch (error) {
|
|
1200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1201
|
+
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
});
|
|
1205
|
+
registeredFallbackTools.push("search_memory");
|
|
1206
|
+
registerToolCompat({
|
|
1207
|
+
name: "store_event",
|
|
1208
|
+
description: "Store event (using builtin system - Cortex Memory disabled)",
|
|
1209
|
+
parameters: {
|
|
1210
|
+
type: "object",
|
|
1211
|
+
properties: {
|
|
1212
|
+
summary: { type: "string", description: "Event summary" },
|
|
1213
|
+
},
|
|
1214
|
+
required: ["summary"],
|
|
1215
|
+
additionalProperties: false,
|
|
1216
|
+
},
|
|
1217
|
+
execute: async (params) => {
|
|
1218
|
+
const args = (params.args || params);
|
|
1219
|
+
const summary = args.summary || "";
|
|
1220
|
+
try {
|
|
1221
|
+
const id = await builtinMemory.store(summary);
|
|
1222
|
+
return { success: true, data: { id } };
|
|
1223
|
+
}
|
|
1224
|
+
catch (error) {
|
|
1225
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1226
|
+
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
});
|
|
1230
|
+
registeredFallbackTools.push("store_event");
|
|
1231
|
+
registerToolCompat({
|
|
1232
|
+
name: "cortex_memory_status",
|
|
1233
|
+
description: "Get the current status of the Cortex Memory plugin",
|
|
1234
|
+
parameters: {
|
|
1235
|
+
type: "object",
|
|
1236
|
+
properties: {},
|
|
1237
|
+
required: [],
|
|
1238
|
+
additionalProperties: false,
|
|
1239
|
+
},
|
|
1240
|
+
execute: async (_params) => {
|
|
1241
|
+
return {
|
|
1242
|
+
success: true,
|
|
1243
|
+
data: {
|
|
1244
|
+
enabled: isEnabled,
|
|
1245
|
+
fallback_enabled: config?.fallbackToBuiltin ?? true,
|
|
1246
|
+
builtin_memory_available: builtinMemory !== null,
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
},
|
|
1250
|
+
});
|
|
1251
|
+
registeredFallbackTools.push("cortex_memory_status");
|
|
1252
|
+
logger.info(`Registered ${registeredFallbackTools.length} fallback tools`);
|
|
1253
|
+
}
|
|
1254
|
+
function unregisterFallbackTools() {
|
|
1255
|
+
if (!api || !api.unregisterTool)
|
|
1256
|
+
return;
|
|
1257
|
+
for (const name of registeredFallbackTools) {
|
|
1258
|
+
try {
|
|
1259
|
+
api.unregisterTool(name);
|
|
1260
|
+
}
|
|
1261
|
+
catch (e) {
|
|
1262
|
+
logger.warn(`Failed to unregister fallback tool ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
registeredFallbackTools = [];
|
|
1266
|
+
}
|
|
1561
1267
|
function registerHooks() {
|
|
1562
1268
|
if (!api)
|
|
1563
1269
|
return;
|
|
@@ -1618,27 +1324,15 @@ function setupProcessHandlers() {
|
|
|
1618
1324
|
isShuttingDown = true;
|
|
1619
1325
|
logger.info(`Received ${signal}, shutting down...`);
|
|
1620
1326
|
stopConfigWatcher();
|
|
1621
|
-
|
|
1622
|
-
process.exit(0);
|
|
1623
|
-
return;
|
|
1624
|
-
}
|
|
1625
|
-
shutdownPythonApi().then(() => {
|
|
1626
|
-
killPythonProcess();
|
|
1627
|
-
process.exit(0);
|
|
1628
|
-
}).catch(() => {
|
|
1629
|
-
killPythonProcess();
|
|
1630
|
-
process.exit(0);
|
|
1631
|
-
});
|
|
1327
|
+
process.exit(0);
|
|
1632
1328
|
};
|
|
1633
1329
|
process.on("exit", () => {
|
|
1634
|
-
killPythonProcess();
|
|
1635
1330
|
stopConfigWatcher();
|
|
1636
1331
|
});
|
|
1637
1332
|
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
1638
1333
|
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
1639
1334
|
process.on("uncaughtException", (err) => {
|
|
1640
1335
|
logger.error("Uncaught exception:", err.message);
|
|
1641
|
-
killPythonProcess();
|
|
1642
1336
|
stopConfigWatcher();
|
|
1643
1337
|
process.exit(1);
|
|
1644
1338
|
});
|
|
@@ -1649,21 +1343,20 @@ async function enable() {
|
|
|
1649
1343
|
return;
|
|
1650
1344
|
}
|
|
1651
1345
|
logger.info("Enabling Cortex Memory plugin...");
|
|
1346
|
+
logLifecycle("enable_start");
|
|
1652
1347
|
try {
|
|
1653
1348
|
unregisterFallbackTools();
|
|
1654
|
-
if (shouldUsePythonRuntime()) {
|
|
1655
|
-
await startPythonService();
|
|
1656
|
-
await waitForService();
|
|
1657
|
-
}
|
|
1658
1349
|
isEnabled = true;
|
|
1659
1350
|
registerTools();
|
|
1660
1351
|
registerHooks();
|
|
1661
1352
|
startAutoReflectScheduler();
|
|
1662
1353
|
logger.info("Cortex Memory plugin enabled successfully");
|
|
1354
|
+
logLifecycle("enable_success", { registeredTools: registeredTools.length, registeredHooks: registeredHooks.length });
|
|
1663
1355
|
}
|
|
1664
1356
|
catch (error) {
|
|
1665
1357
|
const message = error instanceof Error ? error.message : String(error);
|
|
1666
1358
|
logger.error(`Failed to enable Cortex Memory plugin: ${message}`);
|
|
1359
|
+
logLifecycle("enable_failed", { error: message });
|
|
1667
1360
|
throw error;
|
|
1668
1361
|
}
|
|
1669
1362
|
}
|
|
@@ -1673,113 +1366,48 @@ async function disable() {
|
|
|
1673
1366
|
return;
|
|
1674
1367
|
}
|
|
1675
1368
|
logger.info("Disabling Cortex Memory plugin...");
|
|
1369
|
+
logLifecycle("disable_start");
|
|
1676
1370
|
unregisterHooks();
|
|
1677
1371
|
unregisterTools();
|
|
1678
|
-
unregisterFallbackTools();
|
|
1679
1372
|
stopAutoReflectScheduler();
|
|
1680
|
-
if (shouldUsePythonRuntime()) {
|
|
1681
|
-
await stopPythonServiceAsync();
|
|
1682
|
-
}
|
|
1683
1373
|
isEnabled = false;
|
|
1684
1374
|
memoryEngine = null;
|
|
1685
1375
|
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1686
1376
|
logger.info("Falling back to OpenClaw builtin memory system");
|
|
1687
1377
|
registerFallbackTools();
|
|
1378
|
+
logLifecycle("fallback_enabled", { fallbackTools: registeredFallbackTools.length });
|
|
1688
1379
|
}
|
|
1689
1380
|
logger.info("Cortex Memory plugin disabled successfully");
|
|
1690
|
-
}
|
|
1691
|
-
function registerFallbackTools() {
|
|
1692
|
-
if (!api || !builtinMemory)
|
|
1693
|
-
return;
|
|
1694
|
-
registerToolCompat({
|
|
1695
|
-
name: "search_memory",
|
|
1696
|
-
description: "Search memory (using builtin system - Cortex Memory disabled)",
|
|
1697
|
-
parameters: {
|
|
1698
|
-
type: "object",
|
|
1699
|
-
properties: {
|
|
1700
|
-
query: { type: "string", description: "Search query" },
|
|
1701
|
-
top_k: { type: "integer", description: "Number of results" },
|
|
1702
|
-
},
|
|
1703
|
-
required: ["query"],
|
|
1704
|
-
additionalProperties: false,
|
|
1705
|
-
},
|
|
1706
|
-
execute: async ({ args, context }) => searchMemoryWithFallback((args || {}), context),
|
|
1707
|
-
});
|
|
1708
|
-
registeredFallbackTools.push("search_memory");
|
|
1709
|
-
registerToolCompat({
|
|
1710
|
-
name: "store_event",
|
|
1711
|
-
description: "Store event (using builtin system - Cortex Memory disabled)",
|
|
1712
|
-
parameters: {
|
|
1713
|
-
type: "object",
|
|
1714
|
-
properties: {
|
|
1715
|
-
summary: { type: "string", description: "Event summary" },
|
|
1716
|
-
},
|
|
1717
|
-
required: ["summary"],
|
|
1718
|
-
additionalProperties: false,
|
|
1719
|
-
},
|
|
1720
|
-
execute: async ({ args, context }) => storeEventWithFallback((args || {}), context),
|
|
1721
|
-
});
|
|
1722
|
-
registeredFallbackTools.push("store_event");
|
|
1723
|
-
registerToolCompat({
|
|
1724
|
-
name: "cortex_memory_status",
|
|
1725
|
-
description: "Get the current status of the Cortex Memory plugin",
|
|
1726
|
-
parameters: {
|
|
1727
|
-
type: "object",
|
|
1728
|
-
properties: {},
|
|
1729
|
-
required: [],
|
|
1730
|
-
additionalProperties: false,
|
|
1731
|
-
},
|
|
1732
|
-
execute: async ({ args, context }) => getPluginStatus(args || {}, context),
|
|
1733
|
-
});
|
|
1734
|
-
registeredFallbackTools.push("cortex_memory_status");
|
|
1735
|
-
}
|
|
1736
|
-
function unregisterFallbackTools() {
|
|
1737
|
-
if (!api || !api.unregisterTool)
|
|
1738
|
-
return;
|
|
1739
|
-
for (const name of registeredFallbackTools) {
|
|
1740
|
-
try {
|
|
1741
|
-
api.unregisterTool(name);
|
|
1742
|
-
}
|
|
1743
|
-
catch (e) {
|
|
1744
|
-
logger.warn(`Failed to unregister fallback tool ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
registeredFallbackTools = [];
|
|
1381
|
+
logLifecycle("disable_success", { fallbackEnabled: registeredFallbackTools.length > 0 });
|
|
1748
1382
|
}
|
|
1749
1383
|
function getStatus() {
|
|
1750
1384
|
return {
|
|
1751
|
-
enabled: isEnabled
|
|
1752
|
-
serviceRunning: pythonProcess !== null
|
|
1385
|
+
enabled: isEnabled
|
|
1753
1386
|
};
|
|
1754
1387
|
}
|
|
1755
1388
|
async function unregister() {
|
|
1756
1389
|
logger.info("Unregistering Cortex Memory plugin...");
|
|
1390
|
+
logLifecycle("unregister_start");
|
|
1757
1391
|
stopConfigWatcher();
|
|
1758
1392
|
stopAutoReflectScheduler();
|
|
1759
1393
|
unregisterHooks();
|
|
1760
1394
|
unregisterTools();
|
|
1761
1395
|
unregisterFallbackTools();
|
|
1762
|
-
if (shouldUsePythonRuntime()) {
|
|
1763
|
-
await stopPythonServiceAsync();
|
|
1764
|
-
}
|
|
1765
|
-
else {
|
|
1766
|
-
killPythonProcess();
|
|
1767
|
-
}
|
|
1768
1396
|
isEnabled = false;
|
|
1769
1397
|
isInitializing = false;
|
|
1770
1398
|
isRegistered = false;
|
|
1771
1399
|
api = null;
|
|
1772
1400
|
config = null;
|
|
1773
1401
|
autoSearchCacheBySession.clear();
|
|
1774
|
-
builtinMemory = null;
|
|
1775
1402
|
memoryEngine = null;
|
|
1403
|
+
builtinMemory = null;
|
|
1776
1404
|
registeredTools = [];
|
|
1777
1405
|
registeredHooks = [];
|
|
1778
1406
|
registeredFallbackTools = [];
|
|
1779
1407
|
registeredHookHandlers.clear();
|
|
1780
|
-
stopAutoReflectScheduler();
|
|
1781
1408
|
configPath = null;
|
|
1782
1409
|
logger.info("Cortex Memory plugin unregistered successfully");
|
|
1410
|
+
logLifecycle("unregister_success");
|
|
1783
1411
|
}
|
|
1784
1412
|
function register(pluginApi, userConfig) {
|
|
1785
1413
|
if (isInitializing || isRegistered) {
|
|
@@ -1815,10 +1443,72 @@ function register(pluginApi, userConfig) {
|
|
|
1815
1443
|
dbPath: effectiveConfig.dbPath,
|
|
1816
1444
|
autoSync: effectiveConfig.autoSync ?? defaultConfig.autoSync,
|
|
1817
1445
|
autoReflect: effectiveConfig.autoReflect ?? defaultConfig.autoReflect,
|
|
1446
|
+
autoReflectIntervalMinutes: effectiveConfig.autoReflectIntervalMinutes ?? defaultConfig.autoReflectIntervalMinutes,
|
|
1447
|
+
graphQualityMode: effectiveConfig.graphQualityMode ?? defaultConfig.graphQualityMode,
|
|
1448
|
+
readFusion: {
|
|
1449
|
+
enabled: effectiveConfig.readFusion?.enabled ?? defaultConfig.readFusion?.enabled,
|
|
1450
|
+
maxCandidates: effectiveConfig.readFusion?.maxCandidates ?? defaultConfig.readFusion?.maxCandidates,
|
|
1451
|
+
authoritative: effectiveConfig.readFusion?.authoritative ?? defaultConfig.readFusion?.authoritative,
|
|
1452
|
+
channelWeights: effectiveConfig.readFusion?.channelWeights ?? defaultConfig.readFusion?.channelWeights,
|
|
1453
|
+
channelTopK: effectiveConfig.readFusion?.channelTopK ?? defaultConfig.readFusion?.channelTopK,
|
|
1454
|
+
minLexicalHits: effectiveConfig.readFusion?.minLexicalHits ?? defaultConfig.readFusion?.minLexicalHits,
|
|
1455
|
+
minSemanticHits: effectiveConfig.readFusion?.minSemanticHits ?? defaultConfig.readFusion?.minSemanticHits,
|
|
1456
|
+
lengthNorm: {
|
|
1457
|
+
enabled: effectiveConfig.readFusion?.lengthNorm?.enabled ?? defaultConfig.readFusion?.lengthNorm?.enabled,
|
|
1458
|
+
pivotChars: effectiveConfig.readFusion?.lengthNorm?.pivotChars ?? defaultConfig.readFusion?.lengthNorm?.pivotChars,
|
|
1459
|
+
strength: effectiveConfig.readFusion?.lengthNorm?.strength ?? defaultConfig.readFusion?.lengthNorm?.strength,
|
|
1460
|
+
minFactor: effectiveConfig.readFusion?.lengthNorm?.minFactor ?? defaultConfig.readFusion?.lengthNorm?.minFactor,
|
|
1461
|
+
},
|
|
1462
|
+
},
|
|
1463
|
+
vectorChunking: {
|
|
1464
|
+
chunkSize: effectiveConfig.vectorChunking?.chunkSize ?? defaultConfig.vectorChunking?.chunkSize,
|
|
1465
|
+
chunkOverlap: effectiveConfig.vectorChunking?.chunkOverlap ?? defaultConfig.vectorChunking?.chunkOverlap,
|
|
1466
|
+
evidenceMaxChunks: effectiveConfig.vectorChunking?.evidenceMaxChunks ?? defaultConfig.vectorChunking?.evidenceMaxChunks,
|
|
1467
|
+
},
|
|
1468
|
+
writePolicy: {
|
|
1469
|
+
archiveMinConfidence: effectiveConfig.writePolicy?.archiveMinConfidence ?? defaultConfig.writePolicy?.archiveMinConfidence,
|
|
1470
|
+
archiveMinQualityScore: effectiveConfig.writePolicy?.archiveMinQualityScore ?? defaultConfig.writePolicy?.archiveMinQualityScore,
|
|
1471
|
+
activeMinQualityScore: effectiveConfig.writePolicy?.activeMinQualityScore ?? defaultConfig.writePolicy?.activeMinQualityScore,
|
|
1472
|
+
activeDedupTailLines: effectiveConfig.writePolicy?.activeDedupTailLines ?? defaultConfig.writePolicy?.activeDedupTailLines,
|
|
1473
|
+
activeTextMaxChars: effectiveConfig.writePolicy?.activeTextMaxChars ?? defaultConfig.writePolicy?.activeTextMaxChars,
|
|
1474
|
+
archiveSourceTextMaxChars: effectiveConfig.writePolicy?.archiveSourceTextMaxChars ?? defaultConfig.writePolicy?.archiveSourceTextMaxChars,
|
|
1475
|
+
},
|
|
1476
|
+
memoryDecay: {
|
|
1477
|
+
enabled: effectiveConfig.memoryDecay?.enabled ?? defaultConfig.memoryDecay?.enabled,
|
|
1478
|
+
minFloor: effectiveConfig.memoryDecay?.minFloor ?? defaultConfig.memoryDecay?.minFloor,
|
|
1479
|
+
defaultHalfLifeDays: effectiveConfig.memoryDecay?.defaultHalfLifeDays ?? defaultConfig.memoryDecay?.defaultHalfLifeDays,
|
|
1480
|
+
halfLifeByEventType: effectiveConfig.memoryDecay?.halfLifeByEventType ?? defaultConfig.memoryDecay?.halfLifeByEventType,
|
|
1481
|
+
antiDecay: {
|
|
1482
|
+
enabled: effectiveConfig.memoryDecay?.antiDecay?.enabled ?? defaultConfig.memoryDecay?.antiDecay?.enabled,
|
|
1483
|
+
maxBoost: effectiveConfig.memoryDecay?.antiDecay?.maxBoost ?? defaultConfig.memoryDecay?.antiDecay?.maxBoost,
|
|
1484
|
+
hitWeight: effectiveConfig.memoryDecay?.antiDecay?.hitWeight ?? defaultConfig.memoryDecay?.antiDecay?.hitWeight,
|
|
1485
|
+
recentWindowDays: effectiveConfig.memoryDecay?.antiDecay?.recentWindowDays ?? defaultConfig.memoryDecay?.antiDecay?.recentWindowDays,
|
|
1486
|
+
},
|
|
1487
|
+
},
|
|
1488
|
+
readTuning: {
|
|
1489
|
+
scoring: {
|
|
1490
|
+
lexicalWeight: effectiveConfig.readTuning?.scoring?.lexicalWeight ?? defaultConfig.readTuning?.scoring?.lexicalWeight,
|
|
1491
|
+
bm25Scale: effectiveConfig.readTuning?.scoring?.bm25Scale ?? defaultConfig.readTuning?.scoring?.bm25Scale,
|
|
1492
|
+
semanticWeight: effectiveConfig.readTuning?.scoring?.semanticWeight ?? defaultConfig.readTuning?.scoring?.semanticWeight,
|
|
1493
|
+
recencyWeight: effectiveConfig.readTuning?.scoring?.recencyWeight ?? defaultConfig.readTuning?.scoring?.recencyWeight,
|
|
1494
|
+
qualityWeight: effectiveConfig.readTuning?.scoring?.qualityWeight ?? defaultConfig.readTuning?.scoring?.qualityWeight,
|
|
1495
|
+
typeMatchWeight: effectiveConfig.readTuning?.scoring?.typeMatchWeight ?? defaultConfig.readTuning?.scoring?.typeMatchWeight,
|
|
1496
|
+
graphMatchWeight: effectiveConfig.readTuning?.scoring?.graphMatchWeight ?? defaultConfig.readTuning?.scoring?.graphMatchWeight,
|
|
1497
|
+
},
|
|
1498
|
+
rrf: {
|
|
1499
|
+
k: effectiveConfig.readTuning?.rrf?.k ?? defaultConfig.readTuning?.rrf?.k,
|
|
1500
|
+
weight: effectiveConfig.readTuning?.rrf?.weight ?? defaultConfig.readTuning?.rrf?.weight,
|
|
1501
|
+
},
|
|
1502
|
+
recency: {
|
|
1503
|
+
buckets: effectiveConfig.readTuning?.recency?.buckets ?? defaultConfig.readTuning?.recency?.buckets,
|
|
1504
|
+
},
|
|
1505
|
+
autoContext: {
|
|
1506
|
+
queryMaxChars: effectiveConfig.readTuning?.autoContext?.queryMaxChars ?? defaultConfig.readTuning?.autoContext?.queryMaxChars,
|
|
1507
|
+
lightweightSearch: effectiveConfig.readTuning?.autoContext?.lightweightSearch ?? defaultConfig.readTuning?.autoContext?.lightweightSearch,
|
|
1508
|
+
},
|
|
1509
|
+
},
|
|
1818
1510
|
enabled: effectiveConfig.enabled ?? defaultConfig.enabled,
|
|
1819
|
-
fallbackToBuiltin: effectiveConfig.fallbackToBuiltin ??
|
|
1820
|
-
apiUrl: effectiveConfig.apiUrl ?? "http://127.0.0.1:8765",
|
|
1821
|
-
engineMode: "ts",
|
|
1511
|
+
fallbackToBuiltin: effectiveConfig.fallbackToBuiltin ?? true,
|
|
1822
1512
|
};
|
|
1823
1513
|
memoryEngine = null;
|
|
1824
1514
|
if (api.getBuiltinMemory) {
|
|
@@ -1837,7 +1527,6 @@ function register(pluginApi, userConfig) {
|
|
|
1837
1527
|
reranker: { model: config.reranker.model },
|
|
1838
1528
|
enabled: config.enabled,
|
|
1839
1529
|
fallbackToBuiltin: config.fallbackToBuiltin,
|
|
1840
|
-
engineMode: config.engineMode,
|
|
1841
1530
|
});
|
|
1842
1531
|
checkOpenClawVersion().catch(e => logger.warn(`Version check failed: ${e}`));
|
|
1843
1532
|
configPath = findOpenClawConfig();
|
|
@@ -1852,32 +1541,13 @@ function register(pluginApi, userConfig) {
|
|
|
1852
1541
|
isRegistered = true;
|
|
1853
1542
|
logger.info("Cortex Memory plugin registered successfully");
|
|
1854
1543
|
logger.info(`Cortex Memory engine mode: ${resolveEngine().mode}`);
|
|
1544
|
+
logLifecycle("register_success", {
|
|
1545
|
+
enabled: isEnabled,
|
|
1546
|
+
});
|
|
1855
1547
|
if (isEnabled) {
|
|
1856
1548
|
registerTools();
|
|
1857
1549
|
registerHooks();
|
|
1858
1550
|
startAutoReflectScheduler();
|
|
1859
|
-
initializeAsync().catch(error => {
|
|
1860
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1861
|
-
logger.error(`Failed to initialize Cortex Memory: ${message}`);
|
|
1862
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1863
|
-
unregisterHooks();
|
|
1864
|
-
unregisterTools();
|
|
1865
|
-
logger.info("Falling back to builtin memory");
|
|
1866
|
-
isEnabled = false;
|
|
1867
|
-
registerFallbackTools();
|
|
1868
|
-
}
|
|
1869
|
-
});
|
|
1870
|
-
}
|
|
1871
|
-
else if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1872
|
-
registerFallbackTools();
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
async function initializeAsync() {
|
|
1876
|
-
if (!shouldUsePythonRuntime()) {
|
|
1877
|
-
return;
|
|
1878
1551
|
}
|
|
1879
|
-
await startPythonService();
|
|
1880
|
-
await waitForService();
|
|
1881
|
-
logger.info("Cortex Memory Python service started successfully");
|
|
1882
1552
|
}
|
|
1883
1553
|
//# sourceMappingURL=index.js.map
|