openclaw-cortex-memory 0.1.0-Alpha.2 → 0.1.0-Alpha.20
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 +115 -90
- package/SKILL.md +96 -32
- package/dist/index.d.ts +52 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +677 -1195
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +157 -5
- 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 +225 -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 +95 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +918 -38
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +11 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +53 -0
- package/dist/src/graph/ontology.d.ts.map +1 -0
- package/dist/src/graph/ontology.js +252 -0
- package/dist/src/graph/ontology.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 +75 -1
- package/dist/src/reflect/reflector.js.map +1 -1
- package/dist/src/session/session_end.d.ts +56 -0
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +270 -55
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +115 -0
- package/dist/src/store/archive_store.d.ts.map +1 -0
- package/dist/src/store/archive_store.js +446 -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/read_store.d.ts +59 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1114 -17
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +43 -0
- package/dist/src/store/vector_store.d.ts.map +1 -0
- package/dist/src/store/vector_store.js +200 -0
- package/dist/src/store/vector_store.js.map +1 -0
- package/dist/src/store/write_store.d.ts +45 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +230 -0
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +52 -2
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +474 -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 +157 -5
- package/package.json +21 -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,20 @@ 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
49
|
const session_sync_1 = require("./src/sync/session_sync");
|
|
50
50
|
const session_end_1 = require("./src/session/session_end");
|
|
51
51
|
const rule_store_1 = require("./src/rules/rule_store");
|
|
52
52
|
const reflector_1 = require("./src/reflect/reflector");
|
|
53
|
+
const three_stage_deduplicator_1 = require("./src/dedup/three_stage_deduplicator");
|
|
54
|
+
const runtime_env_1 = require("./src/utils/runtime_env");
|
|
53
55
|
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
56
|
NOT_FOUND: {
|
|
65
57
|
code: "E003",
|
|
66
58
|
message: "Memory not found",
|
|
@@ -71,11 +63,6 @@ const ERROR_CODES = {
|
|
|
71
63
|
message: "Invalid input provided",
|
|
72
64
|
suggestion: "Please check your input parameters and try again."
|
|
73
65
|
},
|
|
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
66
|
PLUGIN_DISABLED: {
|
|
80
67
|
code: "E006",
|
|
81
68
|
message: "Cortex Memory plugin is disabled",
|
|
@@ -88,23 +75,79 @@ const MIN_OPENCLAW_VERSION = "2026.3.8";
|
|
|
88
75
|
const MAX_OPENCLAW_VERSION = "2027.0.0";
|
|
89
76
|
const defaultConfig = {
|
|
90
77
|
autoSync: true,
|
|
78
|
+
llmRequiredForWrite: true,
|
|
91
79
|
autoReflect: false,
|
|
80
|
+
autoReflectIntervalMinutes: 30,
|
|
81
|
+
readFusion: {
|
|
82
|
+
enabled: true,
|
|
83
|
+
maxCandidates: 10,
|
|
84
|
+
authoritative: true,
|
|
85
|
+
channelWeights: {
|
|
86
|
+
rules: 1,
|
|
87
|
+
archive: 1.15,
|
|
88
|
+
vector: 1.2,
|
|
89
|
+
graph: 1,
|
|
90
|
+
},
|
|
91
|
+
channelTopK: {
|
|
92
|
+
rules: 8,
|
|
93
|
+
archive: 20,
|
|
94
|
+
vector: 20,
|
|
95
|
+
graph: 12,
|
|
96
|
+
},
|
|
97
|
+
minLexicalHits: 1,
|
|
98
|
+
minSemanticHits: 1,
|
|
99
|
+
lengthNorm: {
|
|
100
|
+
enabled: true,
|
|
101
|
+
pivotChars: 1200,
|
|
102
|
+
strength: 0.75,
|
|
103
|
+
minFactor: 0.45,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
vectorChunking: {
|
|
107
|
+
chunkSize: 600,
|
|
108
|
+
chunkOverlap: 100,
|
|
109
|
+
},
|
|
110
|
+
memoryDecay: {
|
|
111
|
+
enabled: true,
|
|
112
|
+
minFloor: 0.15,
|
|
113
|
+
defaultHalfLifeDays: 90,
|
|
114
|
+
antiDecay: {
|
|
115
|
+
enabled: true,
|
|
116
|
+
maxBoost: 1.6,
|
|
117
|
+
hitWeight: 0.08,
|
|
118
|
+
recentWindowDays: 30,
|
|
119
|
+
},
|
|
120
|
+
halfLifeByEventType: {
|
|
121
|
+
issue: 30,
|
|
122
|
+
fix: 30,
|
|
123
|
+
action_item: 30,
|
|
124
|
+
blocker: 30,
|
|
125
|
+
plan: 60,
|
|
126
|
+
milestone: 60,
|
|
127
|
+
follow_up: 60,
|
|
128
|
+
decision: 120,
|
|
129
|
+
insight: 120,
|
|
130
|
+
retrospective: 120,
|
|
131
|
+
preference: 240,
|
|
132
|
+
constraint: 240,
|
|
133
|
+
requirement: 240,
|
|
134
|
+
dependency: 240,
|
|
135
|
+
assumption: 240,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
92
138
|
enabled: true,
|
|
93
|
-
fallbackToBuiltin: true,
|
|
94
|
-
engineMode: "ts",
|
|
95
139
|
};
|
|
96
140
|
let autoSearchCacheBySession = new Map();
|
|
97
141
|
const AUTO_SEARCH_CACHE_TTL = 60000;
|
|
98
142
|
const MAX_AUTO_SEARCH_CACHE_SESSIONS = 200;
|
|
143
|
+
const HOOK_GUARD_TIMEOUT_MS = 2000;
|
|
99
144
|
let config = null;
|
|
100
145
|
let logger;
|
|
101
|
-
let pythonProcess = null;
|
|
102
146
|
let isShuttingDown = false;
|
|
103
147
|
let isInitializing = false;
|
|
104
148
|
let isRegistered = false;
|
|
105
149
|
let isEnabled = false;
|
|
106
150
|
let api = null;
|
|
107
|
-
let builtinMemory = null;
|
|
108
151
|
let registeredTools = [];
|
|
109
152
|
let registeredHooks = [];
|
|
110
153
|
let registeredFallbackTools = [];
|
|
@@ -114,13 +157,9 @@ let autoReflectInterval = null;
|
|
|
114
157
|
let lastAutoReflectArchiveMarker = "";
|
|
115
158
|
let lastAutoReflectRunAt = 0;
|
|
116
159
|
let configPath = null;
|
|
117
|
-
let pythonStartPromise = null;
|
|
118
160
|
let processHandlersRegistered = false;
|
|
119
|
-
let pythonPidFilePath = null;
|
|
120
161
|
let memoryEngine = null;
|
|
121
|
-
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
162
|
+
let builtinMemory = null;
|
|
124
163
|
function getMemoryRoot() {
|
|
125
164
|
const projectRoot = findProjectRoot();
|
|
126
165
|
return config?.dbPath ? path.resolve(config.dbPath) : path.join(projectRoot, "data", "memory");
|
|
@@ -150,12 +189,38 @@ function getSessionCachedAutoSearch(sessionId) {
|
|
|
150
189
|
ageSeconds: Math.floor((Date.now() - cache.timestamp) / 1000),
|
|
151
190
|
};
|
|
152
191
|
}
|
|
192
|
+
function isInternalSession(sessionId) {
|
|
193
|
+
if (!sessionId)
|
|
194
|
+
return false;
|
|
195
|
+
return sessionId.startsWith("slug-generator-") || sessionId.startsWith("fallback:");
|
|
196
|
+
}
|
|
197
|
+
async function runWithTimeout(task, timeoutMs, label) {
|
|
198
|
+
let timeoutHandle = null;
|
|
199
|
+
try {
|
|
200
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
201
|
+
timeoutHandle = setTimeout(() => resolve(null), timeoutMs);
|
|
202
|
+
});
|
|
203
|
+
const result = await Promise.race([task, timeoutPromise]);
|
|
204
|
+
if (result === null) {
|
|
205
|
+
logger.warn(`${label} timed out after ${timeoutMs}ms; skipped to protect gateway responsiveness`);
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
logger.warn(`${label} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
if (timeoutHandle) {
|
|
215
|
+
clearTimeout(timeoutHandle);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
153
219
|
function resolveEngine() {
|
|
154
220
|
if (!config) {
|
|
155
221
|
throw new Error("Configuration not loaded");
|
|
156
222
|
}
|
|
157
|
-
|
|
158
|
-
if (memoryEngine && memoryEngine.mode === "ts") {
|
|
223
|
+
if (memoryEngine) {
|
|
159
224
|
return memoryEngine;
|
|
160
225
|
}
|
|
161
226
|
const projectRoot = findProjectRoot();
|
|
@@ -164,16 +229,44 @@ function resolveEngine() {
|
|
|
164
229
|
projectRoot,
|
|
165
230
|
dbPath: config.dbPath,
|
|
166
231
|
logger,
|
|
232
|
+
embedding: config.embedding,
|
|
233
|
+
reranker: config.reranker,
|
|
234
|
+
llm: config.llm,
|
|
235
|
+
fusion: config.readFusion,
|
|
236
|
+
memoryDecay: config.memoryDecay,
|
|
237
|
+
});
|
|
238
|
+
const vectorStore = (0, vector_store_1.createVectorStore)({
|
|
239
|
+
memoryRoot,
|
|
240
|
+
logger,
|
|
167
241
|
});
|
|
168
242
|
const writeStore = (0, write_store_1.createWriteStore)({
|
|
169
243
|
projectRoot,
|
|
170
244
|
dbPath: config.dbPath,
|
|
171
245
|
logger,
|
|
246
|
+
embedding: config.embedding,
|
|
247
|
+
vectorChunking: config.vectorChunking,
|
|
248
|
+
vectorStore,
|
|
249
|
+
});
|
|
250
|
+
const deduplicator = (0, three_stage_deduplicator_1.createThreeStageDeduplicator)({
|
|
251
|
+
memoryRoot,
|
|
252
|
+
logger,
|
|
253
|
+
});
|
|
254
|
+
const archiveStore = (0, archive_store_1.createArchiveStore)({
|
|
255
|
+
projectRoot,
|
|
256
|
+
memoryRoot,
|
|
257
|
+
logger,
|
|
258
|
+
embedding: config.embedding,
|
|
259
|
+
vectorChunking: config.vectorChunking,
|
|
260
|
+
deduplicator,
|
|
261
|
+
vectorStore,
|
|
172
262
|
});
|
|
173
263
|
const sessionSync = (0, session_sync_1.createSessionSync)({
|
|
174
264
|
projectRoot,
|
|
175
265
|
dbPath: config.dbPath,
|
|
176
266
|
logger,
|
|
267
|
+
llm: config.llm,
|
|
268
|
+
requireLlmForWrite: config.llmRequiredForWrite ?? true,
|
|
269
|
+
archiveStore,
|
|
177
270
|
writeStore,
|
|
178
271
|
});
|
|
179
272
|
const sessionEnd = (0, session_end_1.createSessionEnd)({
|
|
@@ -181,6 +274,10 @@ function resolveEngine() {
|
|
|
181
274
|
dbPath: config.dbPath,
|
|
182
275
|
logger,
|
|
183
276
|
syncMemory: sessionSync.syncMemory,
|
|
277
|
+
syncDailySummaries: sessionSync.syncDailySummaries,
|
|
278
|
+
archiveStore,
|
|
279
|
+
llm: config.llm,
|
|
280
|
+
requireLlmForWrite: config.llmRequiredForWrite ?? true,
|
|
184
281
|
});
|
|
185
282
|
const ruleStore = (0, rule_store_1.createRuleStore)({
|
|
186
283
|
projectRoot,
|
|
@@ -192,14 +289,22 @@ function resolveEngine() {
|
|
|
192
289
|
dbPath: config.dbPath,
|
|
193
290
|
logger,
|
|
194
291
|
ruleStore,
|
|
292
|
+
llm: config.llm,
|
|
195
293
|
});
|
|
196
294
|
memoryEngine = (0, ts_engine_1.createTsEngine)({
|
|
197
295
|
readStore,
|
|
198
296
|
writeStore,
|
|
297
|
+
vectorStore,
|
|
298
|
+
archiveStore,
|
|
199
299
|
sessionSync,
|
|
200
300
|
sessionEnd,
|
|
201
301
|
reflector,
|
|
202
302
|
memoryRoot,
|
|
303
|
+
projectRoot,
|
|
304
|
+
embedding: config.embedding,
|
|
305
|
+
llm: config.llm,
|
|
306
|
+
reranker: config.reranker,
|
|
307
|
+
vectorChunking: config.vectorChunking,
|
|
203
308
|
getCachedAutoSearch: getSessionCachedAutoSearch,
|
|
204
309
|
resolveSessionId: (context, payload) => resolveSessionId(context, payload),
|
|
205
310
|
normalizeIncomingMessage,
|
|
@@ -242,1133 +347,305 @@ function createConsoleLogger() {
|
|
|
242
347
|
};
|
|
243
348
|
}
|
|
244
349
|
function sanitizeForLogging(obj) {
|
|
245
|
-
const
|
|
350
|
+
const result = {};
|
|
246
351
|
for (const [key, value] of Object.entries(obj)) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
sanitized[key] = "***REDACTED***";
|
|
352
|
+
if (SENSITIVE_KEYS.some(k => key.toUpperCase().includes(k))) {
|
|
353
|
+
result[key] = "***REDACTED***";
|
|
250
354
|
}
|
|
251
|
-
else if (typeof value === "object" && value !== null) {
|
|
252
|
-
|
|
355
|
+
else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
356
|
+
result[key] = sanitizeForLogging(value);
|
|
253
357
|
}
|
|
254
358
|
else {
|
|
255
|
-
|
|
359
|
+
result[key] = value;
|
|
256
360
|
}
|
|
257
361
|
}
|
|
258
|
-
return
|
|
362
|
+
return result;
|
|
259
363
|
}
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
|
|
364
|
+
function findProjectRoot() {
|
|
365
|
+
let dir = __dirname;
|
|
366
|
+
while (dir !== path.dirname(dir)) {
|
|
367
|
+
const pkgPath = path.join(dir, "package.json");
|
|
368
|
+
if (fs.existsSync(pkgPath)) {
|
|
369
|
+
return dir;
|
|
370
|
+
}
|
|
371
|
+
dir = path.dirname(dir);
|
|
263
372
|
}
|
|
264
|
-
return
|
|
373
|
+
return process.cwd();
|
|
265
374
|
}
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
375
|
+
function resolveSessionId(context, payload) {
|
|
376
|
+
const contextObj = (context || {});
|
|
377
|
+
const payloadObj = (payload || {});
|
|
378
|
+
const candidates = [
|
|
379
|
+
contextObj.sessionId,
|
|
380
|
+
contextObj.session_id,
|
|
381
|
+
payloadObj.sessionId,
|
|
382
|
+
payloadObj.session_id,
|
|
383
|
+
payloadObj.id,
|
|
384
|
+
];
|
|
385
|
+
for (const c of candidates) {
|
|
386
|
+
if (typeof c === "string" && c.trim()) {
|
|
387
|
+
return c.trim();
|
|
270
388
|
}
|
|
271
389
|
}
|
|
272
|
-
return
|
|
390
|
+
return `fallback:${Date.now().toString(36)}`;
|
|
273
391
|
}
|
|
274
392
|
function normalizeIncomingMessage(payload) {
|
|
275
|
-
const
|
|
276
|
-
|
|
393
|
+
const p = payload;
|
|
394
|
+
const text = typeof p.text === "string" ? p.text : (typeof p.content === "string" ? p.content : "");
|
|
395
|
+
if (!text)
|
|
277
396
|
return null;
|
|
278
|
-
|
|
279
|
-
const
|
|
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) {
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
397
|
+
const role = typeof p.role === "string" ? p.role : "unknown";
|
|
398
|
+
const source = typeof p.source === "string" ? p.source : "unknown";
|
|
330
399
|
return { text, role, source };
|
|
331
400
|
}
|
|
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
|
-
}
|
|
401
|
+
function asRecord(value) {
|
|
402
|
+
if (typeof value === "object" && value !== null) {
|
|
403
|
+
return value;
|
|
427
404
|
}
|
|
428
405
|
return null;
|
|
429
406
|
}
|
|
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;
|
|
407
|
+
function firstString(values) {
|
|
408
|
+
for (const v of values) {
|
|
409
|
+
if (typeof v === "string" && v.trim()) {
|
|
410
|
+
return v.trim();
|
|
497
411
|
}
|
|
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
412
|
}
|
|
413
|
+
return undefined;
|
|
517
414
|
}
|
|
518
415
|
function validateConfig(cfg) {
|
|
519
416
|
const errors = [];
|
|
520
|
-
if (!cfg.embedding?.provider
|
|
521
|
-
errors.push("embedding.provider
|
|
522
|
-
}
|
|
523
|
-
if (!cfg.embedding?.apiKey || !cfg.embedding?.baseURL) {
|
|
524
|
-
errors.push("embedding.apiKey and embedding.baseURL are required. Please configure third-party embedding endpoint credentials.");
|
|
525
|
-
}
|
|
526
|
-
if (!cfg.llm?.provider || !cfg.llm?.model) {
|
|
527
|
-
errors.push("llm.provider and llm.model are required. Please configure them in openclaw.json");
|
|
528
|
-
}
|
|
529
|
-
if (!cfg.llm?.apiKey || !cfg.llm?.baseURL) {
|
|
530
|
-
errors.push("llm.apiKey and llm.baseURL are required. Please configure third-party LLM endpoint credentials.");
|
|
531
|
-
}
|
|
532
|
-
if (!cfg.reranker?.model) {
|
|
533
|
-
errors.push("reranker.model is required. Please configure it in openclaw.json");
|
|
417
|
+
if (!cfg.embedding?.provider) {
|
|
418
|
+
errors.push("embedding.provider is required.");
|
|
534
419
|
}
|
|
535
|
-
if (!cfg.
|
|
536
|
-
errors.push("
|
|
420
|
+
if (!cfg.embedding?.model) {
|
|
421
|
+
errors.push("embedding.model is required.");
|
|
537
422
|
}
|
|
538
|
-
|
|
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");
|
|
423
|
+
if (!cfg.llm?.provider) {
|
|
424
|
+
errors.push("llm.provider is required.");
|
|
572
425
|
}
|
|
573
|
-
|
|
574
|
-
|
|
426
|
+
if (!cfg.llm?.model) {
|
|
427
|
+
errors.push("llm.model is required.");
|
|
575
428
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
return;
|
|
580
|
-
try {
|
|
581
|
-
if (fs.existsSync(pythonPidFilePath)) {
|
|
582
|
-
fs.unlinkSync(pythonPidFilePath);
|
|
429
|
+
if (cfg.autoReflectIntervalMinutes !== undefined) {
|
|
430
|
+
if (!Number.isFinite(cfg.autoReflectIntervalMinutes) || cfg.autoReflectIntervalMinutes < 5) {
|
|
431
|
+
errors.push("autoReflectIntervalMinutes must be >= 5.");
|
|
583
432
|
}
|
|
584
433
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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");
|
|
611
|
-
}
|
|
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");
|
|
434
|
+
if (cfg.readFusion?.channelWeights) {
|
|
435
|
+
const weights = cfg.readFusion.channelWeights;
|
|
436
|
+
for (const [key, value] of Object.entries(weights)) {
|
|
437
|
+
if (typeof value === "number" && (!Number.isFinite(value) || value < 0)) {
|
|
438
|
+
errors.push(`readFusion.channelWeights.${key} must be >= 0.`);
|
|
624
439
|
}
|
|
625
|
-
|
|
626
|
-
}, 2000);
|
|
627
|
-
}
|
|
628
|
-
catch (e) {
|
|
629
|
-
logger.warn(`Failed to kill process ${pid}: ${e instanceof Error ? e.message : String(e)}`);
|
|
440
|
+
}
|
|
630
441
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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);
|
|
442
|
+
if (cfg.readFusion?.channelTopK) {
|
|
443
|
+
const topK = cfg.readFusion.channelTopK;
|
|
444
|
+
for (const [key, value] of Object.entries(topK)) {
|
|
445
|
+
if (typeof value === "number" && (!Number.isFinite(value) || value < 1)) {
|
|
446
|
+
errors.push(`readFusion.channelTopK.${key} must be >= 1.`);
|
|
646
447
|
}
|
|
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
448
|
}
|
|
657
449
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
async function checkPortInUse() {
|
|
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;
|
|
450
|
+
if (cfg.vectorChunking?.chunkSize !== undefined) {
|
|
451
|
+
if (!Number.isFinite(cfg.vectorChunking.chunkSize) || cfg.vectorChunking.chunkSize < 100) {
|
|
452
|
+
errors.push("vectorChunking.chunkSize must be >= 100.");
|
|
453
|
+
}
|
|
749
454
|
}
|
|
750
|
-
if (
|
|
751
|
-
|
|
455
|
+
if (cfg.vectorChunking?.chunkOverlap !== undefined) {
|
|
456
|
+
if (!Number.isFinite(cfg.vectorChunking.chunkOverlap) || cfg.vectorChunking.chunkOverlap < 0) {
|
|
457
|
+
errors.push("vectorChunking.chunkOverlap must be >= 0.");
|
|
458
|
+
}
|
|
752
459
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
detached: false,
|
|
757
|
-
windowsHide: true,
|
|
758
|
-
env: { ...env, PYTHONWARNINGS: "ignore::RuntimeWarning" },
|
|
759
|
-
});
|
|
760
|
-
if (pythonProcess.pid) {
|
|
761
|
-
writePythonPid(pythonProcess.pid);
|
|
460
|
+
if (cfg.memoryDecay) {
|
|
461
|
+
if (typeof cfg.memoryDecay.minFloor === "number" && (!Number.isFinite(cfg.memoryDecay.minFloor) || cfg.memoryDecay.minFloor < 0 || cfg.memoryDecay.minFloor > 1)) {
|
|
462
|
+
errors.push("memoryDecay.minFloor must be between 0 and 1.");
|
|
762
463
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (
|
|
769
|
-
|
|
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()}`);
|
|
792
|
-
}
|
|
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();
|
|
464
|
+
if (typeof cfg.memoryDecay.defaultHalfLifeDays === "number" && (!Number.isFinite(cfg.memoryDecay.defaultHalfLifeDays) || cfg.memoryDecay.defaultHalfLifeDays <= 0)) {
|
|
465
|
+
errors.push("memoryDecay.defaultHalfLifeDays must be > 0.");
|
|
466
|
+
}
|
|
467
|
+
if (cfg.memoryDecay.antiDecay) {
|
|
468
|
+
const anti = cfg.memoryDecay.antiDecay;
|
|
469
|
+
if (typeof anti.maxBoost === "number" && (!Number.isFinite(anti.maxBoost) || anti.maxBoost < 1)) {
|
|
470
|
+
errors.push("memoryDecay.antiDecay.maxBoost must be >= 1.");
|
|
807
471
|
}
|
|
808
|
-
|
|
809
|
-
|
|
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)}`));
|
|
472
|
+
if (typeof anti.hitWeight === "number" && (!Number.isFinite(anti.hitWeight) || anti.hitWeight < 0)) {
|
|
473
|
+
errors.push("memoryDecay.antiDecay.hitWeight must be >= 0.");
|
|
818
474
|
}
|
|
819
|
-
|
|
820
|
-
|
|
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}`));
|
|
475
|
+
if (typeof anti.recentWindowDays === "number" && (!Number.isFinite(anti.recentWindowDays) || anti.recentWindowDays <= 0)) {
|
|
476
|
+
errors.push("memoryDecay.antiDecay.recentWindowDays must be > 0.");
|
|
825
477
|
}
|
|
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
478
|
}
|
|
865
479
|
}
|
|
866
|
-
|
|
867
|
-
killPythonProcess();
|
|
868
|
-
}
|
|
869
|
-
function stopPythonService() {
|
|
870
|
-
stopPythonServiceAsync();
|
|
871
|
-
}
|
|
872
|
-
function getBaseUrl() {
|
|
873
|
-
return config?.apiUrl ?? "http://127.0.0.1:8765";
|
|
480
|
+
return errors;
|
|
874
481
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
482
|
+
function checkOpenClawVersion() {
|
|
483
|
+
return new Promise((resolve) => {
|
|
878
484
|
try {
|
|
879
|
-
const
|
|
880
|
-
if (
|
|
485
|
+
const version = api.openclawVersion || api.version;
|
|
486
|
+
if (!version) {
|
|
487
|
+
logger.warn("Could not determine OpenClaw version");
|
|
488
|
+
resolve();
|
|
881
489
|
return;
|
|
882
|
-
}
|
|
883
|
-
catch { }
|
|
884
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
885
|
-
}
|
|
886
|
-
throw new Error("Service failed to become ready");
|
|
887
|
-
}
|
|
888
|
-
function formatApiError(error) {
|
|
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;
|
|
924
|
-
}
|
|
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
|
-
}
|
|
934
|
-
return apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout);
|
|
935
|
-
}
|
|
936
|
-
async function apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout) {
|
|
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
490
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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}`);
|
|
491
|
+
const parseVersion = (v) => {
|
|
492
|
+
const parts = v.replace(/[^0-9.]/g, "").split(".").map(Number);
|
|
493
|
+
return parts.length >= 3 ? parts : [...parts, ...Array(3 - parts.length).fill(0)];
|
|
494
|
+
};
|
|
495
|
+
const current = parseVersion(version);
|
|
496
|
+
const min = parseVersion(MIN_OPENCLAW_VERSION);
|
|
497
|
+
const max = parseVersion(MAX_OPENCLAW_VERSION);
|
|
498
|
+
const currentNum = current[0] * 10000 + current[1] * 100 + current[2];
|
|
499
|
+
const minNum = min[0] * 10000 + min[1] * 100 + min[2];
|
|
500
|
+
const maxNum = max[0] * 10000 + max[1] * 100 + max[2];
|
|
501
|
+
if (currentNum < minNum) {
|
|
502
|
+
logger.warn(`OpenClaw version ${version} is below minimum ${MIN_OPENCLAW_VERSION}. Some features may not work.`);
|
|
976
503
|
}
|
|
977
|
-
|
|
978
|
-
|
|
504
|
+
else if (currentNum >= maxNum) {
|
|
505
|
+
logger.warn(`OpenClaw version ${version} may not be fully compatible. Maximum tested version is ${MAX_OPENCLAW_VERSION}.`);
|
|
979
506
|
}
|
|
980
507
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
try {
|
|
984
|
-
return JSON.parse(text);
|
|
985
|
-
}
|
|
986
|
-
catch {
|
|
987
|
-
throw new Error("Invalid JSON response");
|
|
508
|
+
catch (e) {
|
|
509
|
+
logger.warn(`Version check failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
988
510
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
throw new Error(formatApiError(error));
|
|
992
|
-
}
|
|
993
|
-
finally {
|
|
994
|
-
clearTimeout(timeoutId);
|
|
995
|
-
}
|
|
511
|
+
resolve();
|
|
512
|
+
});
|
|
996
513
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
return
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
return { success: true, data: results };
|
|
1008
|
-
}
|
|
1009
|
-
catch (error) {
|
|
1010
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1011
|
-
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1012
|
-
}
|
|
514
|
+
function findOpenClawConfig() {
|
|
515
|
+
const explicitPath = (0, runtime_env_1.getEnvValue)("OPENCLAW_CONFIG_PATH");
|
|
516
|
+
if (explicitPath && fs.existsSync(explicitPath)) {
|
|
517
|
+
return explicitPath;
|
|
518
|
+
}
|
|
519
|
+
const stateDir = (0, runtime_env_1.getEnvValue)("OPENCLAW_STATE_DIR");
|
|
520
|
+
if (stateDir) {
|
|
521
|
+
const stateConfig = path.join(stateDir, "openclaw.json");
|
|
522
|
+
if (fs.existsSync(stateConfig)) {
|
|
523
|
+
return stateConfig;
|
|
1013
524
|
}
|
|
1014
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1015
|
-
}
|
|
1016
|
-
try {
|
|
1017
|
-
const result = await apiCallWithRetry("/search", "POST", {
|
|
1018
|
-
query: args.query,
|
|
1019
|
-
top_k: args.top_k || 3,
|
|
1020
|
-
});
|
|
1021
|
-
return { success: true, data: result.results };
|
|
1022
|
-
}
|
|
1023
|
-
catch (error) {
|
|
1024
|
-
const message = formatApiError(error);
|
|
1025
|
-
logger.error(`search_memory failed: ${message}`);
|
|
1026
|
-
return { success: false, error: message };
|
|
1027
525
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
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 } };
|
|
1040
|
-
}
|
|
1041
|
-
catch (error) {
|
|
1042
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1043
|
-
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1044
|
-
}
|
|
526
|
+
const basePath = (0, runtime_env_1.getEnvValue)("OPENCLAW_BASE_PATH");
|
|
527
|
+
if (basePath) {
|
|
528
|
+
const baseConfig = path.join(basePath, "openclaw.json");
|
|
529
|
+
if (fs.existsSync(baseConfig)) {
|
|
530
|
+
return baseConfig;
|
|
1045
531
|
}
|
|
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
532
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
async function getAutoContext(args, context) {
|
|
1097
|
-
if (!isEnabled) {
|
|
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) {
|
|
1113
|
-
try {
|
|
1114
|
-
const hotResult = await apiCallWithRetry("/hot-context", "GET");
|
|
1115
|
-
result.hot_context = hotResult.context;
|
|
1116
|
-
}
|
|
1117
|
-
catch (error) {
|
|
1118
|
-
logger.debug(`Failed to get hot context: ${formatApiError(error)}`);
|
|
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."
|
|
1127
|
-
}
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
return { success: true, data: result };
|
|
1131
|
-
}
|
|
1132
|
-
async function reflectMemory(_args, _context) {
|
|
1133
|
-
if (!isEnabled) {
|
|
1134
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1135
|
-
}
|
|
1136
|
-
try {
|
|
1137
|
-
await apiCallWithRetry("/reflect", "POST", undefined, {
|
|
1138
|
-
timeout: 120000,
|
|
1139
|
-
maxRetries: 2,
|
|
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 };
|
|
1189
|
-
}
|
|
1190
|
-
catch (error) {
|
|
1191
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1192
|
-
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1193
|
-
}
|
|
533
|
+
const home = (0, runtime_env_1.getHomeDir)();
|
|
534
|
+
const candidates = [
|
|
535
|
+
path.join(home, ".openclaw", "openclaw.json"),
|
|
536
|
+
path.join(home, ".openclaw", "config.json"),
|
|
537
|
+
];
|
|
538
|
+
for (const c of candidates) {
|
|
539
|
+
if (fs.existsSync(c)) {
|
|
540
|
+
return c;
|
|
1194
541
|
}
|
|
1195
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1196
|
-
}
|
|
1197
|
-
try {
|
|
1198
|
-
await apiCallWithRetry(`/memory/${args.memory_id}`, "DELETE");
|
|
1199
|
-
return { success: true };
|
|
1200
|
-
}
|
|
1201
|
-
catch (error) {
|
|
1202
|
-
const message = formatApiError(error);
|
|
1203
|
-
logger.error(`delete_memory failed: ${message}`);
|
|
1204
|
-
return { success: false, error: message };
|
|
1205
542
|
}
|
|
543
|
+
return null;
|
|
1206
544
|
}
|
|
1207
|
-
|
|
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
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
async function runDiagnostics(_args, _context) {
|
|
1243
|
-
if (!isEnabled) {
|
|
1244
|
-
return {
|
|
1245
|
-
success: true,
|
|
1246
|
-
data: {
|
|
1247
|
-
status: "disabled",
|
|
1248
|
-
message: "Cortex Memory plugin is disabled",
|
|
1249
|
-
suggestion: "Enable the plugin using 'openclaw plugins enable cortex-memory'"
|
|
1250
|
-
}
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
try {
|
|
1254
|
-
const result = await apiCallWithRetry("/doctor", "GET");
|
|
1255
|
-
return { success: true, data: result };
|
|
1256
|
-
}
|
|
1257
|
-
catch (error) {
|
|
1258
|
-
const message = formatApiError(error);
|
|
1259
|
-
logger.error(`diagnostics failed: ${message}`);
|
|
1260
|
-
return { success: false, error: message };
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
async function getPluginStatus(_args, _context) {
|
|
1264
|
-
return {
|
|
1265
|
-
success: true,
|
|
1266
|
-
data: {
|
|
1267
|
-
enabled: isEnabled,
|
|
1268
|
-
service_running: pythonProcess !== null,
|
|
1269
|
-
fallback_enabled: config?.fallbackToBuiltin ?? true,
|
|
1270
|
-
builtin_memory_available: builtinMemory !== null,
|
|
1271
|
-
engine_mode: config?.engineMode ?? "python",
|
|
1272
|
-
}
|
|
1273
|
-
};
|
|
1274
|
-
}
|
|
1275
|
-
async function onMessagePythonHandler(payload, context) {
|
|
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);
|
|
545
|
+
function loadPluginEnabledState() {
|
|
1283
546
|
try {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
logger.info(`Stored ${role} message for session ${sessionId}`);
|
|
547
|
+
if (!configPath)
|
|
548
|
+
return true;
|
|
549
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
550
|
+
const cfg = JSON.parse(raw);
|
|
551
|
+
const entry = cfg?.plugins?.entries?.[PLUGIN_ID];
|
|
552
|
+
if (entry && typeof entry.enabled === "boolean") {
|
|
553
|
+
return entry.enabled;
|
|
1292
554
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
555
|
+
const allow = cfg?.plugins?.allow;
|
|
556
|
+
if (Array.isArray(allow)) {
|
|
557
|
+
return allow.includes(PLUGIN_ID);
|
|
1295
558
|
}
|
|
559
|
+
return true;
|
|
1296
560
|
}
|
|
1297
|
-
catch
|
|
1298
|
-
|
|
561
|
+
catch {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function startConfigWatcher() {
|
|
566
|
+
if (configWatchInterval) {
|
|
567
|
+
clearInterval(configWatchInterval);
|
|
1299
568
|
}
|
|
1300
|
-
|
|
569
|
+
configWatchInterval = setInterval(() => {
|
|
1301
570
|
try {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
571
|
+
if (!configPath || !fs.existsSync(configPath))
|
|
572
|
+
return;
|
|
573
|
+
const currentEnabled = loadPluginEnabledState();
|
|
574
|
+
if (currentEnabled !== isEnabled) {
|
|
575
|
+
logger.info(`Plugin enabled state changed from ${isEnabled} to ${currentEnabled}`);
|
|
576
|
+
if (currentEnabled) {
|
|
577
|
+
enable().catch(e => logger.error(`Failed to enable: ${e}`));
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
disable().catch(e => logger.error(`Failed to disable: ${e}`));
|
|
581
|
+
}
|
|
1313
582
|
}
|
|
1314
583
|
}
|
|
1315
|
-
catch (
|
|
1316
|
-
logger.debug(`
|
|
584
|
+
catch (e) {
|
|
585
|
+
logger.debug(`Config watch error: ${e}`);
|
|
1317
586
|
}
|
|
587
|
+
}, 5000);
|
|
588
|
+
}
|
|
589
|
+
function stopConfigWatcher() {
|
|
590
|
+
if (configWatchInterval) {
|
|
591
|
+
clearInterval(configWatchInterval);
|
|
592
|
+
configWatchInterval = null;
|
|
1318
593
|
}
|
|
1319
594
|
}
|
|
1320
|
-
|
|
1321
|
-
if (!
|
|
595
|
+
function startAutoReflectScheduler() {
|
|
596
|
+
if (!config?.autoReflect) {
|
|
1322
597
|
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
598
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
599
|
+
const intervalMinutes = Math.max(5, config.autoReflectIntervalMinutes ?? 30);
|
|
600
|
+
if (autoReflectInterval) {
|
|
601
|
+
clearInterval(autoReflectInterval);
|
|
1333
602
|
}
|
|
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");
|
|
603
|
+
lastAutoReflectArchiveMarker = getArchiveMarker();
|
|
604
|
+
lastAutoReflectRunAt = Date.now();
|
|
605
|
+
autoReflectInterval = setInterval(async () => {
|
|
606
|
+
if (!isEnabled)
|
|
607
|
+
return;
|
|
608
|
+
const currentMarker = getArchiveMarker();
|
|
609
|
+
if (currentMarker !== lastAutoReflectArchiveMarker) {
|
|
610
|
+
lastAutoReflectArchiveMarker = currentMarker;
|
|
611
|
+
try {
|
|
612
|
+
const result = await resolveEngine().reflectMemory({}, { agentId: "scheduler", workspaceId: "default" });
|
|
613
|
+
if (result.success) {
|
|
614
|
+
logger.info("Auto-reflect completed successfully");
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
logger.warn(`Auto-reflect failed: ${result.error}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
catch (e) {
|
|
621
|
+
logger.error(`Auto-reflect error: ${e instanceof Error ? e.message : String(e)}`);
|
|
622
|
+
}
|
|
1361
623
|
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
|
|
624
|
+
}, intervalMinutes * 60 * 1000);
|
|
625
|
+
logger.info(`Auto-reflect scheduler started (interval: ${intervalMinutes} minutes)`);
|
|
626
|
+
}
|
|
627
|
+
function stopAutoReflectScheduler() {
|
|
628
|
+
if (autoReflectInterval) {
|
|
629
|
+
clearInterval(autoReflectInterval);
|
|
630
|
+
autoReflectInterval = null;
|
|
1365
631
|
}
|
|
1366
632
|
}
|
|
633
|
+
function logLifecycle(event, data) {
|
|
634
|
+
logger.info(`[Lifecycle] ${event}${data ? `: ${JSON.stringify(sanitizeForLogging(data))}` : ""}`);
|
|
635
|
+
}
|
|
1367
636
|
async function onMessageHandler(payload, context) {
|
|
1368
|
-
|
|
637
|
+
const sessionId = resolveSessionId(context, payload);
|
|
638
|
+
if (isInternalSession(sessionId)) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
await runWithTimeout(resolveEngine().onMessage(payload, context), HOOK_GUARD_TIMEOUT_MS, "onMessage hook");
|
|
1369
642
|
}
|
|
1370
643
|
async function onSessionEndHandler(payload, context) {
|
|
1371
|
-
|
|
644
|
+
const sessionId = resolveSessionId(context, payload);
|
|
645
|
+
if (isInternalSession(sessionId)) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
await runWithTimeout(resolveEngine().onSessionEnd(payload, context), HOOK_GUARD_TIMEOUT_MS, "onSessionEnd hook");
|
|
1372
649
|
}
|
|
1373
650
|
async function onTimerHandler(payload, context) {
|
|
1374
651
|
await resolveEngine().onTimer(payload, context);
|
|
@@ -1390,7 +667,10 @@ function registerTools() {
|
|
|
1390
667
|
additionalProperties: false,
|
|
1391
668
|
},
|
|
1392
669
|
execute: async (params) => {
|
|
670
|
+
logger.info(`search_memory execute called with params: ${JSON.stringify(params)}`);
|
|
671
|
+
logger.info(`params.args: ${JSON.stringify(params.args)}`);
|
|
1393
672
|
const args = params.args || params;
|
|
673
|
+
logger.info(`args after extraction: ${JSON.stringify(args)}`);
|
|
1394
674
|
return resolveEngine().searchMemory(args, params.context);
|
|
1395
675
|
},
|
|
1396
676
|
},
|
|
@@ -1497,6 +777,26 @@ function registerTools() {
|
|
|
1497
777
|
return resolveEngine().syncMemory(args, params.context);
|
|
1498
778
|
},
|
|
1499
779
|
},
|
|
780
|
+
{
|
|
781
|
+
name: "backfill_embeddings",
|
|
782
|
+
description: "Backfill missing embeddings for active/archive records",
|
|
783
|
+
parameters: {
|
|
784
|
+
type: "object",
|
|
785
|
+
properties: {
|
|
786
|
+
layer: { type: "string", enum: ["active", "archive", "all"], description: "Target layer to backfill" },
|
|
787
|
+
batch_size: { type: "integer", description: "Batch size per processing window" },
|
|
788
|
+
max_retries: { type: "integer", description: "Max retry count for failed records" },
|
|
789
|
+
retry_failed_only: { type: "boolean", description: "Only retry failed records" },
|
|
790
|
+
rebuild_mode: { type: "string", enum: ["incremental", "vector_only", "full"], description: "Rebuild mode" },
|
|
791
|
+
},
|
|
792
|
+
required: [],
|
|
793
|
+
additionalProperties: false,
|
|
794
|
+
},
|
|
795
|
+
execute: async (params) => {
|
|
796
|
+
const args = params.args || params;
|
|
797
|
+
return resolveEngine().backfillEmbeddings(args, params.context);
|
|
798
|
+
},
|
|
799
|
+
},
|
|
1500
800
|
{
|
|
1501
801
|
name: "delete_memory",
|
|
1502
802
|
description: "Delete a memory by ID",
|
|
@@ -1533,16 +833,164 @@ function registerTools() {
|
|
|
1533
833
|
registeredTools.push(tool.name);
|
|
1534
834
|
}
|
|
1535
835
|
}
|
|
836
|
+
function sanitizeToolParametersSchemaValue(schema) {
|
|
837
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
838
|
+
return {};
|
|
839
|
+
}
|
|
840
|
+
const source = schema;
|
|
841
|
+
const target = {};
|
|
842
|
+
if (typeof source.type === "string") {
|
|
843
|
+
target.type = source.type;
|
|
844
|
+
}
|
|
845
|
+
if (typeof source.description === "string" && source.description.trim()) {
|
|
846
|
+
target.description = source.description;
|
|
847
|
+
}
|
|
848
|
+
if (Array.isArray(source.enum)) {
|
|
849
|
+
const values = source.enum.filter(item => typeof item === "string" || typeof item === "number" || typeof item === "boolean" || item === null);
|
|
850
|
+
if (values.length > 0) {
|
|
851
|
+
target.enum = values;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (source.properties && typeof source.properties === "object" && !Array.isArray(source.properties)) {
|
|
855
|
+
const sanitizedProperties = {};
|
|
856
|
+
for (const [key, value] of Object.entries(source.properties)) {
|
|
857
|
+
sanitizedProperties[key] = sanitizeToolParametersSchemaValue(value);
|
|
858
|
+
}
|
|
859
|
+
target.properties = sanitizedProperties;
|
|
860
|
+
}
|
|
861
|
+
if (Array.isArray(source.required)) {
|
|
862
|
+
const required = source.required.filter(item => typeof item === "string");
|
|
863
|
+
if (required.length > 0) {
|
|
864
|
+
target.required = required;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (source.items && typeof source.items === "object" && !Array.isArray(source.items)) {
|
|
868
|
+
target.items = sanitizeToolParametersSchemaValue(source.items);
|
|
869
|
+
}
|
|
870
|
+
if (typeof source.additionalProperties === "boolean") {
|
|
871
|
+
target.additionalProperties = source.additionalProperties;
|
|
872
|
+
}
|
|
873
|
+
else if (source.additionalProperties && typeof source.additionalProperties === "object") {
|
|
874
|
+
target.additionalProperties = true;
|
|
875
|
+
}
|
|
876
|
+
return target;
|
|
877
|
+
}
|
|
878
|
+
function sanitizeToolParametersSchema(schema) {
|
|
879
|
+
const sanitized = sanitizeToolParametersSchemaValue(schema);
|
|
880
|
+
if (sanitized.type !== "object") {
|
|
881
|
+
sanitized.type = "object";
|
|
882
|
+
}
|
|
883
|
+
if (!sanitized.properties || typeof sanitized.properties !== "object" || Array.isArray(sanitized.properties)) {
|
|
884
|
+
sanitized.properties = {};
|
|
885
|
+
}
|
|
886
|
+
if (!Array.isArray(sanitized.required)) {
|
|
887
|
+
sanitized.required = [];
|
|
888
|
+
}
|
|
889
|
+
if (typeof sanitized.additionalProperties !== "boolean") {
|
|
890
|
+
sanitized.additionalProperties = false;
|
|
891
|
+
}
|
|
892
|
+
return sanitized;
|
|
893
|
+
}
|
|
1536
894
|
function registerToolCompat(tool) {
|
|
1537
895
|
if (!api)
|
|
1538
896
|
return;
|
|
1539
|
-
const
|
|
897
|
+
const normalizeContext = (value) => {
|
|
898
|
+
const contextObj = asRecord(value) || {};
|
|
899
|
+
return {
|
|
900
|
+
agentId: firstString([contextObj.agentId, contextObj.agent_id]) || "unknown-agent",
|
|
901
|
+
workspaceId: firstString([contextObj.workspaceId, contextObj.workspace_id]) || "default",
|
|
902
|
+
sessionId: firstString([contextObj.sessionId, contextObj.session_id]) || undefined,
|
|
903
|
+
};
|
|
904
|
+
};
|
|
905
|
+
const normalizeInvocation = (...params) => {
|
|
906
|
+
logger.info(`normalizeInvocation called with params: ${JSON.stringify(params)}`);
|
|
907
|
+
if (params.length === 1) {
|
|
908
|
+
const first = params[0];
|
|
909
|
+
const firstObj = asRecord(first);
|
|
910
|
+
if (firstObj && ("context" in firstObj || "args" in firstObj)) {
|
|
911
|
+
const explicitArgs = asRecord(firstObj.args);
|
|
912
|
+
if (explicitArgs) {
|
|
913
|
+
logger.info(`normalizeInvocation: single param with explicit args: ${JSON.stringify(explicitArgs)}`);
|
|
914
|
+
return {
|
|
915
|
+
args: explicitArgs,
|
|
916
|
+
context: normalizeContext(firstObj.context),
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
const directArgs = { ...firstObj };
|
|
920
|
+
delete directArgs.context;
|
|
921
|
+
delete directArgs.args;
|
|
922
|
+
logger.info(`normalizeInvocation: single param with direct args: ${JSON.stringify(directArgs)}`);
|
|
923
|
+
return {
|
|
924
|
+
args: directArgs,
|
|
925
|
+
context: normalizeContext(firstObj.context),
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
if (firstObj) {
|
|
929
|
+
logger.info(`normalizeInvocation: single param as args: ${JSON.stringify(firstObj)}`);
|
|
930
|
+
return {
|
|
931
|
+
args: firstObj,
|
|
932
|
+
context: normalizeContext(undefined),
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const first = params[0];
|
|
937
|
+
const second = params[1];
|
|
938
|
+
const third = params[2];
|
|
939
|
+
const firstObj = asRecord(first);
|
|
940
|
+
const secondObj = asRecord(second);
|
|
941
|
+
if (typeof first === "string" && secondObj) {
|
|
942
|
+
logger.info(`normalizeInvocation: first is string (tool call ID), second is args: ${JSON.stringify(secondObj)}`);
|
|
943
|
+
return {
|
|
944
|
+
args: secondObj,
|
|
945
|
+
context: normalizeContext(third),
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
if (firstObj && ("context" in firstObj || "args" in firstObj)) {
|
|
949
|
+
const explicitArgs = asRecord(firstObj.args);
|
|
950
|
+
if (explicitArgs) {
|
|
951
|
+
logger.info(`normalizeInvocation: first has explicit args: ${JSON.stringify(explicitArgs)}`);
|
|
952
|
+
return {
|
|
953
|
+
args: explicitArgs,
|
|
954
|
+
context: normalizeContext(firstObj.context),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
const directArgs = { ...firstObj };
|
|
958
|
+
delete directArgs.context;
|
|
959
|
+
delete directArgs.args;
|
|
960
|
+
logger.info(`normalizeInvocation: first has direct args: ${JSON.stringify(directArgs)}`);
|
|
961
|
+
return {
|
|
962
|
+
args: directArgs,
|
|
963
|
+
context: normalizeContext(firstObj.context),
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
if (firstObj && Object.keys(firstObj).length > 0) {
|
|
967
|
+
logger.info(`normalizeInvocation: first is args, second is context: ${JSON.stringify(firstObj)}`);
|
|
968
|
+
return {
|
|
969
|
+
args: firstObj,
|
|
970
|
+
context: normalizeContext(second),
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
logger.info(`normalizeInvocation: fallback to firstObj as args: ${JSON.stringify(firstObj)}`);
|
|
974
|
+
return {
|
|
975
|
+
args: firstObj || {},
|
|
976
|
+
context: normalizeContext(second),
|
|
977
|
+
};
|
|
978
|
+
};
|
|
979
|
+
const invoke = async (...params) => {
|
|
980
|
+
logger.info(`invoke called for tool ${tool.name} with params: ${JSON.stringify(params)}`);
|
|
981
|
+
const normalized = normalizeInvocation(...params);
|
|
982
|
+
logger.info(`invoke: normalized args=${JSON.stringify(normalized.args)}, context=${JSON.stringify(normalized.context)}`);
|
|
983
|
+
return tool.execute({
|
|
984
|
+
args: normalized.args,
|
|
985
|
+
context: normalized.context,
|
|
986
|
+
});
|
|
987
|
+
};
|
|
1540
988
|
api.registerTool({
|
|
1541
989
|
name: tool.name,
|
|
1542
990
|
description: tool.description,
|
|
1543
|
-
parameters: tool.parameters,
|
|
1544
|
-
execute,
|
|
1545
|
-
handler:
|
|
991
|
+
parameters: sanitizeToolParametersSchema(tool.parameters),
|
|
992
|
+
execute: invoke,
|
|
993
|
+
handler: invoke,
|
|
1546
994
|
});
|
|
1547
995
|
}
|
|
1548
996
|
function unregisterTools() {
|
|
@@ -1558,6 +1006,108 @@ function unregisterTools() {
|
|
|
1558
1006
|
}
|
|
1559
1007
|
registeredTools = [];
|
|
1560
1008
|
}
|
|
1009
|
+
function registerFallbackTools() {
|
|
1010
|
+
if (!api || !builtinMemory)
|
|
1011
|
+
return;
|
|
1012
|
+
for (const name of ["search_memory", "store_event", "cortex_memory_status"]) {
|
|
1013
|
+
try {
|
|
1014
|
+
if (api.unregisterTool) {
|
|
1015
|
+
api.unregisterTool(name);
|
|
1016
|
+
logger.info(`Unregistered existing tool ${name} before registering fallback`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
catch (e) {
|
|
1020
|
+
// ignore
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
registerToolCompat({
|
|
1024
|
+
name: "search_memory",
|
|
1025
|
+
description: "Search memory (using builtin system - Cortex Memory disabled)",
|
|
1026
|
+
parameters: {
|
|
1027
|
+
type: "object",
|
|
1028
|
+
properties: {
|
|
1029
|
+
query: { type: "string", description: "Search query" },
|
|
1030
|
+
top_k: { type: "integer", description: "Number of results" },
|
|
1031
|
+
},
|
|
1032
|
+
required: ["query"],
|
|
1033
|
+
additionalProperties: false,
|
|
1034
|
+
},
|
|
1035
|
+
execute: async (params) => {
|
|
1036
|
+
const args = (params.args || params);
|
|
1037
|
+
const query = args.query || "";
|
|
1038
|
+
const topK = args.top_k || 5;
|
|
1039
|
+
try {
|
|
1040
|
+
const results = await builtinMemory.search(query, topK);
|
|
1041
|
+
return { success: true, data: results };
|
|
1042
|
+
}
|
|
1043
|
+
catch (error) {
|
|
1044
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1045
|
+
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
});
|
|
1049
|
+
registeredFallbackTools.push("search_memory");
|
|
1050
|
+
registerToolCompat({
|
|
1051
|
+
name: "store_event",
|
|
1052
|
+
description: "Store event (using builtin system - Cortex Memory disabled)",
|
|
1053
|
+
parameters: {
|
|
1054
|
+
type: "object",
|
|
1055
|
+
properties: {
|
|
1056
|
+
summary: { type: "string", description: "Event summary" },
|
|
1057
|
+
},
|
|
1058
|
+
required: ["summary"],
|
|
1059
|
+
additionalProperties: false,
|
|
1060
|
+
},
|
|
1061
|
+
execute: async (params) => {
|
|
1062
|
+
const args = (params.args || params);
|
|
1063
|
+
const summary = args.summary || "";
|
|
1064
|
+
try {
|
|
1065
|
+
const id = await builtinMemory.store(summary);
|
|
1066
|
+
return { success: true, data: { id } };
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1070
|
+
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
});
|
|
1074
|
+
registeredFallbackTools.push("store_event");
|
|
1075
|
+
registerToolCompat({
|
|
1076
|
+
name: "cortex_memory_status",
|
|
1077
|
+
description: "Get the current status of the Cortex Memory plugin",
|
|
1078
|
+
parameters: {
|
|
1079
|
+
type: "object",
|
|
1080
|
+
properties: {},
|
|
1081
|
+
required: [],
|
|
1082
|
+
additionalProperties: false,
|
|
1083
|
+
},
|
|
1084
|
+
execute: async (_params) => {
|
|
1085
|
+
return {
|
|
1086
|
+
success: true,
|
|
1087
|
+
data: {
|
|
1088
|
+
enabled: isEnabled,
|
|
1089
|
+
fallback_enabled: config?.fallbackToBuiltin ?? true,
|
|
1090
|
+
builtin_memory_available: builtinMemory !== null,
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
},
|
|
1094
|
+
});
|
|
1095
|
+
registeredFallbackTools.push("cortex_memory_status");
|
|
1096
|
+
logger.info(`Registered ${registeredFallbackTools.length} fallback tools`);
|
|
1097
|
+
}
|
|
1098
|
+
function unregisterFallbackTools() {
|
|
1099
|
+
if (!api || !api.unregisterTool)
|
|
1100
|
+
return;
|
|
1101
|
+
for (const name of registeredFallbackTools) {
|
|
1102
|
+
try {
|
|
1103
|
+
api.unregisterTool(name);
|
|
1104
|
+
}
|
|
1105
|
+
catch (e) {
|
|
1106
|
+
logger.warn(`Failed to unregister fallback tool ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
registeredFallbackTools = [];
|
|
1110
|
+
}
|
|
1561
1111
|
function registerHooks() {
|
|
1562
1112
|
if (!api)
|
|
1563
1113
|
return;
|
|
@@ -1618,27 +1168,15 @@ function setupProcessHandlers() {
|
|
|
1618
1168
|
isShuttingDown = true;
|
|
1619
1169
|
logger.info(`Received ${signal}, shutting down...`);
|
|
1620
1170
|
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
|
-
});
|
|
1171
|
+
process.exit(0);
|
|
1632
1172
|
};
|
|
1633
1173
|
process.on("exit", () => {
|
|
1634
|
-
killPythonProcess();
|
|
1635
1174
|
stopConfigWatcher();
|
|
1636
1175
|
});
|
|
1637
1176
|
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
1638
1177
|
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
1639
1178
|
process.on("uncaughtException", (err) => {
|
|
1640
1179
|
logger.error("Uncaught exception:", err.message);
|
|
1641
|
-
killPythonProcess();
|
|
1642
1180
|
stopConfigWatcher();
|
|
1643
1181
|
process.exit(1);
|
|
1644
1182
|
});
|
|
@@ -1649,21 +1187,20 @@ async function enable() {
|
|
|
1649
1187
|
return;
|
|
1650
1188
|
}
|
|
1651
1189
|
logger.info("Enabling Cortex Memory plugin...");
|
|
1190
|
+
logLifecycle("enable_start");
|
|
1652
1191
|
try {
|
|
1653
1192
|
unregisterFallbackTools();
|
|
1654
|
-
if (shouldUsePythonRuntime()) {
|
|
1655
|
-
await startPythonService();
|
|
1656
|
-
await waitForService();
|
|
1657
|
-
}
|
|
1658
1193
|
isEnabled = true;
|
|
1659
1194
|
registerTools();
|
|
1660
1195
|
registerHooks();
|
|
1661
1196
|
startAutoReflectScheduler();
|
|
1662
1197
|
logger.info("Cortex Memory plugin enabled successfully");
|
|
1198
|
+
logLifecycle("enable_success", { registeredTools: registeredTools.length, registeredHooks: registeredHooks.length });
|
|
1663
1199
|
}
|
|
1664
1200
|
catch (error) {
|
|
1665
1201
|
const message = error instanceof Error ? error.message : String(error);
|
|
1666
1202
|
logger.error(`Failed to enable Cortex Memory plugin: ${message}`);
|
|
1203
|
+
logLifecycle("enable_failed", { error: message });
|
|
1667
1204
|
throw error;
|
|
1668
1205
|
}
|
|
1669
1206
|
}
|
|
@@ -1673,113 +1210,48 @@ async function disable() {
|
|
|
1673
1210
|
return;
|
|
1674
1211
|
}
|
|
1675
1212
|
logger.info("Disabling Cortex Memory plugin...");
|
|
1213
|
+
logLifecycle("disable_start");
|
|
1676
1214
|
unregisterHooks();
|
|
1677
1215
|
unregisterTools();
|
|
1678
|
-
unregisterFallbackTools();
|
|
1679
1216
|
stopAutoReflectScheduler();
|
|
1680
|
-
if (shouldUsePythonRuntime()) {
|
|
1681
|
-
await stopPythonServiceAsync();
|
|
1682
|
-
}
|
|
1683
1217
|
isEnabled = false;
|
|
1684
1218
|
memoryEngine = null;
|
|
1685
1219
|
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1686
1220
|
logger.info("Falling back to OpenClaw builtin memory system");
|
|
1687
1221
|
registerFallbackTools();
|
|
1222
|
+
logLifecycle("fallback_enabled", { fallbackTools: registeredFallbackTools.length });
|
|
1688
1223
|
}
|
|
1689
1224
|
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 = [];
|
|
1225
|
+
logLifecycle("disable_success", { fallbackEnabled: registeredFallbackTools.length > 0 });
|
|
1748
1226
|
}
|
|
1749
1227
|
function getStatus() {
|
|
1750
1228
|
return {
|
|
1751
|
-
enabled: isEnabled
|
|
1752
|
-
serviceRunning: pythonProcess !== null
|
|
1229
|
+
enabled: isEnabled
|
|
1753
1230
|
};
|
|
1754
1231
|
}
|
|
1755
1232
|
async function unregister() {
|
|
1756
1233
|
logger.info("Unregistering Cortex Memory plugin...");
|
|
1234
|
+
logLifecycle("unregister_start");
|
|
1757
1235
|
stopConfigWatcher();
|
|
1758
1236
|
stopAutoReflectScheduler();
|
|
1759
1237
|
unregisterHooks();
|
|
1760
1238
|
unregisterTools();
|
|
1761
1239
|
unregisterFallbackTools();
|
|
1762
|
-
if (shouldUsePythonRuntime()) {
|
|
1763
|
-
await stopPythonServiceAsync();
|
|
1764
|
-
}
|
|
1765
|
-
else {
|
|
1766
|
-
killPythonProcess();
|
|
1767
|
-
}
|
|
1768
1240
|
isEnabled = false;
|
|
1769
1241
|
isInitializing = false;
|
|
1770
1242
|
isRegistered = false;
|
|
1771
1243
|
api = null;
|
|
1772
1244
|
config = null;
|
|
1773
1245
|
autoSearchCacheBySession.clear();
|
|
1774
|
-
builtinMemory = null;
|
|
1775
1246
|
memoryEngine = null;
|
|
1247
|
+
builtinMemory = null;
|
|
1776
1248
|
registeredTools = [];
|
|
1777
1249
|
registeredHooks = [];
|
|
1778
1250
|
registeredFallbackTools = [];
|
|
1779
1251
|
registeredHookHandlers.clear();
|
|
1780
|
-
stopAutoReflectScheduler();
|
|
1781
1252
|
configPath = null;
|
|
1782
1253
|
logger.info("Cortex Memory plugin unregistered successfully");
|
|
1254
|
+
logLifecycle("unregister_success");
|
|
1783
1255
|
}
|
|
1784
1256
|
function register(pluginApi, userConfig) {
|
|
1785
1257
|
if (isInitializing || isRegistered) {
|
|
@@ -1815,10 +1287,40 @@ function register(pluginApi, userConfig) {
|
|
|
1815
1287
|
dbPath: effectiveConfig.dbPath,
|
|
1816
1288
|
autoSync: effectiveConfig.autoSync ?? defaultConfig.autoSync,
|
|
1817
1289
|
autoReflect: effectiveConfig.autoReflect ?? defaultConfig.autoReflect,
|
|
1290
|
+
autoReflectIntervalMinutes: effectiveConfig.autoReflectIntervalMinutes ?? defaultConfig.autoReflectIntervalMinutes,
|
|
1291
|
+
readFusion: {
|
|
1292
|
+
enabled: effectiveConfig.readFusion?.enabled ?? defaultConfig.readFusion?.enabled,
|
|
1293
|
+
maxCandidates: effectiveConfig.readFusion?.maxCandidates ?? defaultConfig.readFusion?.maxCandidates,
|
|
1294
|
+
authoritative: effectiveConfig.readFusion?.authoritative ?? defaultConfig.readFusion?.authoritative,
|
|
1295
|
+
channelWeights: effectiveConfig.readFusion?.channelWeights ?? defaultConfig.readFusion?.channelWeights,
|
|
1296
|
+
channelTopK: effectiveConfig.readFusion?.channelTopK ?? defaultConfig.readFusion?.channelTopK,
|
|
1297
|
+
minLexicalHits: effectiveConfig.readFusion?.minLexicalHits ?? defaultConfig.readFusion?.minLexicalHits,
|
|
1298
|
+
minSemanticHits: effectiveConfig.readFusion?.minSemanticHits ?? defaultConfig.readFusion?.minSemanticHits,
|
|
1299
|
+
lengthNorm: {
|
|
1300
|
+
enabled: effectiveConfig.readFusion?.lengthNorm?.enabled ?? defaultConfig.readFusion?.lengthNorm?.enabled,
|
|
1301
|
+
pivotChars: effectiveConfig.readFusion?.lengthNorm?.pivotChars ?? defaultConfig.readFusion?.lengthNorm?.pivotChars,
|
|
1302
|
+
strength: effectiveConfig.readFusion?.lengthNorm?.strength ?? defaultConfig.readFusion?.lengthNorm?.strength,
|
|
1303
|
+
minFactor: effectiveConfig.readFusion?.lengthNorm?.minFactor ?? defaultConfig.readFusion?.lengthNorm?.minFactor,
|
|
1304
|
+
},
|
|
1305
|
+
},
|
|
1306
|
+
vectorChunking: {
|
|
1307
|
+
chunkSize: effectiveConfig.vectorChunking?.chunkSize ?? defaultConfig.vectorChunking?.chunkSize,
|
|
1308
|
+
chunkOverlap: effectiveConfig.vectorChunking?.chunkOverlap ?? defaultConfig.vectorChunking?.chunkOverlap,
|
|
1309
|
+
},
|
|
1310
|
+
memoryDecay: {
|
|
1311
|
+
enabled: effectiveConfig.memoryDecay?.enabled ?? defaultConfig.memoryDecay?.enabled,
|
|
1312
|
+
minFloor: effectiveConfig.memoryDecay?.minFloor ?? defaultConfig.memoryDecay?.minFloor,
|
|
1313
|
+
defaultHalfLifeDays: effectiveConfig.memoryDecay?.defaultHalfLifeDays ?? defaultConfig.memoryDecay?.defaultHalfLifeDays,
|
|
1314
|
+
halfLifeByEventType: effectiveConfig.memoryDecay?.halfLifeByEventType ?? defaultConfig.memoryDecay?.halfLifeByEventType,
|
|
1315
|
+
antiDecay: {
|
|
1316
|
+
enabled: effectiveConfig.memoryDecay?.antiDecay?.enabled ?? defaultConfig.memoryDecay?.antiDecay?.enabled,
|
|
1317
|
+
maxBoost: effectiveConfig.memoryDecay?.antiDecay?.maxBoost ?? defaultConfig.memoryDecay?.antiDecay?.maxBoost,
|
|
1318
|
+
hitWeight: effectiveConfig.memoryDecay?.antiDecay?.hitWeight ?? defaultConfig.memoryDecay?.antiDecay?.hitWeight,
|
|
1319
|
+
recentWindowDays: effectiveConfig.memoryDecay?.antiDecay?.recentWindowDays ?? defaultConfig.memoryDecay?.antiDecay?.recentWindowDays,
|
|
1320
|
+
},
|
|
1321
|
+
},
|
|
1818
1322
|
enabled: effectiveConfig.enabled ?? defaultConfig.enabled,
|
|
1819
|
-
fallbackToBuiltin: effectiveConfig.fallbackToBuiltin ??
|
|
1820
|
-
apiUrl: effectiveConfig.apiUrl ?? "http://127.0.0.1:8765",
|
|
1821
|
-
engineMode: "ts",
|
|
1323
|
+
fallbackToBuiltin: effectiveConfig.fallbackToBuiltin ?? true,
|
|
1822
1324
|
};
|
|
1823
1325
|
memoryEngine = null;
|
|
1824
1326
|
if (api.getBuiltinMemory) {
|
|
@@ -1837,7 +1339,6 @@ function register(pluginApi, userConfig) {
|
|
|
1837
1339
|
reranker: { model: config.reranker.model },
|
|
1838
1340
|
enabled: config.enabled,
|
|
1839
1341
|
fallbackToBuiltin: config.fallbackToBuiltin,
|
|
1840
|
-
engineMode: config.engineMode,
|
|
1841
1342
|
});
|
|
1842
1343
|
checkOpenClawVersion().catch(e => logger.warn(`Version check failed: ${e}`));
|
|
1843
1344
|
configPath = findOpenClawConfig();
|
|
@@ -1852,32 +1353,13 @@ function register(pluginApi, userConfig) {
|
|
|
1852
1353
|
isRegistered = true;
|
|
1853
1354
|
logger.info("Cortex Memory plugin registered successfully");
|
|
1854
1355
|
logger.info(`Cortex Memory engine mode: ${resolveEngine().mode}`);
|
|
1356
|
+
logLifecycle("register_success", {
|
|
1357
|
+
enabled: isEnabled,
|
|
1358
|
+
});
|
|
1855
1359
|
if (isEnabled) {
|
|
1856
1360
|
registerTools();
|
|
1857
1361
|
registerHooks();
|
|
1858
1362
|
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
1363
|
}
|
|
1879
|
-
await startPythonService();
|
|
1880
|
-
await waitForService();
|
|
1881
|
-
logger.info("Cortex Memory Python service started successfully");
|
|
1882
1364
|
}
|
|
1883
1365
|
//# sourceMappingURL=index.js.map
|