openclaw-cortex-memory 0.1.0-Alpha.13 → 0.1.0-Alpha.15
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 +86 -146
- package/SKILL.md +1 -1
- package/dist/index.d.ts +6 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +331 -1305
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +1 -1
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +32 -5
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/store/vector_store.d.ts.map +1 -1
- package/dist/src/store/vector_store.js +17 -3
- package/dist/src/store/vector_store.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39,10 +39,8 @@ 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");
|
|
@@ -55,16 +53,6 @@ const reflector_1 = require("./src/reflect/reflector");
|
|
|
55
53
|
const three_stage_deduplicator_1 = require("./src/dedup/three_stage_deduplicator");
|
|
56
54
|
const runtime_env_1 = require("./src/utils/runtime_env");
|
|
57
55
|
const ERROR_CODES = {
|
|
58
|
-
CONNECTION_REFUSED: {
|
|
59
|
-
code: "E001",
|
|
60
|
-
message: "Cannot connect to the memory service",
|
|
61
|
-
suggestion: "The Python backend may not be running. Try restarting the OpenClaw gateway."
|
|
62
|
-
},
|
|
63
|
-
TIMEOUT: {
|
|
64
|
-
code: "E002",
|
|
65
|
-
message: "The memory service is not responding",
|
|
66
|
-
suggestion: "The service may be overloaded. Wait a moment and try again."
|
|
67
|
-
},
|
|
68
56
|
NOT_FOUND: {
|
|
69
57
|
code: "E003",
|
|
70
58
|
message: "Memory not found",
|
|
@@ -75,11 +63,6 @@ const ERROR_CODES = {
|
|
|
75
63
|
message: "Invalid input provided",
|
|
76
64
|
suggestion: "Please check your input parameters and try again."
|
|
77
65
|
},
|
|
78
|
-
SERVICE_ERROR: {
|
|
79
|
-
code: "E005",
|
|
80
|
-
message: "The memory service encountered an error",
|
|
81
|
-
suggestion: "Check the service logs for details or try restarting the gateway."
|
|
82
|
-
},
|
|
83
66
|
PLUGIN_DISABLED: {
|
|
84
67
|
code: "E006",
|
|
85
68
|
message: "Cortex Memory plugin is disabled",
|
|
@@ -153,8 +136,6 @@ const defaultConfig = {
|
|
|
153
136
|
},
|
|
154
137
|
},
|
|
155
138
|
enabled: true,
|
|
156
|
-
fallbackToBuiltin: true,
|
|
157
|
-
engineMode: "ts",
|
|
158
139
|
};
|
|
159
140
|
let autoSearchCacheBySession = new Map();
|
|
160
141
|
const AUTO_SEARCH_CACHE_TTL = 60000;
|
|
@@ -162,13 +143,11 @@ const MAX_AUTO_SEARCH_CACHE_SESSIONS = 200;
|
|
|
162
143
|
const HOOK_GUARD_TIMEOUT_MS = 2000;
|
|
163
144
|
let config = null;
|
|
164
145
|
let logger;
|
|
165
|
-
let pythonProcess = null;
|
|
166
146
|
let isShuttingDown = false;
|
|
167
147
|
let isInitializing = false;
|
|
168
148
|
let isRegistered = false;
|
|
169
149
|
let isEnabled = false;
|
|
170
150
|
let api = null;
|
|
171
|
-
let builtinMemory = null;
|
|
172
151
|
let registeredTools = [];
|
|
173
152
|
let registeredHooks = [];
|
|
174
153
|
let registeredFallbackTools = [];
|
|
@@ -178,13 +157,9 @@ let autoReflectInterval = null;
|
|
|
178
157
|
let lastAutoReflectArchiveMarker = "";
|
|
179
158
|
let lastAutoReflectRunAt = 0;
|
|
180
159
|
let configPath = null;
|
|
181
|
-
let pythonStartPromise = null;
|
|
182
160
|
let processHandlersRegistered = false;
|
|
183
|
-
let pythonPidFilePath = null;
|
|
184
161
|
let memoryEngine = null;
|
|
185
|
-
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
162
|
+
let builtinMemory = null;
|
|
188
163
|
function getMemoryRoot() {
|
|
189
164
|
const projectRoot = findProjectRoot();
|
|
190
165
|
return config?.dbPath ? path.resolve(config.dbPath) : path.join(projectRoot, "data", "memory");
|
|
@@ -245,8 +220,7 @@ function resolveEngine() {
|
|
|
245
220
|
if (!config) {
|
|
246
221
|
throw new Error("Configuration not loaded");
|
|
247
222
|
}
|
|
248
|
-
|
|
249
|
-
if (memoryEngine && memoryEngine.mode === "ts") {
|
|
223
|
+
if (memoryEngine) {
|
|
250
224
|
return memoryEngine;
|
|
251
225
|
}
|
|
252
226
|
const projectRoot = findProjectRoot();
|
|
@@ -373,393 +347,125 @@ function createConsoleLogger() {
|
|
|
373
347
|
};
|
|
374
348
|
}
|
|
375
349
|
function sanitizeForLogging(obj) {
|
|
376
|
-
const
|
|
350
|
+
const result = {};
|
|
377
351
|
for (const [key, value] of Object.entries(obj)) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
sanitized[key] = "***REDACTED***";
|
|
352
|
+
if (SENSITIVE_KEYS.some(k => key.toUpperCase().includes(k))) {
|
|
353
|
+
result[key] = "***REDACTED***";
|
|
381
354
|
}
|
|
382
|
-
else if (typeof value === "object" && value !== null) {
|
|
383
|
-
|
|
355
|
+
else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
356
|
+
result[key] = sanitizeForLogging(value);
|
|
384
357
|
}
|
|
385
358
|
else {
|
|
386
|
-
|
|
359
|
+
result[key] = value;
|
|
387
360
|
}
|
|
388
361
|
}
|
|
389
|
-
return
|
|
362
|
+
return result;
|
|
390
363
|
}
|
|
391
|
-
function
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
function asRecord(value) {
|
|
401
|
-
if (typeof value === "object" && value !== null) {
|
|
402
|
-
return value;
|
|
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);
|
|
403
372
|
}
|
|
404
|
-
return
|
|
373
|
+
return process.cwd();
|
|
405
374
|
}
|
|
406
|
-
function
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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();
|
|
410
388
|
}
|
|
411
389
|
}
|
|
412
|
-
return
|
|
390
|
+
return `fallback:${Date.now().toString(36)}`;
|
|
413
391
|
}
|
|
414
392
|
function normalizeIncomingMessage(payload) {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
const message = asRecord(data.message);
|
|
420
|
-
const eventData = asRecord(data.data);
|
|
421
|
-
const update = asRecord(data.update);
|
|
422
|
-
const updateMessage = update ? asRecord(update.message) : null;
|
|
423
|
-
const role = firstString([
|
|
424
|
-
data.role,
|
|
425
|
-
data.fromRole,
|
|
426
|
-
data.senderRole,
|
|
427
|
-
message?.role,
|
|
428
|
-
eventData?.role,
|
|
429
|
-
updateMessage?.role,
|
|
430
|
-
]) || "user";
|
|
431
|
-
const source = firstString([
|
|
432
|
-
data.source,
|
|
433
|
-
data.platform,
|
|
434
|
-
data.channel,
|
|
435
|
-
data.provider,
|
|
436
|
-
message?.source,
|
|
437
|
-
eventData?.source,
|
|
438
|
-
]) || "message";
|
|
439
|
-
let text = firstString([
|
|
440
|
-
data.content,
|
|
441
|
-
data.text,
|
|
442
|
-
data.body,
|
|
443
|
-
data.prompt,
|
|
444
|
-
data.message,
|
|
445
|
-
message?.content,
|
|
446
|
-
message?.text,
|
|
447
|
-
message?.body,
|
|
448
|
-
eventData?.content,
|
|
449
|
-
eventData?.text,
|
|
450
|
-
updateMessage?.text,
|
|
451
|
-
updateMessage?.caption,
|
|
452
|
-
]);
|
|
453
|
-
if (!text && Array.isArray(data.messages)) {
|
|
454
|
-
const merged = data.messages
|
|
455
|
-
.map(item => {
|
|
456
|
-
if (typeof item === "string")
|
|
457
|
-
return item;
|
|
458
|
-
const msgObj = asRecord(item);
|
|
459
|
-
if (!msgObj)
|
|
460
|
-
return "";
|
|
461
|
-
return firstString([msgObj.content, msgObj.text, msgObj.body]) || "";
|
|
462
|
-
})
|
|
463
|
-
.filter(Boolean)
|
|
464
|
-
.join("\n");
|
|
465
|
-
text = merged.trim() || undefined;
|
|
466
|
-
}
|
|
467
|
-
if (!text) {
|
|
393
|
+
const p = payload;
|
|
394
|
+
const text = typeof p.text === "string" ? p.text : (typeof p.content === "string" ? p.content : "");
|
|
395
|
+
if (!text)
|
|
468
396
|
return null;
|
|
469
|
-
|
|
397
|
+
const role = typeof p.role === "string" ? p.role : "unknown";
|
|
398
|
+
const source = typeof p.source === "string" ? p.source : "unknown";
|
|
470
399
|
return { text, role, source };
|
|
471
400
|
}
|
|
472
|
-
function
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
return fromContext;
|
|
476
|
-
const data = asRecord(payload);
|
|
477
|
-
const dataChat = data ? asRecord(data.chat) : null;
|
|
478
|
-
const message = data ? asRecord(data.message) : null;
|
|
479
|
-
const messageChat = message ? asRecord(message.chat) : null;
|
|
480
|
-
const eventData = data ? asRecord(data.data) : null;
|
|
481
|
-
const eventChat = eventData ? asRecord(eventData.chat) : null;
|
|
482
|
-
const update = data ? asRecord(data.update) : null;
|
|
483
|
-
const updateMessage = update ? asRecord(update.message) : null;
|
|
484
|
-
const updateChat = updateMessage ? asRecord(updateMessage.chat) : null;
|
|
485
|
-
const direct = firstString([
|
|
486
|
-
data?.sessionId,
|
|
487
|
-
data?.session_id,
|
|
488
|
-
data?.conversationId,
|
|
489
|
-
data?.conversation_id,
|
|
490
|
-
data?.threadId,
|
|
491
|
-
data?.thread_id,
|
|
492
|
-
message?.sessionId,
|
|
493
|
-
eventData?.sessionId,
|
|
494
|
-
]);
|
|
495
|
-
if (direct)
|
|
496
|
-
return direct;
|
|
497
|
-
const chatId = firstString([
|
|
498
|
-
data?.chatId,
|
|
499
|
-
data?.chat_id,
|
|
500
|
-
dataChat?.id,
|
|
501
|
-
messageChat?.id,
|
|
502
|
-
eventChat?.id,
|
|
503
|
-
updateChat?.id,
|
|
504
|
-
]);
|
|
505
|
-
if (chatId)
|
|
506
|
-
return `chat:${chatId}`;
|
|
507
|
-
return `fallback:${context.workspaceId || "default"}:${context.agentId || "agent"}`;
|
|
508
|
-
}
|
|
509
|
-
function compareVersions(a, b) {
|
|
510
|
-
const partsA = a.split(".").map(Number);
|
|
511
|
-
const partsB = b.split(".").map(Number);
|
|
512
|
-
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
513
|
-
const valA = partsA[i] || 0;
|
|
514
|
-
const valB = partsB[i] || 0;
|
|
515
|
-
if (valA < valB)
|
|
516
|
-
return -1;
|
|
517
|
-
if (valA > valB)
|
|
518
|
-
return 1;
|
|
519
|
-
}
|
|
520
|
-
return 0;
|
|
521
|
-
}
|
|
522
|
-
async function checkOpenClawVersion() {
|
|
523
|
-
try {
|
|
524
|
-
const version = (0, runtime_env_1.getEnvValue)("OPENCLAW_VERSION");
|
|
525
|
-
if (version) {
|
|
526
|
-
if (compareVersions(version, MIN_OPENCLAW_VERSION) < 0) {
|
|
527
|
-
throw new Error(`Incompatible OpenClaw version: ${version}. Minimum required: ${MIN_OPENCLAW_VERSION}`);
|
|
528
|
-
}
|
|
529
|
-
if (compareVersions(version, MAX_OPENCLAW_VERSION) >= 0) {
|
|
530
|
-
throw new Error(`Incompatible OpenClaw version: ${version}. Maximum supported: <${MAX_OPENCLAW_VERSION}`);
|
|
531
|
-
}
|
|
532
|
-
logger.info(`OpenClaw version check passed: ${version}`);
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
logger.warn("Could not determine OpenClaw version, proceeding with caution");
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
catch (e) {
|
|
539
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
540
|
-
logger.warn(`Version check warning: ${message}`);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
function findProjectRoot() {
|
|
544
|
-
if (api && api.rootDir) {
|
|
545
|
-
return api.rootDir;
|
|
546
|
-
}
|
|
547
|
-
let current = process.cwd();
|
|
548
|
-
while (current !== path.dirname(current)) {
|
|
549
|
-
if (fs.existsSync(path.join(current, "openclaw.plugin.json")) &&
|
|
550
|
-
fs.existsSync(path.join(current, "package.json"))) {
|
|
551
|
-
return current;
|
|
552
|
-
}
|
|
553
|
-
current = path.dirname(current);
|
|
554
|
-
}
|
|
555
|
-
throw new Error("Cannot find project root directory");
|
|
556
|
-
}
|
|
557
|
-
function findOpenClawConfig() {
|
|
558
|
-
const explicitConfigPath = (0, runtime_env_1.getEnvValue)("OPENCLAW_CONFIG_PATH");
|
|
559
|
-
const stateDir = (0, runtime_env_1.getEnvValue)("OPENCLAW_STATE_DIR");
|
|
560
|
-
const basePath = (0, runtime_env_1.getEnvValue)("OPENCLAW_BASE_PATH");
|
|
561
|
-
const homePath = (0, runtime_env_1.getHomeDir)();
|
|
562
|
-
const possiblePaths = [
|
|
563
|
-
explicitConfigPath,
|
|
564
|
-
stateDir ? path.join(stateDir, "openclaw.json") : "",
|
|
565
|
-
basePath ? path.join(basePath, "openclaw.json") : "",
|
|
566
|
-
path.join(process.cwd(), "openclaw.json"),
|
|
567
|
-
homePath ? path.join(homePath, ".openclaw", "openclaw.json") : "",
|
|
568
|
-
];
|
|
569
|
-
for (const p of possiblePaths) {
|
|
570
|
-
if (p && fs.existsSync(p)) {
|
|
571
|
-
return p;
|
|
572
|
-
}
|
|
401
|
+
function asRecord(value) {
|
|
402
|
+
if (typeof value === "object" && value !== null) {
|
|
403
|
+
return value;
|
|
573
404
|
}
|
|
574
405
|
return null;
|
|
575
406
|
}
|
|
576
|
-
function
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
try {
|
|
581
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
582
|
-
const openclawConfig = JSON.parse(content);
|
|
583
|
-
const pluginEntry = openclawConfig?.plugins?.entries?.[PLUGIN_ID];
|
|
584
|
-
if (pluginEntry && typeof pluginEntry === "object") {
|
|
585
|
-
return pluginEntry.enabled !== false;
|
|
586
|
-
}
|
|
587
|
-
const legacyPluginConfig = openclawConfig?.plugins?.["cortex-memory"];
|
|
588
|
-
return legacyPluginConfig?.enabled !== false;
|
|
589
|
-
}
|
|
590
|
-
catch (e) {
|
|
591
|
-
logger.warn(`Failed to load config state: ${e}`);
|
|
592
|
-
return true;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
function startConfigWatcher() {
|
|
596
|
-
if (configWatchInterval) {
|
|
597
|
-
clearInterval(configWatchInterval);
|
|
598
|
-
}
|
|
599
|
-
let lastEnabledState = isEnabled;
|
|
600
|
-
configWatchInterval = setInterval(() => {
|
|
601
|
-
const newState = loadPluginEnabledState();
|
|
602
|
-
if (newState !== lastEnabledState) {
|
|
603
|
-
lastEnabledState = newState;
|
|
604
|
-
if (newState && !isEnabled) {
|
|
605
|
-
logger.info("Detected config change: enabling Cortex Memory plugin");
|
|
606
|
-
enable();
|
|
607
|
-
}
|
|
608
|
-
else if (!newState && isEnabled) {
|
|
609
|
-
logger.info("Detected config change: disabling Cortex Memory plugin");
|
|
610
|
-
disable();
|
|
611
|
-
}
|
|
407
|
+
function firstString(values) {
|
|
408
|
+
for (const v of values) {
|
|
409
|
+
if (typeof v === "string" && v.trim()) {
|
|
410
|
+
return v.trim();
|
|
612
411
|
}
|
|
613
|
-
}, 5000);
|
|
614
|
-
}
|
|
615
|
-
function stopConfigWatcher() {
|
|
616
|
-
if (configWatchInterval) {
|
|
617
|
-
clearInterval(configWatchInterval);
|
|
618
|
-
configWatchInterval = null;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
function startAutoReflectScheduler() {
|
|
622
|
-
if (!config?.autoReflect || autoReflectInterval) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
const intervalMinutes = Math.max(5, Math.floor(config.autoReflectIntervalMinutes ?? 30));
|
|
626
|
-
const intervalMs = intervalMinutes * 60 * 1000;
|
|
627
|
-
autoReflectInterval = setInterval(() => {
|
|
628
|
-
if (!isEnabled) {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
const schedulerContext = {
|
|
632
|
-
agentId: "cortex-memory-scheduler",
|
|
633
|
-
workspaceId: "system",
|
|
634
|
-
};
|
|
635
|
-
const marker = getArchiveMarker();
|
|
636
|
-
const now = Date.now();
|
|
637
|
-
if (marker === "missing" || marker === "error") {
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
if (marker === lastAutoReflectArchiveMarker) {
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
if (now - lastAutoReflectRunAt < intervalMs) {
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
resolveEngine().reflectMemory({}, schedulerContext)
|
|
647
|
-
.then(result => {
|
|
648
|
-
if (result.success) {
|
|
649
|
-
lastAutoReflectArchiveMarker = marker;
|
|
650
|
-
lastAutoReflectRunAt = now;
|
|
651
|
-
logger.info("Scheduled reflection complete");
|
|
652
|
-
}
|
|
653
|
-
else {
|
|
654
|
-
logger.warn(`Auto-reflect failed: ${result.error ?? "unknown_error"}`);
|
|
655
|
-
}
|
|
656
|
-
})
|
|
657
|
-
.catch(error => logger.warn(`Auto-reflect failed: ${String(error)}`));
|
|
658
|
-
}, intervalMs);
|
|
659
|
-
}
|
|
660
|
-
function stopAutoReflectScheduler() {
|
|
661
|
-
if (autoReflectInterval) {
|
|
662
|
-
clearInterval(autoReflectInterval);
|
|
663
|
-
autoReflectInterval = null;
|
|
664
412
|
}
|
|
413
|
+
return undefined;
|
|
665
414
|
}
|
|
666
415
|
function validateConfig(cfg) {
|
|
667
416
|
const errors = [];
|
|
668
|
-
if (!cfg.embedding?.provider
|
|
669
|
-
errors.push("embedding.provider
|
|
670
|
-
}
|
|
671
|
-
if (!cfg.embedding?.apiKey || !cfg.embedding?.baseURL) {
|
|
672
|
-
errors.push("embedding.apiKey and embedding.baseURL are required. Please configure third-party embedding endpoint credentials.");
|
|
673
|
-
}
|
|
674
|
-
if (typeof cfg.embedding?.timeoutMs === "number" && (!Number.isFinite(cfg.embedding.timeoutMs) || cfg.embedding.timeoutMs < 1000)) {
|
|
675
|
-
errors.push("embedding.timeoutMs must be a number >= 1000.");
|
|
676
|
-
}
|
|
677
|
-
if (typeof cfg.embedding?.maxRetries === "number" && (!Number.isFinite(cfg.embedding.maxRetries) || cfg.embedding.maxRetries < 1 || cfg.embedding.maxRetries > 8)) {
|
|
678
|
-
errors.push("embedding.maxRetries must be between 1 and 8.");
|
|
417
|
+
if (!cfg.embedding?.provider) {
|
|
418
|
+
errors.push("embedding.provider is required.");
|
|
679
419
|
}
|
|
680
|
-
if (!cfg.
|
|
681
|
-
errors.push("
|
|
420
|
+
if (!cfg.embedding?.model) {
|
|
421
|
+
errors.push("embedding.model is required.");
|
|
682
422
|
}
|
|
683
|
-
if (!cfg.llm?.
|
|
684
|
-
errors.push("llm.
|
|
423
|
+
if (!cfg.llm?.provider) {
|
|
424
|
+
errors.push("llm.provider is required.");
|
|
685
425
|
}
|
|
686
|
-
if (!cfg.
|
|
687
|
-
errors.push("
|
|
426
|
+
if (!cfg.llm?.model) {
|
|
427
|
+
errors.push("llm.model is required.");
|
|
688
428
|
}
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
errors.push("autoReflectIntervalMinutes must be a number >= 5.");
|
|
694
|
-
}
|
|
695
|
-
if (cfg.readFusion && typeof cfg.readFusion.maxCandidates === "number" && (!Number.isFinite(cfg.readFusion.maxCandidates) || cfg.readFusion.maxCandidates < 2)) {
|
|
696
|
-
errors.push("readFusion.maxCandidates must be a number >= 2.");
|
|
429
|
+
if (cfg.autoReflectIntervalMinutes !== undefined) {
|
|
430
|
+
if (!Number.isFinite(cfg.autoReflectIntervalMinutes) || cfg.autoReflectIntervalMinutes < 5) {
|
|
431
|
+
errors.push("autoReflectIntervalMinutes must be >= 5.");
|
|
432
|
+
}
|
|
697
433
|
}
|
|
698
434
|
if (cfg.readFusion?.channelWeights) {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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.`);
|
|
703
439
|
}
|
|
704
440
|
}
|
|
705
441
|
}
|
|
706
442
|
if (cfg.readFusion?.channelTopK) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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.`);
|
|
711
447
|
}
|
|
712
448
|
}
|
|
713
449
|
}
|
|
714
|
-
if (
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (typeof cfg.readFusion?.minSemanticHits === "number" && (!Number.isFinite(cfg.readFusion.minSemanticHits) || cfg.readFusion.minSemanticHits < 0)) {
|
|
718
|
-
errors.push("readFusion.minSemanticHits must be a number >= 0.");
|
|
719
|
-
}
|
|
720
|
-
if (cfg.readFusion?.lengthNorm) {
|
|
721
|
-
const ln = cfg.readFusion.lengthNorm;
|
|
722
|
-
if (typeof ln.pivotChars === "number" && (!Number.isFinite(ln.pivotChars) || ln.pivotChars <= 0)) {
|
|
723
|
-
errors.push("readFusion.lengthNorm.pivotChars must be > 0.");
|
|
724
|
-
}
|
|
725
|
-
if (typeof ln.strength === "number" && (!Number.isFinite(ln.strength) || ln.strength <= 0)) {
|
|
726
|
-
errors.push("readFusion.lengthNorm.strength must be > 0.");
|
|
727
|
-
}
|
|
728
|
-
if (typeof ln.minFactor === "number" && (!Number.isFinite(ln.minFactor) || ln.minFactor <= 0 || ln.minFactor > 1)) {
|
|
729
|
-
errors.push("readFusion.lengthNorm.minFactor must be within (0,1].");
|
|
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.");
|
|
730
453
|
}
|
|
731
454
|
}
|
|
732
|
-
if (cfg.vectorChunking) {
|
|
733
|
-
if (
|
|
734
|
-
errors.push("vectorChunking.chunkSize must be >= 200.");
|
|
735
|
-
}
|
|
736
|
-
if (typeof cfg.vectorChunking.chunkOverlap === "number" && (!Number.isFinite(cfg.vectorChunking.chunkOverlap) || cfg.vectorChunking.chunkOverlap < 0)) {
|
|
455
|
+
if (cfg.vectorChunking?.chunkOverlap !== undefined) {
|
|
456
|
+
if (!Number.isFinite(cfg.vectorChunking.chunkOverlap) || cfg.vectorChunking.chunkOverlap < 0) {
|
|
737
457
|
errors.push("vectorChunking.chunkOverlap must be >= 0.");
|
|
738
458
|
}
|
|
739
|
-
if (typeof cfg.vectorChunking.chunkSize === "number" &&
|
|
740
|
-
typeof cfg.vectorChunking.chunkOverlap === "number" &&
|
|
741
|
-
cfg.vectorChunking.chunkOverlap >= cfg.vectorChunking.chunkSize) {
|
|
742
|
-
errors.push("vectorChunking.chunkOverlap must be smaller than chunkSize.");
|
|
743
|
-
}
|
|
744
459
|
}
|
|
745
460
|
if (cfg.memoryDecay) {
|
|
746
461
|
if (typeof cfg.memoryDecay.minFloor === "number" && (!Number.isFinite(cfg.memoryDecay.minFloor) || cfg.memoryDecay.minFloor < 0 || cfg.memoryDecay.minFloor > 1)) {
|
|
747
|
-
errors.push("memoryDecay.minFloor must be
|
|
462
|
+
errors.push("memoryDecay.minFloor must be between 0 and 1.");
|
|
748
463
|
}
|
|
749
464
|
if (typeof cfg.memoryDecay.defaultHalfLifeDays === "number" && (!Number.isFinite(cfg.memoryDecay.defaultHalfLifeDays) || cfg.memoryDecay.defaultHalfLifeDays <= 0)) {
|
|
750
465
|
errors.push("memoryDecay.defaultHalfLifeDays must be > 0.");
|
|
751
466
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
for (const [key, value] of Object.entries(mapping)) {
|
|
755
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
756
|
-
errors.push(`memoryDecay.halfLifeByEventType.${key} must be > 0.`);
|
|
757
|
-
break;
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
const anti = cfg.memoryDecay.antiDecay;
|
|
762
|
-
if (anti) {
|
|
467
|
+
if (cfg.memoryDecay.antiDecay) {
|
|
468
|
+
const anti = cfg.memoryDecay.antiDecay;
|
|
763
469
|
if (typeof anti.maxBoost === "number" && (!Number.isFinite(anti.maxBoost) || anti.maxBoost < 1)) {
|
|
764
470
|
errors.push("memoryDecay.antiDecay.maxBoost must be >= 1.");
|
|
765
471
|
}
|
|
@@ -771,834 +477,161 @@ function validateConfig(cfg) {
|
|
|
771
477
|
}
|
|
772
478
|
}
|
|
773
479
|
}
|
|
774
|
-
return errors;
|
|
775
|
-
}
|
|
776
|
-
function getApiHostAndPort() {
|
|
777
|
-
const parsed = new URL(getBaseUrl());
|
|
778
|
-
const host = parsed.hostname || "127.0.0.1";
|
|
779
|
-
const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
|
|
780
|
-
return { host, port };
|
|
781
|
-
}
|
|
782
|
-
function sleep(ms) {
|
|
783
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
784
|
-
}
|
|
785
|
-
function isPortListening(host, port, timeoutMs = 700) {
|
|
786
|
-
return new Promise(resolve => {
|
|
787
|
-
const socket = new net.Socket();
|
|
788
|
-
let settled = false;
|
|
789
|
-
const finalize = (value) => {
|
|
790
|
-
if (settled)
|
|
791
|
-
return;
|
|
792
|
-
settled = true;
|
|
793
|
-
socket.destroy();
|
|
794
|
-
resolve(value);
|
|
795
|
-
};
|
|
796
|
-
socket.setTimeout(timeoutMs);
|
|
797
|
-
socket.once("connect", () => finalize(true));
|
|
798
|
-
socket.once("timeout", () => finalize(false));
|
|
799
|
-
socket.once("error", () => finalize(false));
|
|
800
|
-
socket.connect(port, host);
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
function writePythonPid(pid) {
|
|
804
|
-
if (!pythonPidFilePath)
|
|
805
|
-
return;
|
|
806
|
-
try {
|
|
807
|
-
fs.writeFileSync(pythonPidFilePath, String(pid), "utf-8");
|
|
808
|
-
}
|
|
809
|
-
catch (e) {
|
|
810
|
-
logger.warn(`Failed to write Python pid file: ${e instanceof Error ? e.message : String(e)}`);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
function clearPythonPidFile() {
|
|
814
|
-
if (!pythonPidFilePath)
|
|
815
|
-
return;
|
|
816
|
-
try {
|
|
817
|
-
if (fs.existsSync(pythonPidFilePath)) {
|
|
818
|
-
fs.unlinkSync(pythonPidFilePath);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
catch (e) {
|
|
822
|
-
logger.warn(`Failed to clear Python pid file: ${e instanceof Error ? e.message : String(e)}`);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
function readPythonPid() {
|
|
826
|
-
if (!pythonPidFilePath || !fs.existsSync(pythonPidFilePath))
|
|
827
|
-
return null;
|
|
828
|
-
try {
|
|
829
|
-
const raw = fs.readFileSync(pythonPidFilePath, "utf-8").trim();
|
|
830
|
-
const pid = Number(raw);
|
|
831
|
-
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
832
|
-
}
|
|
833
|
-
catch {
|
|
834
|
-
return null;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
function killProcessByPid(pid) {
|
|
838
|
-
if (!pid)
|
|
839
|
-
return;
|
|
840
|
-
try {
|
|
841
|
-
if (process.platform === "win32") {
|
|
842
|
-
(0, child_process_1.execSync)(`taskkill /pid ${pid} /f /t`, { stdio: "ignore" });
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
try {
|
|
846
|
-
process.kill(pid, "SIGTERM");
|
|
847
|
-
}
|
|
848
|
-
catch { }
|
|
849
|
-
try {
|
|
850
|
-
process.kill(-pid, "SIGTERM");
|
|
851
|
-
}
|
|
852
|
-
catch { }
|
|
853
|
-
setTimeout(() => {
|
|
854
|
-
try {
|
|
855
|
-
process.kill(pid, "SIGKILL");
|
|
856
|
-
}
|
|
857
|
-
catch { }
|
|
858
|
-
try {
|
|
859
|
-
process.kill(-pid, "SIGKILL");
|
|
860
|
-
}
|
|
861
|
-
catch { }
|
|
862
|
-
}, 2000);
|
|
863
|
-
}
|
|
864
|
-
catch (e) {
|
|
865
|
-
logger.warn(`Failed to kill process ${pid}: ${e instanceof Error ? e.message : String(e)}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
function freePortWithSystemTools(port) {
|
|
869
|
-
try {
|
|
870
|
-
if (process.platform === "win32") {
|
|
871
|
-
const output = (0, child_process_1.execSync)(`netstat -ano | findstr :${port}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] });
|
|
872
|
-
const pids = output
|
|
873
|
-
.split(/\r?\n/)
|
|
874
|
-
.filter(line => line.includes("LISTENING"))
|
|
875
|
-
.map(line => {
|
|
876
|
-
const parts = line.trim().split(/\s+/);
|
|
877
|
-
return Number(parts[parts.length - 1]);
|
|
878
|
-
})
|
|
879
|
-
.filter(pid => Number.isInteger(pid) && pid > 0);
|
|
880
|
-
for (const pid of pids) {
|
|
881
|
-
killProcessByPid(pid);
|
|
882
|
-
}
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
const output = (0, child_process_1.execSync)(`sh -lc "lsof -ti tcp:${port} 2>/dev/null || true"`, { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] });
|
|
886
|
-
const pids = output
|
|
887
|
-
.split(/\r?\n/)
|
|
888
|
-
.map(line => Number(line.trim()))
|
|
889
|
-
.filter(pid => Number.isInteger(pid) && pid > 0);
|
|
890
|
-
for (const pid of pids) {
|
|
891
|
-
killProcessByPid(pid);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
catch {
|
|
895
|
-
// ignore
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
async function checkPortInUse() {
|
|
899
|
-
const apiUrl = getBaseUrl();
|
|
900
|
-
try {
|
|
901
|
-
const response = await fetch(`${apiUrl}/health`, { signal: AbortSignal.timeout(1000) });
|
|
902
|
-
return response.ok;
|
|
903
|
-
}
|
|
904
|
-
catch {
|
|
905
|
-
return false;
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
async function startPythonService() {
|
|
909
|
-
if (pythonStartPromise) {
|
|
910
|
-
return pythonStartPromise;
|
|
911
|
-
}
|
|
912
|
-
pythonStartPromise = startPythonServiceInternal().finally(() => {
|
|
913
|
-
pythonStartPromise = null;
|
|
914
|
-
});
|
|
915
|
-
return pythonStartPromise;
|
|
916
|
-
}
|
|
917
|
-
async function startPythonServiceInternal() {
|
|
918
|
-
if (!config) {
|
|
919
|
-
throw new Error("Configuration not loaded");
|
|
920
|
-
}
|
|
921
|
-
const projectRoot = findProjectRoot();
|
|
922
|
-
pythonPidFilePath = path.join(projectRoot, ".cortex-memory-python.pid");
|
|
923
|
-
const { host, port } = getApiHostAndPort();
|
|
924
|
-
const stalePid = readPythonPid();
|
|
925
|
-
if (stalePid && (!pythonProcess || pythonProcess.pid !== stalePid)) {
|
|
926
|
-
logger.info(`Found stale Python pid ${stalePid}, trying to stop it...`);
|
|
927
|
-
killProcessByPid(stalePid);
|
|
928
|
-
await sleep(800);
|
|
929
|
-
clearPythonPidFile();
|
|
930
|
-
}
|
|
931
|
-
const healthyRunning = await checkPortInUse();
|
|
932
|
-
if (healthyRunning) {
|
|
933
|
-
logger.info("Python service already running, shutting down old instance...");
|
|
934
|
-
await shutdownPythonApi();
|
|
935
|
-
await sleep(1000);
|
|
936
|
-
}
|
|
937
|
-
const occupied = await isPortListening(host, port);
|
|
938
|
-
if (occupied) {
|
|
939
|
-
logger.warn(`Port ${port} is still occupied after graceful shutdown, forcing cleanup...`);
|
|
940
|
-
freePortWithSystemTools(port);
|
|
941
|
-
await sleep(1000);
|
|
942
|
-
}
|
|
943
|
-
if (await isPortListening(host, port)) {
|
|
944
|
-
throw new Error(`Port ${port} is already in use by another process. Please stop that process and retry.`);
|
|
945
|
-
}
|
|
946
|
-
const venvDir = path.join(projectRoot, "venv");
|
|
947
|
-
const pythonCmd = process.platform === "win32"
|
|
948
|
-
? path.join(venvDir, "Scripts", "python.exe")
|
|
949
|
-
: path.join(venvDir, "bin", "python");
|
|
950
|
-
if (!fs.existsSync(pythonCmd)) {
|
|
951
|
-
throw new Error("Python environment not found. Please run 'npm install' first.");
|
|
952
|
-
}
|
|
953
|
-
const errors = validateConfig(config);
|
|
954
|
-
if (errors.length > 0) {
|
|
955
|
-
throw new Error(`Configuration errors:\n${errors.join("\n")}`);
|
|
956
|
-
}
|
|
957
|
-
logger.info("Starting Cortex Memory Python service...");
|
|
958
|
-
const env = {
|
|
959
|
-
...(0, runtime_env_1.getProcessEnvCopy)(),
|
|
960
|
-
CORTEX_MEMORY_EMBEDDING_PROVIDER: config.embedding.provider,
|
|
961
|
-
CORTEX_MEMORY_EMBEDDING_MODEL: config.embedding.model,
|
|
962
|
-
CORTEX_MEMORY_LLM_PROVIDER: config.llm.provider,
|
|
963
|
-
CORTEX_MEMORY_LLM_MODEL: config.llm.model,
|
|
964
|
-
CORTEX_MEMORY_RERANKER_PROVIDER: config.reranker.provider || "",
|
|
965
|
-
CORTEX_MEMORY_RERANKER_MODEL: config.reranker.model,
|
|
966
|
-
CORTEX_MEMORY_DB_PATH: config.dbPath || path.join((0, runtime_env_1.getHomeDir)(), ".openclaw", "agents", "main", "lancedb_store"),
|
|
967
|
-
};
|
|
968
|
-
if (config.embedding.apiKey) {
|
|
969
|
-
env.CORTEX_MEMORY_EMBEDDING_API_KEY = config.embedding.apiKey;
|
|
970
|
-
}
|
|
971
|
-
if (config.embedding.baseURL) {
|
|
972
|
-
env.CORTEX_MEMORY_EMBEDDING_BASE_URL = config.embedding.baseURL;
|
|
973
|
-
}
|
|
974
|
-
if (config.embedding.dimensions) {
|
|
975
|
-
env.CORTEX_MEMORY_EMBEDDING_DIMENSIONS = String(config.embedding.dimensions);
|
|
976
|
-
}
|
|
977
|
-
if (config.llm.apiKey) {
|
|
978
|
-
env.CORTEX_MEMORY_LLM_API_KEY = config.llm.apiKey;
|
|
979
|
-
}
|
|
980
|
-
if (config.llm.baseURL) {
|
|
981
|
-
env.CORTEX_MEMORY_LLM_BASE_URL = config.llm.baseURL;
|
|
982
|
-
}
|
|
983
|
-
if (config.reranker.apiKey) {
|
|
984
|
-
env.CORTEX_MEMORY_RERANKER_API_KEY = config.reranker.apiKey;
|
|
985
|
-
}
|
|
986
|
-
if (config.reranker.baseURL) {
|
|
987
|
-
env.CORTEX_MEMORY_RERANKER_ENDPOINT = config.reranker.baseURL;
|
|
988
|
-
}
|
|
989
|
-
return new Promise((resolve, reject) => {
|
|
990
|
-
pythonProcess = (0, child_process_1.spawn)(pythonCmd, ["-m", "api.server"], {
|
|
991
|
-
cwd: projectRoot,
|
|
992
|
-
detached: false,
|
|
993
|
-
windowsHide: true,
|
|
994
|
-
env: { ...env, PYTHONWARNINGS: "ignore::RuntimeWarning" },
|
|
995
|
-
});
|
|
996
|
-
if (pythonProcess.pid) {
|
|
997
|
-
writePythonPid(pythonProcess.pid);
|
|
998
|
-
}
|
|
999
|
-
let started = false;
|
|
1000
|
-
let stderrBuffer = "";
|
|
1001
|
-
let settled = false;
|
|
1002
|
-
let startupTimeout = null;
|
|
1003
|
-
const resolveOnce = () => {
|
|
1004
|
-
if (settled)
|
|
1005
|
-
return;
|
|
1006
|
-
settled = true;
|
|
1007
|
-
started = true;
|
|
1008
|
-
if (startupTimeout) {
|
|
1009
|
-
clearTimeout(startupTimeout);
|
|
1010
|
-
startupTimeout = null;
|
|
1011
|
-
}
|
|
1012
|
-
resolve();
|
|
1013
|
-
};
|
|
1014
|
-
const rejectOnce = (error) => {
|
|
1015
|
-
if (settled)
|
|
1016
|
-
return;
|
|
1017
|
-
settled = true;
|
|
1018
|
-
if (startupTimeout) {
|
|
1019
|
-
clearTimeout(startupTimeout);
|
|
1020
|
-
startupTimeout = null;
|
|
1021
|
-
}
|
|
1022
|
-
reject(error);
|
|
1023
|
-
};
|
|
1024
|
-
pythonProcess.stdout?.on("data", (data) => {
|
|
1025
|
-
const output = data.toString();
|
|
1026
|
-
if (!output.toLowerCase().includes("key") && !output.toLowerCase().includes("token")) {
|
|
1027
|
-
logger.info(`[Python] ${output.trim()}`);
|
|
1028
|
-
}
|
|
1029
|
-
if (output.includes("Cortex Memory API started") || output.includes("Application startup complete")) {
|
|
1030
|
-
resolveOnce();
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
|
-
pythonProcess.stderr?.on("data", (data) => {
|
|
1034
|
-
const output = data.toString();
|
|
1035
|
-
stderrBuffer += output;
|
|
1036
|
-
if (!output.toLowerCase().includes("key") && !output.toLowerCase().includes("token")) {
|
|
1037
|
-
logger.warn(`[Python] ${output.trim()}`);
|
|
1038
|
-
}
|
|
1039
|
-
if (output.includes("Cortex Memory API started") ||
|
|
1040
|
-
output.includes("Application startup complete") ||
|
|
1041
|
-
output.includes("Uvicorn running on")) {
|
|
1042
|
-
resolveOnce();
|
|
1043
|
-
}
|
|
1044
|
-
});
|
|
1045
|
-
pythonProcess.on("error", (error) => {
|
|
1046
|
-
logger.error("Failed to start Python service:", error.message);
|
|
1047
|
-
rejectOnce(error);
|
|
1048
|
-
});
|
|
1049
|
-
pythonProcess.on("exit", (code) => {
|
|
1050
|
-
clearPythonPidFile();
|
|
1051
|
-
pythonProcess = null;
|
|
1052
|
-
if (!started && code !== 0 && !isShuttingDown) {
|
|
1053
|
-
rejectOnce(new Error(`Python service exited with code ${code}. Stderr: ${stderrBuffer.slice(-500)}`));
|
|
1054
|
-
}
|
|
1055
|
-
});
|
|
1056
|
-
startupTimeout = setTimeout(() => {
|
|
1057
|
-
if (!started) {
|
|
1058
|
-
const tail = stderrBuffer ? `\nLast stderr: ${stderrBuffer.slice(-500)}` : "";
|
|
1059
|
-
killPythonProcess();
|
|
1060
|
-
rejectOnce(new Error(`Timeout waiting for Python service to start (300s)${tail}`));
|
|
1061
|
-
}
|
|
1062
|
-
}, 300000);
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
async function shutdownPythonApi() {
|
|
1066
|
-
const apiUrl = getBaseUrl();
|
|
1067
|
-
try {
|
|
1068
|
-
await fetch(`${apiUrl}/shutdown`, { method: "POST", signal: AbortSignal.timeout(2000) });
|
|
1069
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1070
|
-
}
|
|
1071
|
-
catch {
|
|
1072
|
-
// ignore
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
function killPythonProcess() {
|
|
1076
|
-
const directPid = pythonProcess?.pid ?? null;
|
|
1077
|
-
const pidFromFile = readPythonPid();
|
|
1078
|
-
const pid = directPid || pidFromFile;
|
|
1079
|
-
if (!pid)
|
|
1080
|
-
return;
|
|
1081
|
-
try {
|
|
1082
|
-
killProcessByPid(pid);
|
|
1083
|
-
}
|
|
1084
|
-
catch (e) {
|
|
1085
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1086
|
-
logger.warn(`Failed to kill Python process: ${message}`);
|
|
1087
|
-
}
|
|
1088
|
-
finally {
|
|
1089
|
-
pythonProcess = null;
|
|
1090
|
-
clearPythonPidFile();
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
async function stopPythonServiceAsync() {
|
|
1094
|
-
if (pythonStartPromise) {
|
|
1095
|
-
try {
|
|
1096
|
-
await pythonStartPromise;
|
|
1097
|
-
}
|
|
1098
|
-
catch {
|
|
1099
|
-
// ignore
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
await shutdownPythonApi();
|
|
1103
|
-
killPythonProcess();
|
|
1104
|
-
}
|
|
1105
|
-
function stopPythonService() {
|
|
1106
|
-
stopPythonServiceAsync();
|
|
1107
|
-
}
|
|
1108
|
-
function getBaseUrl() {
|
|
1109
|
-
return config?.apiUrl ?? "http://localhost:8765";
|
|
1110
|
-
}
|
|
1111
|
-
async function waitForService(maxAttempts = 30) {
|
|
1112
|
-
const apiUrl = getBaseUrl();
|
|
1113
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
1114
|
-
try {
|
|
1115
|
-
const response = await fetch(`${apiUrl}/health`, { signal: AbortSignal.timeout(1000) });
|
|
1116
|
-
if (response.ok)
|
|
1117
|
-
return;
|
|
1118
|
-
}
|
|
1119
|
-
catch { }
|
|
1120
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1121
|
-
}
|
|
1122
|
-
throw new Error("Service failed to become ready");
|
|
1123
|
-
}
|
|
1124
|
-
function formatApiError(error) {
|
|
1125
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1126
|
-
const lower = message.toLowerCase();
|
|
1127
|
-
if (lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("fetch failed")) {
|
|
1128
|
-
const err = ERROR_CODES.CONNECTION_REFUSED;
|
|
1129
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
1130
|
-
}
|
|
1131
|
-
if (lower.includes("abort") || lower.includes("timed out")) {
|
|
1132
|
-
const err = ERROR_CODES.TIMEOUT;
|
|
1133
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
1134
|
-
}
|
|
1135
|
-
if (lower.includes("404") || lower.includes("not found")) {
|
|
1136
|
-
const err = ERROR_CODES.NOT_FOUND;
|
|
1137
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
1138
|
-
}
|
|
1139
|
-
if (lower.includes("400") || lower.includes("invalid")) {
|
|
1140
|
-
const err = ERROR_CODES.INVALID_INPUT;
|
|
1141
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
1142
|
-
}
|
|
1143
|
-
const err = ERROR_CODES.SERVICE_ERROR;
|
|
1144
|
-
return `${err.message} (${err.code}). Details: ${message}`;
|
|
1145
|
-
}
|
|
1146
|
-
const pendingRequests = new Map();
|
|
1147
|
-
const requestDebounceMs = 100;
|
|
1148
|
-
function getRequestKey(endpoint, method, body) {
|
|
1149
|
-
const bodyHash = body ? JSON.stringify(body).slice(0, 100) : "";
|
|
1150
|
-
return `${method}:${endpoint}:${bodyHash}`;
|
|
1151
|
-
}
|
|
1152
|
-
async function apiCallWithRetry(endpoint, method = "GET", body, options) {
|
|
1153
|
-
const { maxRetries = 3, baseDelay = 1000, timeout = 30000, skipDebounce = false } = options || {};
|
|
1154
|
-
if (!skipDebounce) {
|
|
1155
|
-
const requestKey = getRequestKey(endpoint, method, body);
|
|
1156
|
-
const pending = pendingRequests.get(requestKey);
|
|
1157
|
-
if (pending) {
|
|
1158
|
-
logger.debug(`Reusing pending request for ${endpoint}`);
|
|
1159
|
-
return pending.promise;
|
|
1160
|
-
}
|
|
1161
|
-
const requestPromise = apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout)
|
|
1162
|
-
.finally(() => {
|
|
1163
|
-
setTimeout(() => pendingRequests.delete(requestKey), requestDebounceMs);
|
|
1164
|
-
});
|
|
1165
|
-
pendingRequests.set(requestKey, {
|
|
1166
|
-
promise: requestPromise
|
|
1167
|
-
});
|
|
1168
|
-
return await requestPromise;
|
|
1169
|
-
}
|
|
1170
|
-
return apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout);
|
|
1171
|
-
}
|
|
1172
|
-
async function apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout) {
|
|
1173
|
-
let lastError = null;
|
|
1174
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1175
|
-
try {
|
|
1176
|
-
return await apiCall(endpoint, method, body, timeout);
|
|
1177
|
-
}
|
|
1178
|
-
catch (error) {
|
|
1179
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1180
|
-
const isRetryable = lastError.message.includes("E001") ||
|
|
1181
|
-
lastError.message.includes("E002") ||
|
|
1182
|
-
lastError.message.includes("timeout") ||
|
|
1183
|
-
lastError.message.includes("ECONNREFUSED") ||
|
|
1184
|
-
lastError.message.includes("ENOTFOUND");
|
|
1185
|
-
if (attempt < maxRetries - 1 && isRetryable) {
|
|
1186
|
-
const delay = baseDelay * Math.pow(2, attempt);
|
|
1187
|
-
logger.warn(`API call failed (attempt ${attempt + 1}/${maxRetries}), retrying in ${delay}ms: ${lastError.message.split(".")[0]}`);
|
|
1188
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
throw lastError;
|
|
1193
|
-
}
|
|
1194
|
-
async function apiCall(endpoint, method = "GET", body, timeout = 30000) {
|
|
1195
|
-
const url = `${getBaseUrl()}${endpoint}`;
|
|
1196
|
-
const controller = new AbortController();
|
|
1197
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1198
|
-
const options = {
|
|
1199
|
-
method,
|
|
1200
|
-
headers: { "Content-Type": "application/json" },
|
|
1201
|
-
signal: controller.signal,
|
|
1202
|
-
};
|
|
1203
|
-
if (body)
|
|
1204
|
-
options.body = JSON.stringify(body);
|
|
1205
|
-
try {
|
|
1206
|
-
const response = await fetch(url, options);
|
|
1207
|
-
const text = await response.text();
|
|
1208
|
-
if (!response.ok) {
|
|
1209
|
-
try {
|
|
1210
|
-
const errorData = JSON.parse(text);
|
|
1211
|
-
throw new Error(errorData.error || errorData.detail || `HTTP ${response.status}`);
|
|
1212
|
-
}
|
|
1213
|
-
catch {
|
|
1214
|
-
throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
if (!text)
|
|
1218
|
-
return {};
|
|
1219
|
-
try {
|
|
1220
|
-
return JSON.parse(text);
|
|
1221
|
-
}
|
|
1222
|
-
catch {
|
|
1223
|
-
throw new Error("Invalid JSON response");
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
catch (error) {
|
|
1227
|
-
throw new Error(formatApiError(error));
|
|
1228
|
-
}
|
|
1229
|
-
finally {
|
|
1230
|
-
clearTimeout(timeoutId);
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
async function searchMemoryWithFallback(args, context) {
|
|
1234
|
-
if (!args || !args.query) {
|
|
1235
|
-
logger.error(`search_memory called with invalid args: ${JSON.stringify(args)}`);
|
|
1236
|
-
return { success: false, error: ERROR_CODES.INVALID_INPUT.message + " Missing 'query' parameter.", errorCode: ERROR_CODES.INVALID_INPUT.code };
|
|
1237
|
-
}
|
|
1238
|
-
if (!isEnabled) {
|
|
1239
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1240
|
-
logger.info("Using builtin memory (plugin disabled)");
|
|
1241
|
-
try {
|
|
1242
|
-
const results = await builtinMemory.search(args.query, args.top_k || 3);
|
|
1243
|
-
return { success: true, data: results };
|
|
1244
|
-
}
|
|
1245
|
-
catch (error) {
|
|
1246
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1247
|
-
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1251
|
-
}
|
|
1252
|
-
try {
|
|
1253
|
-
const result = await apiCallWithRetry("/search", "POST", {
|
|
1254
|
-
query: args.query,
|
|
1255
|
-
top_k: args.top_k || 3,
|
|
1256
|
-
});
|
|
1257
|
-
return { success: true, data: result.results };
|
|
1258
|
-
}
|
|
1259
|
-
catch (error) {
|
|
1260
|
-
const message = formatApiError(error);
|
|
1261
|
-
logger.error(`search_memory failed: ${message}`);
|
|
1262
|
-
return { success: false, error: message };
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
async function storeEventWithFallback(args, context) {
|
|
1266
|
-
if (!isEnabled) {
|
|
1267
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1268
|
-
logger.info("Using builtin memory (plugin disabled)");
|
|
1269
|
-
try {
|
|
1270
|
-
const id = await builtinMemory.store(args.summary, {
|
|
1271
|
-
entities: args.entities,
|
|
1272
|
-
outcome: args.outcome,
|
|
1273
|
-
relations: args.relations
|
|
1274
|
-
});
|
|
1275
|
-
return { success: true, data: { event_id: id } };
|
|
1276
|
-
}
|
|
1277
|
-
catch (error) {
|
|
1278
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1279
|
-
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1283
|
-
}
|
|
1284
|
-
try {
|
|
1285
|
-
const result = await apiCallWithRetry("/event", "POST", {
|
|
1286
|
-
summary: args.summary,
|
|
1287
|
-
entities: args.entities,
|
|
1288
|
-
outcome: args.outcome,
|
|
1289
|
-
relations: args.relations,
|
|
1290
|
-
});
|
|
1291
|
-
return { success: true, data: result };
|
|
1292
|
-
}
|
|
1293
|
-
catch (error) {
|
|
1294
|
-
const message = formatApiError(error);
|
|
1295
|
-
logger.error(`store_event failed: ${message}`);
|
|
1296
|
-
return { success: false, error: message };
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
async function queryGraph(args, _context) {
|
|
1300
|
-
if (!args || !args.entity) {
|
|
1301
|
-
logger.error(`query_graph called with invalid args: ${JSON.stringify(args)}`);
|
|
1302
|
-
return { success: false, error: ERROR_CODES.INVALID_INPUT.message + " Missing 'entity' parameter.", errorCode: ERROR_CODES.INVALID_INPUT.code };
|
|
1303
|
-
}
|
|
1304
|
-
if (!isEnabled) {
|
|
1305
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1306
|
-
}
|
|
1307
|
-
try {
|
|
1308
|
-
const result = await apiCallWithRetry("/graph/query", "POST", { entity: args.entity });
|
|
1309
|
-
return { success: true, data: result.graph };
|
|
1310
|
-
}
|
|
1311
|
-
catch (error) {
|
|
1312
|
-
const message = formatApiError(error);
|
|
1313
|
-
logger.error(`query_graph failed: ${message}`);
|
|
1314
|
-
return { success: false, error: message };
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
async function getHotContext(args, _context) {
|
|
1318
|
-
if (!isEnabled) {
|
|
1319
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1320
|
-
}
|
|
1321
|
-
try {
|
|
1322
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 20;
|
|
1323
|
-
const result = await apiCallWithRetry(`/hot-context?limit=${limit}`, "GET");
|
|
1324
|
-
return { success: true, data: result.context };
|
|
1325
|
-
}
|
|
1326
|
-
catch (error) {
|
|
1327
|
-
const message = formatApiError(error);
|
|
1328
|
-
logger.error(`get_hot_context failed: ${message}`);
|
|
1329
|
-
return { success: false, error: message };
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
async function getAutoContext(args, context) {
|
|
1333
|
-
if (!isEnabled) {
|
|
1334
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1335
|
-
}
|
|
1336
|
-
const now = Date.now();
|
|
1337
|
-
const result = {};
|
|
1338
|
-
const sessionId = resolveSessionId(context);
|
|
1339
|
-
clearStaleAutoSearchCache(now);
|
|
1340
|
-
const sessionCache = autoSearchCacheBySession.get(sessionId);
|
|
1341
|
-
if (sessionCache) {
|
|
1342
|
-
result.auto_search = {
|
|
1343
|
-
query: sessionCache.query,
|
|
1344
|
-
results: sessionCache.results,
|
|
1345
|
-
age_seconds: Math.floor((now - sessionCache.timestamp) / 1000),
|
|
1346
|
-
};
|
|
1347
|
-
}
|
|
1348
|
-
if (args.include_hot !== false) {
|
|
1349
|
-
try {
|
|
1350
|
-
const hotResult = await apiCallWithRetry("/hot-context", "GET");
|
|
1351
|
-
result.hot_context = hotResult.context;
|
|
1352
|
-
}
|
|
1353
|
-
catch (error) {
|
|
1354
|
-
logger.debug(`Failed to get hot context: ${formatApiError(error)}`);
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
if (!result.auto_search && !result.hot_context) {
|
|
1358
|
-
return {
|
|
1359
|
-
success: true,
|
|
1360
|
-
data: {
|
|
1361
|
-
message: "No session-scoped auto-search results cached and hot context unavailable",
|
|
1362
|
-
suggestion: "Send a user message in this session or call get_hot_context."
|
|
1363
|
-
}
|
|
1364
|
-
};
|
|
1365
|
-
}
|
|
1366
|
-
return { success: true, data: result };
|
|
1367
|
-
}
|
|
1368
|
-
async function reflectMemory(_args, _context) {
|
|
1369
|
-
if (!isEnabled) {
|
|
1370
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1371
|
-
}
|
|
1372
|
-
try {
|
|
1373
|
-
await apiCallWithRetry("/reflect", "POST", undefined, {
|
|
1374
|
-
timeout: 120000,
|
|
1375
|
-
maxRetries: 2,
|
|
1376
|
-
});
|
|
1377
|
-
return { success: true };
|
|
1378
|
-
}
|
|
1379
|
-
catch (error) {
|
|
1380
|
-
const message = formatApiError(error);
|
|
1381
|
-
logger.error(`reflect_memory failed: ${message}`);
|
|
1382
|
-
return { success: false, error: message };
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
async function syncMemory(_args, _context) {
|
|
1386
|
-
if (!isEnabled) {
|
|
1387
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1388
|
-
}
|
|
1389
|
-
try {
|
|
1390
|
-
await apiCallWithRetry("/sync", "POST", undefined, {
|
|
1391
|
-
timeout: 300000,
|
|
1392
|
-
maxRetries: 2,
|
|
1393
|
-
});
|
|
1394
|
-
return { success: true };
|
|
1395
|
-
}
|
|
1396
|
-
catch (error) {
|
|
1397
|
-
const message = formatApiError(error);
|
|
1398
|
-
logger.error(`sync_memory failed: ${message}`);
|
|
1399
|
-
return { success: false, error: message };
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
async function promoteMemory(_args, _context) {
|
|
1403
|
-
if (!isEnabled) {
|
|
1404
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1405
|
-
}
|
|
1406
|
-
try {
|
|
1407
|
-
await apiCallWithRetry("/promote", "POST", undefined, {
|
|
1408
|
-
timeout: 120000,
|
|
1409
|
-
maxRetries: 2,
|
|
1410
|
-
});
|
|
1411
|
-
return { success: true };
|
|
1412
|
-
}
|
|
1413
|
-
catch (error) {
|
|
1414
|
-
const message = formatApiError(error);
|
|
1415
|
-
logger.error(`promote_memory failed: ${message}`);
|
|
1416
|
-
return { success: false, error: message };
|
|
1417
|
-
}
|
|
480
|
+
return errors;
|
|
1418
481
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
482
|
+
function checkOpenClawVersion() {
|
|
483
|
+
return new Promise((resolve) => {
|
|
484
|
+
try {
|
|
485
|
+
const version = api.openclawVersion || api.version;
|
|
486
|
+
if (!version) {
|
|
487
|
+
logger.warn("Could not determine OpenClaw version");
|
|
488
|
+
resolve();
|
|
489
|
+
return;
|
|
1425
490
|
}
|
|
1426
|
-
|
|
1427
|
-
const
|
|
1428
|
-
return
|
|
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.`);
|
|
503
|
+
}
|
|
504
|
+
else if (currentNum >= maxNum) {
|
|
505
|
+
logger.warn(`OpenClaw version ${version} may not be fully compatible. Maximum tested version is ${MAX_OPENCLAW_VERSION}.`);
|
|
1429
506
|
}
|
|
1430
507
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1437
|
-
catch (error) {
|
|
1438
|
-
const message = formatApiError(error);
|
|
1439
|
-
logger.error(`delete_memory failed: ${message}`);
|
|
1440
|
-
return { success: false, error: message };
|
|
1441
|
-
}
|
|
508
|
+
catch (e) {
|
|
509
|
+
logger.warn(`Version check failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
510
|
+
}
|
|
511
|
+
resolve();
|
|
512
|
+
});
|
|
1442
513
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
try {
|
|
1448
|
-
await apiCallWithRetry(`/memory/${args.memory_id}`, "PATCH", {
|
|
1449
|
-
text: args.text,
|
|
1450
|
-
type: args.type,
|
|
1451
|
-
weight: args.weight,
|
|
1452
|
-
});
|
|
1453
|
-
return { success: true };
|
|
1454
|
-
}
|
|
1455
|
-
catch (error) {
|
|
1456
|
-
const message = formatApiError(error);
|
|
1457
|
-
logger.error(`update_memory failed: ${message}`);
|
|
1458
|
-
return { success: false, error: message };
|
|
514
|
+
function findOpenClawConfig() {
|
|
515
|
+
const explicitPath = (0, runtime_env_1.getEnvValue)("OPENCLAW_CONFIG_PATH");
|
|
516
|
+
if (explicitPath && fs.existsSync(explicitPath)) {
|
|
517
|
+
return explicitPath;
|
|
1459
518
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
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;
|
|
524
|
+
}
|
|
1464
525
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
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;
|
|
531
|
+
}
|
|
1471
532
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
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;
|
|
541
|
+
}
|
|
1476
542
|
}
|
|
543
|
+
return null;
|
|
1477
544
|
}
|
|
1478
|
-
|
|
1479
|
-
if (!isEnabled) {
|
|
1480
|
-
return {
|
|
1481
|
-
success: true,
|
|
1482
|
-
data: {
|
|
1483
|
-
status: "disabled",
|
|
1484
|
-
message: "Cortex Memory plugin is disabled",
|
|
1485
|
-
suggestion: "Enable the plugin using 'openclaw plugins enable cortex-memory'"
|
|
1486
|
-
}
|
|
1487
|
-
};
|
|
1488
|
-
}
|
|
545
|
+
function loadPluginEnabledState() {
|
|
1489
546
|
try {
|
|
1490
|
-
|
|
1491
|
-
|
|
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;
|
|
554
|
+
}
|
|
555
|
+
const allow = cfg?.plugins?.allow;
|
|
556
|
+
if (Array.isArray(allow)) {
|
|
557
|
+
return allow.includes(PLUGIN_ID);
|
|
558
|
+
}
|
|
559
|
+
return true;
|
|
1492
560
|
}
|
|
1493
|
-
catch
|
|
1494
|
-
|
|
1495
|
-
logger.error(`diagnostics failed: ${message}`);
|
|
1496
|
-
return { success: false, error: message };
|
|
561
|
+
catch {
|
|
562
|
+
return true;
|
|
1497
563
|
}
|
|
1498
564
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
data: {
|
|
1503
|
-
enabled: isEnabled,
|
|
1504
|
-
service_running: pythonProcess !== null,
|
|
1505
|
-
fallback_enabled: config?.fallbackToBuiltin ?? true,
|
|
1506
|
-
builtin_memory_available: builtinMemory !== null,
|
|
1507
|
-
engine_mode: config?.engineMode ?? "python",
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
async function onMessagePythonHandler(payload, context) {
|
|
1512
|
-
if (!isEnabled)
|
|
1513
|
-
return;
|
|
1514
|
-
const normalized = normalizeIncomingMessage(payload);
|
|
1515
|
-
if (!normalized)
|
|
1516
|
-
return;
|
|
1517
|
-
const { text, role, source } = normalized;
|
|
1518
|
-
const sessionId = resolveSessionId(context, payload);
|
|
1519
|
-
try {
|
|
1520
|
-
const writeResult = await apiCallWithRetry("/write", "POST", {
|
|
1521
|
-
text,
|
|
1522
|
-
source,
|
|
1523
|
-
role,
|
|
1524
|
-
session_id: sessionId
|
|
1525
|
-
});
|
|
1526
|
-
if (writeResult.status === "ok") {
|
|
1527
|
-
logger.info(`Stored ${role} message for session ${sessionId}`);
|
|
1528
|
-
}
|
|
1529
|
-
else {
|
|
1530
|
-
logger.debug(`Write skipped for session ${sessionId}: ${writeResult.reason || writeResult.status || "unknown"}`);
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
catch (error) {
|
|
1534
|
-
logger.warn(`Failed to store message: ${formatApiError(error)}`);
|
|
565
|
+
function startConfigWatcher() {
|
|
566
|
+
if (configWatchInterval) {
|
|
567
|
+
clearInterval(configWatchInterval);
|
|
1535
568
|
}
|
|
1536
|
-
|
|
569
|
+
configWatchInterval = setInterval(() => {
|
|
1537
570
|
try {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
+
}
|
|
1549
582
|
}
|
|
1550
583
|
}
|
|
1551
|
-
catch (
|
|
1552
|
-
logger.debug(`
|
|
584
|
+
catch (e) {
|
|
585
|
+
logger.debug(`Config watch error: ${e}`);
|
|
1553
586
|
}
|
|
587
|
+
}, 5000);
|
|
588
|
+
}
|
|
589
|
+
function stopConfigWatcher() {
|
|
590
|
+
if (configWatchInterval) {
|
|
591
|
+
clearInterval(configWatchInterval);
|
|
592
|
+
configWatchInterval = null;
|
|
1554
593
|
}
|
|
1555
594
|
}
|
|
1556
|
-
|
|
1557
|
-
if (!
|
|
595
|
+
function startAutoReflectScheduler() {
|
|
596
|
+
if (!config?.autoReflect) {
|
|
1558
597
|
return;
|
|
1559
|
-
const sessionId = resolveSessionId(context, payload);
|
|
1560
|
-
try {
|
|
1561
|
-
const endResult = await apiCallWithRetry("/session-end", "POST", {
|
|
1562
|
-
session_id: sessionId,
|
|
1563
|
-
sync_records: config?.autoSync ?? true,
|
|
1564
|
-
});
|
|
1565
|
-
logger.info(`Session ${sessionId} ended, generated ${endResult.events_generated} events`);
|
|
1566
598
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
599
|
+
const intervalMinutes = Math.max(5, config.autoReflectIntervalMinutes ?? 30);
|
|
600
|
+
if (autoReflectInterval) {
|
|
601
|
+
clearInterval(autoReflectInterval);
|
|
1569
602
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
}
|
|
1591
|
-
else if (action === "promote") {
|
|
1592
|
-
await apiCallWithRetry("/promote", "POST", undefined, {
|
|
1593
|
-
timeout: 120000,
|
|
1594
|
-
maxRetries: 2,
|
|
1595
|
-
});
|
|
1596
|
-
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
|
+
}
|
|
1597
623
|
}
|
|
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;
|
|
1598
631
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
}
|
|
632
|
+
}
|
|
633
|
+
function logLifecycle(event, data) {
|
|
634
|
+
logger.info(`[Lifecycle] ${event}${data ? `: ${JSON.stringify(sanitizeForLogging(data))}` : ""}`);
|
|
1602
635
|
}
|
|
1603
636
|
async function onMessageHandler(payload, context) {
|
|
1604
637
|
const sessionId = resolveSessionId(context, payload);
|
|
@@ -1858,30 +891,50 @@ function sanitizeToolParametersSchema(schema) {
|
|
|
1858
891
|
function registerToolCompat(tool) {
|
|
1859
892
|
if (!api)
|
|
1860
893
|
return;
|
|
1861
|
-
const
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
894
|
+
const normalizeContext = (value) => {
|
|
895
|
+
const contextObj = asRecord(value) || {};
|
|
896
|
+
return {
|
|
897
|
+
agentId: firstString([contextObj.agentId, contextObj.agent_id]) || "unknown-agent",
|
|
898
|
+
workspaceId: firstString([contextObj.workspaceId, contextObj.workspace_id]) || "default",
|
|
899
|
+
sessionId: firstString([contextObj.sessionId, contextObj.session_id]) || undefined,
|
|
900
|
+
};
|
|
901
|
+
};
|
|
902
|
+
const normalizeInvocation = (first, second) => {
|
|
903
|
+
const firstObj = asRecord(first);
|
|
904
|
+
if (firstObj && ("context" in firstObj || "args" in firstObj)) {
|
|
905
|
+
const explicitArgs = asRecord(firstObj.args);
|
|
906
|
+
if (explicitArgs) {
|
|
907
|
+
return {
|
|
908
|
+
args: explicitArgs,
|
|
909
|
+
context: normalizeContext(firstObj.context),
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
const directArgs = { ...firstObj };
|
|
913
|
+
delete directArgs.context;
|
|
914
|
+
delete directArgs.args;
|
|
915
|
+
return {
|
|
916
|
+
args: directArgs,
|
|
917
|
+
context: normalizeContext(firstObj.context),
|
|
918
|
+
};
|
|
1873
919
|
}
|
|
1874
|
-
return
|
|
1875
|
-
args:
|
|
1876
|
-
context: (second
|
|
920
|
+
return {
|
|
921
|
+
args: firstObj || {},
|
|
922
|
+
context: normalizeContext(second),
|
|
923
|
+
};
|
|
924
|
+
};
|
|
925
|
+
const invoke = async (...params) => {
|
|
926
|
+
const normalized = normalizeInvocation(params[0], params[1]);
|
|
927
|
+
return tool.execute({
|
|
928
|
+
args: normalized.args,
|
|
929
|
+
context: normalized.context,
|
|
1877
930
|
});
|
|
1878
931
|
};
|
|
1879
932
|
api.registerTool({
|
|
1880
933
|
name: tool.name,
|
|
1881
934
|
description: tool.description,
|
|
1882
935
|
parameters: sanitizeToolParametersSchema(tool.parameters),
|
|
1883
|
-
execute,
|
|
1884
|
-
handler,
|
|
936
|
+
execute: invoke,
|
|
937
|
+
handler: invoke,
|
|
1885
938
|
});
|
|
1886
939
|
}
|
|
1887
940
|
function unregisterTools() {
|
|
@@ -1897,6 +950,94 @@ function unregisterTools() {
|
|
|
1897
950
|
}
|
|
1898
951
|
registeredTools = [];
|
|
1899
952
|
}
|
|
953
|
+
function registerFallbackTools() {
|
|
954
|
+
if (!api || !builtinMemory)
|
|
955
|
+
return;
|
|
956
|
+
registerToolCompat({
|
|
957
|
+
name: "search_memory",
|
|
958
|
+
description: "Search memory (using builtin system - Cortex Memory disabled)",
|
|
959
|
+
parameters: {
|
|
960
|
+
type: "object",
|
|
961
|
+
properties: {
|
|
962
|
+
query: { type: "string", description: "Search query" },
|
|
963
|
+
top_k: { type: "integer", description: "Number of results" },
|
|
964
|
+
},
|
|
965
|
+
required: ["query"],
|
|
966
|
+
additionalProperties: false,
|
|
967
|
+
},
|
|
968
|
+
execute: async ({ args, context }) => {
|
|
969
|
+
try {
|
|
970
|
+
const query = args?.query || "";
|
|
971
|
+
const topK = args?.top_k || 5;
|
|
972
|
+
const results = await builtinMemory.search(query, topK);
|
|
973
|
+
return { success: true, data: results };
|
|
974
|
+
}
|
|
975
|
+
catch (error) {
|
|
976
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
977
|
+
return { success: false, error: `Builtin memory error: ${message}` };
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
registeredFallbackTools.push("search_memory");
|
|
982
|
+
registerToolCompat({
|
|
983
|
+
name: "store_event",
|
|
984
|
+
description: "Store event (using builtin system - Cortex Memory disabled)",
|
|
985
|
+
parameters: {
|
|
986
|
+
type: "object",
|
|
987
|
+
properties: {
|
|
988
|
+
summary: { type: "string", description: "Event summary" },
|
|
989
|
+
},
|
|
990
|
+
required: ["summary"],
|
|
991
|
+
additionalProperties: false,
|
|
992
|
+
},
|
|
993
|
+
execute: async ({ args, context }) => {
|
|
994
|
+
try {
|
|
995
|
+
const summary = args?.summary || "";
|
|
996
|
+
const id = await builtinMemory.store(summary);
|
|
997
|
+
return { success: true, data: { id } };
|
|
998
|
+
}
|
|
999
|
+
catch (error) {
|
|
1000
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1001
|
+
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1002
|
+
}
|
|
1003
|
+
},
|
|
1004
|
+
});
|
|
1005
|
+
registeredFallbackTools.push("store_event");
|
|
1006
|
+
registerToolCompat({
|
|
1007
|
+
name: "cortex_memory_status",
|
|
1008
|
+
description: "Get the current status of the Cortex Memory plugin",
|
|
1009
|
+
parameters: {
|
|
1010
|
+
type: "object",
|
|
1011
|
+
properties: {},
|
|
1012
|
+
required: [],
|
|
1013
|
+
additionalProperties: false,
|
|
1014
|
+
},
|
|
1015
|
+
execute: async (_params) => {
|
|
1016
|
+
return {
|
|
1017
|
+
success: true,
|
|
1018
|
+
data: {
|
|
1019
|
+
enabled: isEnabled,
|
|
1020
|
+
fallback_enabled: config?.fallbackToBuiltin ?? true,
|
|
1021
|
+
builtin_memory_available: builtinMemory !== null,
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
registeredFallbackTools.push("cortex_memory_status");
|
|
1027
|
+
}
|
|
1028
|
+
function unregisterFallbackTools() {
|
|
1029
|
+
if (!api || !api.unregisterTool)
|
|
1030
|
+
return;
|
|
1031
|
+
for (const name of registeredFallbackTools) {
|
|
1032
|
+
try {
|
|
1033
|
+
api.unregisterTool(name);
|
|
1034
|
+
}
|
|
1035
|
+
catch (e) {
|
|
1036
|
+
logger.warn(`Failed to unregister fallback tool ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
registeredFallbackTools = [];
|
|
1040
|
+
}
|
|
1900
1041
|
function registerHooks() {
|
|
1901
1042
|
if (!api)
|
|
1902
1043
|
return;
|
|
@@ -1957,27 +1098,15 @@ function setupProcessHandlers() {
|
|
|
1957
1098
|
isShuttingDown = true;
|
|
1958
1099
|
logger.info(`Received ${signal}, shutting down...`);
|
|
1959
1100
|
stopConfigWatcher();
|
|
1960
|
-
|
|
1961
|
-
process.exit(0);
|
|
1962
|
-
return;
|
|
1963
|
-
}
|
|
1964
|
-
shutdownPythonApi().then(() => {
|
|
1965
|
-
killPythonProcess();
|
|
1966
|
-
process.exit(0);
|
|
1967
|
-
}).catch(() => {
|
|
1968
|
-
killPythonProcess();
|
|
1969
|
-
process.exit(0);
|
|
1970
|
-
});
|
|
1101
|
+
process.exit(0);
|
|
1971
1102
|
};
|
|
1972
1103
|
process.on("exit", () => {
|
|
1973
|
-
killPythonProcess();
|
|
1974
1104
|
stopConfigWatcher();
|
|
1975
1105
|
});
|
|
1976
1106
|
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
1977
1107
|
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
1978
1108
|
process.on("uncaughtException", (err) => {
|
|
1979
1109
|
logger.error("Uncaught exception:", err.message);
|
|
1980
|
-
killPythonProcess();
|
|
1981
1110
|
stopConfigWatcher();
|
|
1982
1111
|
process.exit(1);
|
|
1983
1112
|
});
|
|
@@ -1991,10 +1120,6 @@ async function enable() {
|
|
|
1991
1120
|
logLifecycle("enable_start");
|
|
1992
1121
|
try {
|
|
1993
1122
|
unregisterFallbackTools();
|
|
1994
|
-
if (shouldUsePythonRuntime()) {
|
|
1995
|
-
await startPythonService();
|
|
1996
|
-
await waitForService();
|
|
1997
|
-
}
|
|
1998
1123
|
isEnabled = true;
|
|
1999
1124
|
registerTools();
|
|
2000
1125
|
registerHooks();
|
|
@@ -2018,11 +1143,7 @@ async function disable() {
|
|
|
2018
1143
|
logLifecycle("disable_start");
|
|
2019
1144
|
unregisterHooks();
|
|
2020
1145
|
unregisterTools();
|
|
2021
|
-
unregisterFallbackTools();
|
|
2022
1146
|
stopAutoReflectScheduler();
|
|
2023
|
-
if (shouldUsePythonRuntime()) {
|
|
2024
|
-
await stopPythonServiceAsync();
|
|
2025
|
-
}
|
|
2026
1147
|
isEnabled = false;
|
|
2027
1148
|
memoryEngine = null;
|
|
2028
1149
|
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
@@ -2033,68 +1154,9 @@ async function disable() {
|
|
|
2033
1154
|
logger.info("Cortex Memory plugin disabled successfully");
|
|
2034
1155
|
logLifecycle("disable_success", { fallbackEnabled: registeredFallbackTools.length > 0 });
|
|
2035
1156
|
}
|
|
2036
|
-
function registerFallbackTools() {
|
|
2037
|
-
if (!api || !builtinMemory)
|
|
2038
|
-
return;
|
|
2039
|
-
registerToolCompat({
|
|
2040
|
-
name: "search_memory",
|
|
2041
|
-
description: "Search memory (using builtin system - Cortex Memory disabled)",
|
|
2042
|
-
parameters: {
|
|
2043
|
-
type: "object",
|
|
2044
|
-
properties: {
|
|
2045
|
-
query: { type: "string", description: "Search query" },
|
|
2046
|
-
top_k: { type: "integer", description: "Number of results" },
|
|
2047
|
-
},
|
|
2048
|
-
required: ["query"],
|
|
2049
|
-
additionalProperties: false,
|
|
2050
|
-
},
|
|
2051
|
-
execute: async ({ args, context }) => searchMemoryWithFallback((args || {}), context),
|
|
2052
|
-
});
|
|
2053
|
-
registeredFallbackTools.push("search_memory");
|
|
2054
|
-
registerToolCompat({
|
|
2055
|
-
name: "store_event",
|
|
2056
|
-
description: "Store event (using builtin system - Cortex Memory disabled)",
|
|
2057
|
-
parameters: {
|
|
2058
|
-
type: "object",
|
|
2059
|
-
properties: {
|
|
2060
|
-
summary: { type: "string", description: "Event summary" },
|
|
2061
|
-
},
|
|
2062
|
-
required: ["summary"],
|
|
2063
|
-
additionalProperties: false,
|
|
2064
|
-
},
|
|
2065
|
-
execute: async ({ args, context }) => storeEventWithFallback((args || {}), context),
|
|
2066
|
-
});
|
|
2067
|
-
registeredFallbackTools.push("store_event");
|
|
2068
|
-
registerToolCompat({
|
|
2069
|
-
name: "cortex_memory_status",
|
|
2070
|
-
description: "Get the current status of the Cortex Memory plugin",
|
|
2071
|
-
parameters: {
|
|
2072
|
-
type: "object",
|
|
2073
|
-
properties: {},
|
|
2074
|
-
required: [],
|
|
2075
|
-
additionalProperties: false,
|
|
2076
|
-
},
|
|
2077
|
-
execute: async ({ args, context }) => getPluginStatus(args || {}, context),
|
|
2078
|
-
});
|
|
2079
|
-
registeredFallbackTools.push("cortex_memory_status");
|
|
2080
|
-
}
|
|
2081
|
-
function unregisterFallbackTools() {
|
|
2082
|
-
if (!api || !api.unregisterTool)
|
|
2083
|
-
return;
|
|
2084
|
-
for (const name of registeredFallbackTools) {
|
|
2085
|
-
try {
|
|
2086
|
-
api.unregisterTool(name);
|
|
2087
|
-
}
|
|
2088
|
-
catch (e) {
|
|
2089
|
-
logger.warn(`Failed to unregister fallback tool ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
registeredFallbackTools = [];
|
|
2093
|
-
}
|
|
2094
1157
|
function getStatus() {
|
|
2095
1158
|
return {
|
|
2096
|
-
enabled: isEnabled
|
|
2097
|
-
serviceRunning: pythonProcess !== null
|
|
1159
|
+
enabled: isEnabled
|
|
2098
1160
|
};
|
|
2099
1161
|
}
|
|
2100
1162
|
async function unregister() {
|
|
@@ -2105,25 +1167,18 @@ async function unregister() {
|
|
|
2105
1167
|
unregisterHooks();
|
|
2106
1168
|
unregisterTools();
|
|
2107
1169
|
unregisterFallbackTools();
|
|
2108
|
-
if (shouldUsePythonRuntime()) {
|
|
2109
|
-
await stopPythonServiceAsync();
|
|
2110
|
-
}
|
|
2111
|
-
else {
|
|
2112
|
-
killPythonProcess();
|
|
2113
|
-
}
|
|
2114
1170
|
isEnabled = false;
|
|
2115
1171
|
isInitializing = false;
|
|
2116
1172
|
isRegistered = false;
|
|
2117
1173
|
api = null;
|
|
2118
1174
|
config = null;
|
|
2119
1175
|
autoSearchCacheBySession.clear();
|
|
2120
|
-
builtinMemory = null;
|
|
2121
1176
|
memoryEngine = null;
|
|
1177
|
+
builtinMemory = null;
|
|
2122
1178
|
registeredTools = [];
|
|
2123
1179
|
registeredHooks = [];
|
|
2124
1180
|
registeredFallbackTools = [];
|
|
2125
1181
|
registeredHookHandlers.clear();
|
|
2126
|
-
stopAutoReflectScheduler();
|
|
2127
1182
|
configPath = null;
|
|
2128
1183
|
logger.info("Cortex Memory plugin unregistered successfully");
|
|
2129
1184
|
logLifecycle("unregister_success");
|
|
@@ -2195,9 +1250,7 @@ function register(pluginApi, userConfig) {
|
|
|
2195
1250
|
},
|
|
2196
1251
|
},
|
|
2197
1252
|
enabled: effectiveConfig.enabled ?? defaultConfig.enabled,
|
|
2198
|
-
fallbackToBuiltin: effectiveConfig.fallbackToBuiltin ??
|
|
2199
|
-
apiUrl: effectiveConfig.apiUrl ?? "http://localhost:8765",
|
|
2200
|
-
engineMode: "ts",
|
|
1253
|
+
fallbackToBuiltin: effectiveConfig.fallbackToBuiltin ?? true,
|
|
2201
1254
|
};
|
|
2202
1255
|
memoryEngine = null;
|
|
2203
1256
|
if (api.getBuiltinMemory) {
|
|
@@ -2216,7 +1269,6 @@ function register(pluginApi, userConfig) {
|
|
|
2216
1269
|
reranker: { model: config.reranker.model },
|
|
2217
1270
|
enabled: config.enabled,
|
|
2218
1271
|
fallbackToBuiltin: config.fallbackToBuiltin,
|
|
2219
|
-
engineMode: config.engineMode,
|
|
2220
1272
|
});
|
|
2221
1273
|
checkOpenClawVersion().catch(e => logger.warn(`Version check failed: ${e}`));
|
|
2222
1274
|
configPath = findOpenClawConfig();
|
|
@@ -2232,38 +1284,12 @@ function register(pluginApi, userConfig) {
|
|
|
2232
1284
|
logger.info("Cortex Memory plugin registered successfully");
|
|
2233
1285
|
logger.info(`Cortex Memory engine mode: ${resolveEngine().mode}`);
|
|
2234
1286
|
logLifecycle("register_success", {
|
|
2235
|
-
engineMode: config.engineMode,
|
|
2236
1287
|
enabled: isEnabled,
|
|
2237
|
-
hasBuiltinFallback: Boolean(builtinMemory),
|
|
2238
1288
|
});
|
|
2239
1289
|
if (isEnabled) {
|
|
2240
1290
|
registerTools();
|
|
2241
1291
|
registerHooks();
|
|
2242
1292
|
startAutoReflectScheduler();
|
|
2243
|
-
initializeAsync().catch(error => {
|
|
2244
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2245
|
-
logger.error(`Failed to initialize Cortex Memory: ${message}`);
|
|
2246
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
2247
|
-
unregisterHooks();
|
|
2248
|
-
unregisterTools();
|
|
2249
|
-
logger.info("Falling back to builtin memory");
|
|
2250
|
-
isEnabled = false;
|
|
2251
|
-
registerFallbackTools();
|
|
2252
|
-
logLifecycle("fallback_after_init_error", { fallbackTools: registeredFallbackTools.length, error: message });
|
|
2253
|
-
}
|
|
2254
|
-
});
|
|
2255
|
-
}
|
|
2256
|
-
else if (config?.fallbackToBuiltin && builtinMemory) {
|
|
2257
|
-
registerFallbackTools();
|
|
2258
|
-
logLifecycle("fallback_registered_on_start", { fallbackTools: registeredFallbackTools.length });
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
async function initializeAsync() {
|
|
2262
|
-
if (!shouldUsePythonRuntime()) {
|
|
2263
|
-
return;
|
|
2264
1293
|
}
|
|
2265
|
-
await startPythonService();
|
|
2266
|
-
await waitForService();
|
|
2267
|
-
logger.info("Cortex Memory Python service started successfully");
|
|
2268
1294
|
}
|
|
2269
1295
|
//# sourceMappingURL=index.js.map
|