openclaw-cortex-memory 0.1.0-Alpha.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +347 -290
- package/SIGNATURE.md +7 -0
- package/SKILL.md +96 -345
- package/dist/index.d.ts +69 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1130 -1330
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +377 -18
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -1
- package/dist/src/dedup/three_stage_deduplicator.js +13 -3
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -1
- package/dist/src/engine/memory_engine.d.ts +5 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +149 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +863 -203
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +20 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +87 -15
- package/dist/src/graph/ontology.d.ts.map +1 -1
- package/dist/src/graph/ontology.js +999 -12
- package/dist/src/graph/ontology.js.map +1 -1
- package/dist/src/net/http_post.d.ts +17 -0
- package/dist/src/net/http_post.d.ts.map +1 -0
- package/dist/src/net/http_post.js +56 -0
- package/dist/src/net/http_post.js.map +1 -0
- package/dist/src/quality/llm_output_validator.d.ts +65 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +635 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +296 -26
- package/dist/src/reflect/reflector.js.map +1 -1
- package/dist/src/rules/rule_store.d.ts.map +1 -1
- package/dist/src/rules/rule_store.js +75 -16
- package/dist/src/rules/rule_store.js.map +1 -1
- package/dist/src/session/session_end.d.ts +20 -42
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +21 -218
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +28 -7
- package/dist/src/store/archive_store.d.ts.map +1 -1
- package/dist/src/store/archive_store.js +367 -130
- package/dist/src/store/archive_store.js.map +1 -1
- package/dist/src/store/graph_memory_store.d.ts +115 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +1061 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +75 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1837 -312
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +2 -0
- package/dist/src/store/vector_store.d.ts.map +1 -1
- package/dist/src/store/vector_store.js +19 -3
- package/dist/src/store/vector_store.js.map +1 -1
- package/dist/src/store/write_store.d.ts +11 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +242 -42
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +72 -1
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +2246 -126
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/wiki/wiki_linter.d.ts +26 -0
- package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
- package/dist/src/wiki/wiki_linter.js +339 -0
- package/dist/src/wiki/wiki_linter.js.map +1 -0
- package/dist/src/wiki/wiki_logger.d.ts +10 -0
- package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
- package/dist/src/wiki/wiki_logger.js +78 -0
- package/dist/src/wiki/wiki_logger.js.map +1 -0
- package/dist/src/wiki/wiki_maintainer.d.ts +39 -0
- package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
- package/dist/src/wiki/wiki_maintainer.js +38 -0
- package/dist/src/wiki/wiki_maintainer.js.map +1 -0
- package/dist/src/wiki/wiki_projector.d.ts +35 -0
- package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
- package/dist/src/wiki/wiki_projector.js +1151 -0
- package/dist/src/wiki/wiki_projector.js.map +1 -0
- package/dist/src/wiki/wiki_queue.d.ts +29 -0
- package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
- package/dist/src/wiki/wiki_queue.js +137 -0
- package/dist/src/wiki/wiki_queue.js.map +1 -0
- package/openclaw.plugin.json +377 -18
- package/package.json +52 -6
- package/schema/graph.schema.yaml +330 -0
- package/scripts/cli.js +67 -13
- package/scripts/repair-memory.js +321 -0
- package/skills/cortex-memory/SKILL.md +83 -0
- package/skills/cortex-memory/references/agent-manual.md +127 -0
- package/skills/cortex-memory/references/configuration.md +109 -0
- package/skills/cortex-memory/references/publish-checklist.md +45 -0
- package/skills/cortex-memory/references/system-prompt-template.md +27 -0
- package/skills/cortex-memory/references/tools.md +191 -0
package/dist/index.js
CHANGED
|
@@ -39,15 +39,14 @@ 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");
|
|
49
47
|
const archive_store_1 = require("./src/store/archive_store");
|
|
50
48
|
const vector_store_1 = require("./src/store/vector_store");
|
|
49
|
+
const graph_memory_store_1 = require("./src/store/graph_memory_store");
|
|
51
50
|
const session_sync_1 = require("./src/sync/session_sync");
|
|
52
51
|
const session_end_1 = require("./src/session/session_end");
|
|
53
52
|
const rule_store_1 = require("./src/rules/rule_store");
|
|
@@ -55,16 +54,6 @@ const reflector_1 = require("./src/reflect/reflector");
|
|
|
55
54
|
const three_stage_deduplicator_1 = require("./src/dedup/three_stage_deduplicator");
|
|
56
55
|
const runtime_env_1 = require("./src/utils/runtime_env");
|
|
57
56
|
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
57
|
NOT_FOUND: {
|
|
69
58
|
code: "E003",
|
|
70
59
|
message: "Memory not found",
|
|
@@ -75,25 +64,41 @@ const ERROR_CODES = {
|
|
|
75
64
|
message: "Invalid input provided",
|
|
76
65
|
suggestion: "Please check your input parameters and try again."
|
|
77
66
|
},
|
|
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
67
|
PLUGIN_DISABLED: {
|
|
84
68
|
code: "E006",
|
|
85
69
|
message: "Cortex Memory plugin is disabled",
|
|
86
|
-
suggestion: "Enable the plugin using 'openclaw plugins enable cortex-memory' or check openclaw.json"
|
|
70
|
+
suggestion: "Enable the plugin using 'openclaw plugins enable openclaw-cortex-memory' or check openclaw.json"
|
|
87
71
|
}
|
|
88
72
|
};
|
|
89
73
|
const SENSITIVE_KEYS = ["API_KEY", "SECRET", "TOKEN", "PASSWORD", "APIKEY"];
|
|
90
74
|
const PLUGIN_ID = "openclaw-cortex-memory";
|
|
91
|
-
const
|
|
92
|
-
const
|
|
75
|
+
const TOOL_NAME_CORTEX_DIAGNOSTICS = "cortex_diagnostics";
|
|
76
|
+
const TOOL_NAME_DIAGNOSTICS_LEGACY = "diagnostics";
|
|
77
|
+
const definePluginEntryCompat = (() => {
|
|
78
|
+
try {
|
|
79
|
+
const sdk = require("openclaw/plugin-sdk/plugin-entry");
|
|
80
|
+
if (sdk && typeof sdk.definePluginEntry === "function") {
|
|
81
|
+
return sdk.definePluginEntry;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Fallback keeps compatibility with hosts that only support legacy exports.
|
|
86
|
+
}
|
|
87
|
+
return (entry) => entry;
|
|
88
|
+
})();
|
|
89
|
+
const MIN_OPENCLAW_GATEWAY_VERSION = "2026.4.5";
|
|
90
|
+
const MAX_OPENCLAW_GATEWAY_VERSION = "2027.0.0";
|
|
93
91
|
const defaultConfig = {
|
|
94
92
|
autoSync: true,
|
|
93
|
+
llmRequiredForWrite: true,
|
|
95
94
|
autoReflect: false,
|
|
96
95
|
autoReflectIntervalMinutes: 30,
|
|
96
|
+
graphQualityMode: "warn",
|
|
97
|
+
wikiProjection: {
|
|
98
|
+
enabled: true,
|
|
99
|
+
mode: "incremental",
|
|
100
|
+
maxBatch: 100,
|
|
101
|
+
},
|
|
97
102
|
readFusion: {
|
|
98
103
|
enabled: true,
|
|
99
104
|
maxCandidates: 10,
|
|
@@ -122,6 +127,19 @@ const defaultConfig = {
|
|
|
122
127
|
vectorChunking: {
|
|
123
128
|
chunkSize: 600,
|
|
124
129
|
chunkOverlap: 100,
|
|
130
|
+
evidenceMaxChunks: 2,
|
|
131
|
+
},
|
|
132
|
+
writePolicy: {
|
|
133
|
+
archiveMinConfidence: 0.35,
|
|
134
|
+
archiveMinQualityScore: 0.4,
|
|
135
|
+
activeMinQualityScore: 0.45,
|
|
136
|
+
activeDedupTailLines: 200,
|
|
137
|
+
activeTextMaxChars: 200000,
|
|
138
|
+
archiveSourceTextMaxChars: 500000,
|
|
139
|
+
},
|
|
140
|
+
syncPolicy: {
|
|
141
|
+
includeLocalActiveInput: false,
|
|
142
|
+
graphQualityMode: "strict",
|
|
125
143
|
},
|
|
126
144
|
memoryDecay: {
|
|
127
145
|
enabled: true,
|
|
@@ -151,23 +169,50 @@ const defaultConfig = {
|
|
|
151
169
|
assumption: 240,
|
|
152
170
|
},
|
|
153
171
|
},
|
|
172
|
+
readTuning: {
|
|
173
|
+
scoring: {
|
|
174
|
+
lexicalWeight: 0.2,
|
|
175
|
+
bm25Scale: 2,
|
|
176
|
+
semanticWeight: 0.3,
|
|
177
|
+
recencyWeight: 0.1,
|
|
178
|
+
qualityWeight: 0.15,
|
|
179
|
+
typeMatchWeight: 0.15,
|
|
180
|
+
graphMatchWeight: 0.1,
|
|
181
|
+
},
|
|
182
|
+
rrf: {
|
|
183
|
+
k: 60,
|
|
184
|
+
weight: 1.5,
|
|
185
|
+
},
|
|
186
|
+
recency: {
|
|
187
|
+
buckets: [
|
|
188
|
+
{ maxAgeHours: 12, score: 1, bonus: 0.6 },
|
|
189
|
+
{ maxAgeHours: 24, score: 0.8, bonus: 0.6 },
|
|
190
|
+
{ maxAgeHours: 72, score: 0.6, bonus: 0.3 },
|
|
191
|
+
{ maxAgeHours: 168, score: 0.4, bonus: 0.3 },
|
|
192
|
+
{ maxAgeHours: 720, score: 0.2, bonus: 0 },
|
|
193
|
+
{ maxAgeHours: Number.POSITIVE_INFINITY, score: 0.05, bonus: 0 },
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
autoContext: {
|
|
197
|
+
queryMaxChars: 240,
|
|
198
|
+
lightweightSearch: true,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
154
201
|
enabled: true,
|
|
155
|
-
fallbackToBuiltin: true,
|
|
156
|
-
engineMode: "ts",
|
|
157
202
|
};
|
|
158
203
|
let autoSearchCacheBySession = new Map();
|
|
204
|
+
const messageHookInFlightBySession = new Map();
|
|
159
205
|
const AUTO_SEARCH_CACHE_TTL = 60000;
|
|
160
206
|
const MAX_AUTO_SEARCH_CACHE_SESSIONS = 200;
|
|
161
207
|
const HOOK_GUARD_TIMEOUT_MS = 2000;
|
|
208
|
+
const SYNC_DEBOUNCE_WINDOW_MS = 120000;
|
|
162
209
|
let config = null;
|
|
163
210
|
let logger;
|
|
164
|
-
let pythonProcess = null;
|
|
165
211
|
let isShuttingDown = false;
|
|
166
212
|
let isInitializing = false;
|
|
167
213
|
let isRegistered = false;
|
|
168
214
|
let isEnabled = false;
|
|
169
215
|
let api = null;
|
|
170
|
-
let builtinMemory = null;
|
|
171
216
|
let registeredTools = [];
|
|
172
217
|
let registeredHooks = [];
|
|
173
218
|
let registeredFallbackTools = [];
|
|
@@ -177,16 +222,44 @@ let autoReflectInterval = null;
|
|
|
177
222
|
let lastAutoReflectArchiveMarker = "";
|
|
178
223
|
let lastAutoReflectRunAt = 0;
|
|
179
224
|
let configPath = null;
|
|
180
|
-
let pythonStartPromise = null;
|
|
181
225
|
let processHandlersRegistered = false;
|
|
182
|
-
let pythonPidFilePath = null;
|
|
183
226
|
let memoryEngine = null;
|
|
184
|
-
|
|
185
|
-
|
|
227
|
+
let builtinMemory = null;
|
|
228
|
+
const TOOL_TRACE_PAYLOAD_MAX_CHARS = 1500;
|
|
229
|
+
function inferOpenClawBasePathForWorkspace() {
|
|
230
|
+
const explicitConfigPath = (0, runtime_env_1.getEnvValue)("OPENCLAW_CONFIG_PATH").trim();
|
|
231
|
+
if (explicitConfigPath) {
|
|
232
|
+
return path.dirname(path.resolve(explicitConfigPath));
|
|
233
|
+
}
|
|
234
|
+
const stateDir = (0, runtime_env_1.getEnvValue)("OPENCLAW_STATE_DIR").trim();
|
|
235
|
+
if (stateDir) {
|
|
236
|
+
return path.resolve(stateDir);
|
|
237
|
+
}
|
|
238
|
+
const basePath = (0, runtime_env_1.getEnvValue)("OPENCLAW_BASE_PATH").trim();
|
|
239
|
+
if (basePath) {
|
|
240
|
+
return path.resolve(basePath);
|
|
241
|
+
}
|
|
242
|
+
const discoveredConfigPath = findOpenClawConfig();
|
|
243
|
+
if (discoveredConfigPath) {
|
|
244
|
+
return path.dirname(path.resolve(discoveredConfigPath));
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
function resolveDefaultMemoryRoot(projectRoot) {
|
|
249
|
+
const openClawBasePath = inferOpenClawBasePathForWorkspace();
|
|
250
|
+
if (openClawBasePath) {
|
|
251
|
+
return path.join(openClawBasePath, "workspace", "memory", PLUGIN_ID);
|
|
252
|
+
}
|
|
253
|
+
return path.join(projectRoot, "data", "memory");
|
|
254
|
+
}
|
|
255
|
+
function resolveConfiguredMemoryRoot(configuredDbPath) {
|
|
256
|
+
if (typeof configuredDbPath === "string" && configuredDbPath.trim()) {
|
|
257
|
+
return path.resolve(configuredDbPath.trim());
|
|
258
|
+
}
|
|
259
|
+
return resolveDefaultMemoryRoot(findProjectRoot());
|
|
186
260
|
}
|
|
187
261
|
function getMemoryRoot() {
|
|
188
|
-
|
|
189
|
-
return config?.dbPath ? path.resolve(config.dbPath) : path.join(projectRoot, "data", "memory");
|
|
262
|
+
return resolveConfiguredMemoryRoot(config?.dbPath);
|
|
190
263
|
}
|
|
191
264
|
function getArchiveMarker() {
|
|
192
265
|
try {
|
|
@@ -244,8 +317,7 @@ function resolveEngine() {
|
|
|
244
317
|
if (!config) {
|
|
245
318
|
throw new Error("Configuration not loaded");
|
|
246
319
|
}
|
|
247
|
-
|
|
248
|
-
if (memoryEngine && memoryEngine.mode === "ts") {
|
|
320
|
+
if (memoryEngine) {
|
|
249
321
|
return memoryEngine;
|
|
250
322
|
}
|
|
251
323
|
const projectRoot = findProjectRoot();
|
|
@@ -259,6 +331,7 @@ function resolveEngine() {
|
|
|
259
331
|
llm: config.llm,
|
|
260
332
|
fusion: config.readFusion,
|
|
261
333
|
memoryDecay: config.memoryDecay,
|
|
334
|
+
readTuning: config.readTuning,
|
|
262
335
|
});
|
|
263
336
|
const vectorStore = (0, vector_store_1.createVectorStore)({
|
|
264
337
|
memoryRoot,
|
|
@@ -270,6 +343,7 @@ function resolveEngine() {
|
|
|
270
343
|
logger,
|
|
271
344
|
embedding: config.embedding,
|
|
272
345
|
vectorChunking: config.vectorChunking,
|
|
346
|
+
writePolicy: config.writePolicy,
|
|
273
347
|
vectorStore,
|
|
274
348
|
});
|
|
275
349
|
const deduplicator = (0, three_stage_deduplicator_1.createThreeStageDeduplicator)({
|
|
@@ -282,25 +356,60 @@ function resolveEngine() {
|
|
|
282
356
|
logger,
|
|
283
357
|
embedding: config.embedding,
|
|
284
358
|
vectorChunking: config.vectorChunking,
|
|
359
|
+
writePolicy: config.writePolicy,
|
|
285
360
|
deduplicator,
|
|
286
361
|
vectorStore,
|
|
287
362
|
});
|
|
363
|
+
const graphMemoryStore = (0, graph_memory_store_1.createGraphMemoryStore)({
|
|
364
|
+
projectRoot,
|
|
365
|
+
memoryRoot,
|
|
366
|
+
logger,
|
|
367
|
+
qualityMode: config.graphQualityMode || "warn",
|
|
368
|
+
wikiProjection: config.wikiProjection,
|
|
369
|
+
});
|
|
288
370
|
const sessionSync = (0, session_sync_1.createSessionSync)({
|
|
289
371
|
projectRoot,
|
|
290
372
|
dbPath: config.dbPath,
|
|
291
373
|
logger,
|
|
292
374
|
llm: config.llm,
|
|
375
|
+
graphQualityMode: config.graphQualityMode || "warn",
|
|
376
|
+
requireLlmForWrite: config.llmRequiredForWrite ?? true,
|
|
377
|
+
writePolicy: config.writePolicy,
|
|
378
|
+
syncPolicy: config.syncPolicy,
|
|
293
379
|
archiveStore,
|
|
380
|
+
graphMemoryStore,
|
|
294
381
|
writeStore,
|
|
295
382
|
});
|
|
383
|
+
let syncInFlight = null;
|
|
384
|
+
let lastSyncFinishedAt = 0;
|
|
385
|
+
const dedupedSyncMemory = async () => {
|
|
386
|
+
const now = Date.now();
|
|
387
|
+
if (syncInFlight) {
|
|
388
|
+
logger.info("sync_memory dedup: join in-flight run");
|
|
389
|
+
return syncInFlight;
|
|
390
|
+
}
|
|
391
|
+
if ((now - lastSyncFinishedAt) < SYNC_DEBOUNCE_WINDOW_MS) {
|
|
392
|
+
const waitMs = SYNC_DEBOUNCE_WINDOW_MS - (now - lastSyncFinishedAt);
|
|
393
|
+
logger.info(`sync_memory dedup: skip due to debounce window (${waitMs}ms remaining)`);
|
|
394
|
+
return { imported: 0, skipped: 0, filesProcessed: 0 };
|
|
395
|
+
}
|
|
396
|
+
syncInFlight = sessionSync.syncMemory();
|
|
397
|
+
try {
|
|
398
|
+
const result = await syncInFlight;
|
|
399
|
+
lastSyncFinishedAt = Date.now();
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
syncInFlight = null;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
296
406
|
const sessionEnd = (0, session_end_1.createSessionEnd)({
|
|
297
407
|
projectRoot,
|
|
298
408
|
dbPath: config.dbPath,
|
|
299
409
|
logger,
|
|
300
|
-
syncMemory:
|
|
410
|
+
syncMemory: dedupedSyncMemory,
|
|
301
411
|
syncDailySummaries: sessionSync.syncDailySummaries,
|
|
302
|
-
|
|
303
|
-
llm: config.llm,
|
|
412
|
+
routeTranscript: sessionSync.routeTranscript,
|
|
304
413
|
});
|
|
305
414
|
const ruleStore = (0, rule_store_1.createRuleStore)({
|
|
306
415
|
projectRoot,
|
|
@@ -314,12 +423,17 @@ function resolveEngine() {
|
|
|
314
423
|
ruleStore,
|
|
315
424
|
llm: config.llm,
|
|
316
425
|
});
|
|
426
|
+
const sessionSyncBridge = {
|
|
427
|
+
...sessionSync,
|
|
428
|
+
syncMemory: dedupedSyncMemory,
|
|
429
|
+
};
|
|
317
430
|
memoryEngine = (0, ts_engine_1.createTsEngine)({
|
|
318
431
|
readStore,
|
|
319
432
|
writeStore,
|
|
320
433
|
vectorStore,
|
|
321
434
|
archiveStore,
|
|
322
|
-
|
|
435
|
+
graphMemoryStore,
|
|
436
|
+
sessionSync: sessionSyncBridge,
|
|
323
437
|
sessionEnd,
|
|
324
438
|
reflector,
|
|
325
439
|
memoryRoot,
|
|
@@ -338,6 +452,84 @@ function resolveEngine() {
|
|
|
338
452
|
});
|
|
339
453
|
return memoryEngine;
|
|
340
454
|
}
|
|
455
|
+
function normalizeToolNameList(input) {
|
|
456
|
+
if (!input)
|
|
457
|
+
return [];
|
|
458
|
+
if (Array.isArray(input)) {
|
|
459
|
+
const names = input
|
|
460
|
+
.map((item) => {
|
|
461
|
+
if (typeof item === "string")
|
|
462
|
+
return item.trim();
|
|
463
|
+
if (item && typeof item === "object") {
|
|
464
|
+
const name = firstString([item.name]);
|
|
465
|
+
return name || "";
|
|
466
|
+
}
|
|
467
|
+
return "";
|
|
468
|
+
})
|
|
469
|
+
.filter(Boolean);
|
|
470
|
+
return [...new Set(names)].sort();
|
|
471
|
+
}
|
|
472
|
+
if (typeof input === "object") {
|
|
473
|
+
return Object.keys(input).sort();
|
|
474
|
+
}
|
|
475
|
+
return [];
|
|
476
|
+
}
|
|
477
|
+
async function getApiVisibleToolNames() {
|
|
478
|
+
if (!api)
|
|
479
|
+
return [];
|
|
480
|
+
const apiObj = api;
|
|
481
|
+
const readers = [];
|
|
482
|
+
if (typeof apiObj.listTools === "function") {
|
|
483
|
+
readers.push(() => apiObj.listTools());
|
|
484
|
+
}
|
|
485
|
+
if (typeof apiObj.getTools === "function") {
|
|
486
|
+
readers.push(() => apiObj.getTools());
|
|
487
|
+
}
|
|
488
|
+
if (typeof apiObj.tools === "object" && apiObj.tools !== null) {
|
|
489
|
+
readers.push(() => apiObj.tools);
|
|
490
|
+
}
|
|
491
|
+
if (typeof apiObj.registeredTools === "object" && apiObj.registeredTools !== null) {
|
|
492
|
+
readers.push(() => apiObj.registeredTools);
|
|
493
|
+
}
|
|
494
|
+
for (const read of readers) {
|
|
495
|
+
try {
|
|
496
|
+
const value = await Promise.resolve(read());
|
|
497
|
+
const names = normalizeToolNameList(value);
|
|
498
|
+
if (names.length > 0) {
|
|
499
|
+
return names;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
logger.debug(`Failed to read visible tools from API: ${error instanceof Error ? error.message : String(error)}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
async function withToolVisibilityDiagnostics(result, context) {
|
|
509
|
+
if (!result.success || !result.data || typeof result.data !== "object" || Array.isArray(result.data)) {
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
const visible = await getApiVisibleToolNames();
|
|
513
|
+
const registered = [...registeredTools].sort();
|
|
514
|
+
const missingFromVisibility = registered.filter((name) => !visible.includes(name));
|
|
515
|
+
const data = result.data;
|
|
516
|
+
return {
|
|
517
|
+
...result,
|
|
518
|
+
data: {
|
|
519
|
+
...data,
|
|
520
|
+
tool_visibility: {
|
|
521
|
+
lane_context: {
|
|
522
|
+
agent_id: context?.agentId || "unknown-agent",
|
|
523
|
+
session_id: context?.sessionId || null,
|
|
524
|
+
workspace_id: context?.workspaceId || "default",
|
|
525
|
+
},
|
|
526
|
+
registered_tools: registered,
|
|
527
|
+
api_visible_tools: visible,
|
|
528
|
+
missing_from_visible: missingFromVisibility,
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
};
|
|
532
|
+
}
|
|
341
533
|
function clearStaleAutoSearchCache(now = Date.now()) {
|
|
342
534
|
for (const [sessionId, cache] of autoSearchCacheBySession.entries()) {
|
|
343
535
|
if ((now - cache.timestamp) >= AUTO_SEARCH_CACHE_TTL) {
|
|
@@ -369,394 +561,236 @@ function createConsoleLogger() {
|
|
|
369
561
|
error: (message, ...args) => console.error(`[CortexMemory] ${message}`, ...args),
|
|
370
562
|
};
|
|
371
563
|
}
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
375
|
-
const isSensitive = SENSITIVE_KEYS.some(k => key.toUpperCase().includes(k));
|
|
376
|
-
if (isSensitive) {
|
|
377
|
-
sanitized[key] = "***REDACTED***";
|
|
378
|
-
}
|
|
379
|
-
else if (typeof value === "object" && value !== null) {
|
|
380
|
-
sanitized[key] = sanitizeForLogging(value);
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
sanitized[key] = value;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return sanitized;
|
|
387
|
-
}
|
|
388
|
-
function logLifecycle(event, details = {}) {
|
|
389
|
-
const payload = sanitizeForLogging({
|
|
390
|
-
event,
|
|
391
|
-
plugin: PLUGIN_ID,
|
|
392
|
-
ts: new Date().toISOString(),
|
|
393
|
-
...details,
|
|
394
|
-
});
|
|
395
|
-
logger.info(`[Lifecycle] ${JSON.stringify(payload)}`);
|
|
396
|
-
}
|
|
397
|
-
function asRecord(value) {
|
|
398
|
-
if (typeof value === "object" && value !== null) {
|
|
564
|
+
function toTextContent(value) {
|
|
565
|
+
if (typeof value === "string") {
|
|
399
566
|
return value;
|
|
400
567
|
}
|
|
401
|
-
|
|
568
|
+
try {
|
|
569
|
+
return JSON.stringify(value, null, 2);
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
return String(value);
|
|
573
|
+
}
|
|
402
574
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return value.trim();
|
|
407
|
-
}
|
|
575
|
+
function truncateForLog(value, maxChars = TOOL_TRACE_PAYLOAD_MAX_CHARS) {
|
|
576
|
+
if (value.length <= maxChars) {
|
|
577
|
+
return value;
|
|
408
578
|
}
|
|
409
|
-
return
|
|
579
|
+
return `${value.slice(0, maxChars)}...<truncated>`;
|
|
410
580
|
}
|
|
411
|
-
function
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return null;
|
|
581
|
+
function formatUnknownForLog(value) {
|
|
582
|
+
if (typeof value === "string") {
|
|
583
|
+
return truncateForLog(value);
|
|
415
584
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const update = asRecord(data.update);
|
|
419
|
-
const updateMessage = update ? asRecord(update.message) : null;
|
|
420
|
-
const role = firstString([
|
|
421
|
-
data.role,
|
|
422
|
-
data.fromRole,
|
|
423
|
-
data.senderRole,
|
|
424
|
-
message?.role,
|
|
425
|
-
eventData?.role,
|
|
426
|
-
updateMessage?.role,
|
|
427
|
-
]) || "user";
|
|
428
|
-
const source = firstString([
|
|
429
|
-
data.source,
|
|
430
|
-
data.platform,
|
|
431
|
-
data.channel,
|
|
432
|
-
data.provider,
|
|
433
|
-
message?.source,
|
|
434
|
-
eventData?.source,
|
|
435
|
-
]) || "message";
|
|
436
|
-
let text = firstString([
|
|
437
|
-
data.content,
|
|
438
|
-
data.text,
|
|
439
|
-
data.body,
|
|
440
|
-
data.prompt,
|
|
441
|
-
data.message,
|
|
442
|
-
message?.content,
|
|
443
|
-
message?.text,
|
|
444
|
-
message?.body,
|
|
445
|
-
eventData?.content,
|
|
446
|
-
eventData?.text,
|
|
447
|
-
updateMessage?.text,
|
|
448
|
-
updateMessage?.caption,
|
|
449
|
-
]);
|
|
450
|
-
if (!text && Array.isArray(data.messages)) {
|
|
451
|
-
const merged = data.messages
|
|
452
|
-
.map(item => {
|
|
453
|
-
if (typeof item === "string")
|
|
454
|
-
return item;
|
|
455
|
-
const msgObj = asRecord(item);
|
|
456
|
-
if (!msgObj)
|
|
457
|
-
return "";
|
|
458
|
-
return firstString([msgObj.content, msgObj.text, msgObj.body]) || "";
|
|
459
|
-
})
|
|
460
|
-
.filter(Boolean)
|
|
461
|
-
.join("\n");
|
|
462
|
-
text = merged.trim() || undefined;
|
|
585
|
+
try {
|
|
586
|
+
return truncateForLog(JSON.stringify(value));
|
|
463
587
|
}
|
|
464
|
-
|
|
465
|
-
return
|
|
588
|
+
catch {
|
|
589
|
+
return truncateForLog(String(value));
|
|
466
590
|
}
|
|
467
|
-
return { text, role, source };
|
|
468
591
|
}
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
if (fromContext)
|
|
472
|
-
return fromContext;
|
|
473
|
-
const data = asRecord(payload);
|
|
474
|
-
const dataChat = data ? asRecord(data.chat) : null;
|
|
475
|
-
const message = data ? asRecord(data.message) : null;
|
|
476
|
-
const messageChat = message ? asRecord(message.chat) : null;
|
|
477
|
-
const eventData = data ? asRecord(data.data) : null;
|
|
478
|
-
const eventChat = eventData ? asRecord(eventData.chat) : null;
|
|
479
|
-
const update = data ? asRecord(data.update) : null;
|
|
480
|
-
const updateMessage = update ? asRecord(update.message) : null;
|
|
481
|
-
const updateChat = updateMessage ? asRecord(updateMessage.chat) : null;
|
|
482
|
-
const direct = firstString([
|
|
483
|
-
data?.sessionId,
|
|
484
|
-
data?.session_id,
|
|
485
|
-
data?.conversationId,
|
|
486
|
-
data?.conversation_id,
|
|
487
|
-
data?.threadId,
|
|
488
|
-
data?.thread_id,
|
|
489
|
-
message?.sessionId,
|
|
490
|
-
eventData?.sessionId,
|
|
491
|
-
]);
|
|
492
|
-
if (direct)
|
|
493
|
-
return direct;
|
|
494
|
-
const chatId = firstString([
|
|
495
|
-
data?.chatId,
|
|
496
|
-
data?.chat_id,
|
|
497
|
-
dataChat?.id,
|
|
498
|
-
messageChat?.id,
|
|
499
|
-
eventChat?.id,
|
|
500
|
-
updateChat?.id,
|
|
501
|
-
]);
|
|
502
|
-
if (chatId)
|
|
503
|
-
return `chat:${chatId}`;
|
|
504
|
-
return `fallback:${context.workspaceId || "default"}:${context.agentId || "agent"}`;
|
|
592
|
+
function createToolTraceId(toolName) {
|
|
593
|
+
return `${toolName}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 8)}`;
|
|
505
594
|
}
|
|
506
|
-
function
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const valB = partsB[i] || 0;
|
|
512
|
-
if (valA < valB)
|
|
513
|
-
return -1;
|
|
514
|
-
if (valA > valB)
|
|
515
|
-
return 1;
|
|
516
|
-
}
|
|
517
|
-
return 0;
|
|
595
|
+
function toErrorMessage(error) {
|
|
596
|
+
if (error instanceof Error) {
|
|
597
|
+
return error.message;
|
|
598
|
+
}
|
|
599
|
+
return String(error);
|
|
518
600
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
601
|
+
function toAgentToolResult(result, traceId) {
|
|
602
|
+
if (!result.success) {
|
|
603
|
+
const errorText = result.error || "Tool execution failed";
|
|
604
|
+
return {
|
|
605
|
+
content: [{ type: "text", text: errorText }],
|
|
606
|
+
details: {
|
|
607
|
+
status: "error",
|
|
608
|
+
error: errorText,
|
|
609
|
+
...(traceId ? { traceId } : {}),
|
|
610
|
+
...(result.errorCode ? { errorCode: result.errorCode } : {}),
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
const payloadText = toTextContent(result.data ?? { ok: true });
|
|
615
|
+
const detailsData = result.data && typeof result.data === "object" && !Array.isArray(result.data)
|
|
616
|
+
? result.data
|
|
617
|
+
: { value: result.data ?? null };
|
|
618
|
+
return {
|
|
619
|
+
content: [{ type: "text", text: payloadText }],
|
|
620
|
+
details: {
|
|
621
|
+
status: "ok",
|
|
622
|
+
...(traceId ? { traceId } : {}),
|
|
623
|
+
...detailsData,
|
|
624
|
+
},
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function sanitizeForLogging(obj) {
|
|
628
|
+
const result = {};
|
|
629
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
630
|
+
if (SENSITIVE_KEYS.some(k => key.toUpperCase().includes(k))) {
|
|
631
|
+
result[key] = "***REDACTED***";
|
|
632
|
+
}
|
|
633
|
+
else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
634
|
+
result[key] = sanitizeForLogging(value);
|
|
530
635
|
}
|
|
531
636
|
else {
|
|
532
|
-
|
|
637
|
+
result[key] = value;
|
|
533
638
|
}
|
|
534
639
|
}
|
|
535
|
-
|
|
536
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
537
|
-
logger.warn(`Version check warning: ${message}`);
|
|
538
|
-
}
|
|
640
|
+
return result;
|
|
539
641
|
}
|
|
540
642
|
function findProjectRoot() {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if (fs.existsSync(path.join(current, "openclaw.plugin.json")) &&
|
|
547
|
-
fs.existsSync(path.join(current, "package.json"))) {
|
|
548
|
-
return current;
|
|
643
|
+
let dir = __dirname;
|
|
644
|
+
while (dir !== path.dirname(dir)) {
|
|
645
|
+
const pkgPath = path.join(dir, "package.json");
|
|
646
|
+
if (fs.existsSync(pkgPath)) {
|
|
647
|
+
return dir;
|
|
549
648
|
}
|
|
550
|
-
|
|
649
|
+
dir = path.dirname(dir);
|
|
551
650
|
}
|
|
552
|
-
|
|
651
|
+
return process.cwd();
|
|
553
652
|
}
|
|
554
|
-
function
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
path.join(process.cwd(), "openclaw.json"),
|
|
564
|
-
homePath ? path.join(homePath, ".openclaw", "openclaw.json") : "",
|
|
653
|
+
function resolveSessionId(context, payload) {
|
|
654
|
+
const contextObj = (context || {});
|
|
655
|
+
const payloadObj = (payload || {});
|
|
656
|
+
const candidates = [
|
|
657
|
+
contextObj.sessionId,
|
|
658
|
+
contextObj.session_id,
|
|
659
|
+
payloadObj.sessionId,
|
|
660
|
+
payloadObj.session_id,
|
|
661
|
+
payloadObj.id,
|
|
565
662
|
];
|
|
566
|
-
for (const
|
|
567
|
-
if (
|
|
568
|
-
return
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
return null;
|
|
572
|
-
}
|
|
573
|
-
function loadPluginEnabledState() {
|
|
574
|
-
if (!configPath || !fs.existsSync(configPath)) {
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
try {
|
|
578
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
579
|
-
const openclawConfig = JSON.parse(content);
|
|
580
|
-
const pluginEntry = openclawConfig?.plugins?.entries?.[PLUGIN_ID];
|
|
581
|
-
if (pluginEntry && typeof pluginEntry === "object") {
|
|
582
|
-
return pluginEntry.enabled !== false;
|
|
663
|
+
for (const c of candidates) {
|
|
664
|
+
if (typeof c === "string" && c.trim()) {
|
|
665
|
+
return c.trim();
|
|
583
666
|
}
|
|
584
|
-
const legacyPluginConfig = openclawConfig?.plugins?.["cortex-memory"];
|
|
585
|
-
return legacyPluginConfig?.enabled !== false;
|
|
586
|
-
}
|
|
587
|
-
catch (e) {
|
|
588
|
-
logger.warn(`Failed to load config state: ${e}`);
|
|
589
|
-
return true;
|
|
590
667
|
}
|
|
668
|
+
return `fallback:${Date.now().toString(36)}`;
|
|
591
669
|
}
|
|
592
|
-
function
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
lastEnabledState = newState;
|
|
601
|
-
if (newState && !isEnabled) {
|
|
602
|
-
logger.info("Detected config change: enabling Cortex Memory plugin");
|
|
603
|
-
enable();
|
|
604
|
-
}
|
|
605
|
-
else if (!newState && isEnabled) {
|
|
606
|
-
logger.info("Detected config change: disabling Cortex Memory plugin");
|
|
607
|
-
disable();
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}, 5000);
|
|
670
|
+
function normalizeIncomingMessage(payload) {
|
|
671
|
+
const p = payload;
|
|
672
|
+
const text = typeof p.text === "string" ? p.text : (typeof p.content === "string" ? p.content : "");
|
|
673
|
+
if (!text)
|
|
674
|
+
return null;
|
|
675
|
+
const role = typeof p.role === "string" ? p.role : "unknown";
|
|
676
|
+
const source = typeof p.source === "string" ? p.source : "unknown";
|
|
677
|
+
return { text, role, source };
|
|
611
678
|
}
|
|
612
|
-
function
|
|
613
|
-
if (
|
|
614
|
-
|
|
615
|
-
configWatchInterval = null;
|
|
679
|
+
function asRecord(value) {
|
|
680
|
+
if (typeof value === "object" && value !== null) {
|
|
681
|
+
return value;
|
|
616
682
|
}
|
|
683
|
+
return null;
|
|
617
684
|
}
|
|
618
|
-
function
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const intervalMinutes = Math.max(5, Math.floor(config.autoReflectIntervalMinutes ?? 30));
|
|
623
|
-
const intervalMs = intervalMinutes * 60 * 1000;
|
|
624
|
-
autoReflectInterval = setInterval(() => {
|
|
625
|
-
if (!isEnabled) {
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
const schedulerContext = {
|
|
629
|
-
agentId: "cortex-memory-scheduler",
|
|
630
|
-
workspaceId: "system",
|
|
631
|
-
};
|
|
632
|
-
const marker = getArchiveMarker();
|
|
633
|
-
const now = Date.now();
|
|
634
|
-
if (marker === "missing" || marker === "error") {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (marker === lastAutoReflectArchiveMarker) {
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
if (now - lastAutoReflectRunAt < intervalMs) {
|
|
641
|
-
return;
|
|
685
|
+
function firstString(values) {
|
|
686
|
+
for (const v of values) {
|
|
687
|
+
if (typeof v === "string" && v.trim()) {
|
|
688
|
+
return v.trim();
|
|
642
689
|
}
|
|
643
|
-
resolveEngine().reflectMemory({}, schedulerContext)
|
|
644
|
-
.then(result => {
|
|
645
|
-
if (result.success) {
|
|
646
|
-
lastAutoReflectArchiveMarker = marker;
|
|
647
|
-
lastAutoReflectRunAt = now;
|
|
648
|
-
logger.info("Scheduled reflection complete");
|
|
649
|
-
}
|
|
650
|
-
else {
|
|
651
|
-
logger.warn(`Auto-reflect failed: ${result.error ?? "unknown_error"}`);
|
|
652
|
-
}
|
|
653
|
-
})
|
|
654
|
-
.catch(error => logger.warn(`Auto-reflect failed: ${String(error)}`));
|
|
655
|
-
}, intervalMs);
|
|
656
|
-
}
|
|
657
|
-
function stopAutoReflectScheduler() {
|
|
658
|
-
if (autoReflectInterval) {
|
|
659
|
-
clearInterval(autoReflectInterval);
|
|
660
|
-
autoReflectInterval = null;
|
|
661
690
|
}
|
|
691
|
+
return undefined;
|
|
662
692
|
}
|
|
663
693
|
function validateConfig(cfg) {
|
|
664
694
|
const errors = [];
|
|
665
|
-
if (!cfg.embedding?.provider
|
|
666
|
-
errors.push("embedding.provider
|
|
667
|
-
}
|
|
668
|
-
if (!cfg.embedding?.apiKey || !cfg.embedding?.baseURL) {
|
|
669
|
-
errors.push("embedding.apiKey and embedding.baseURL are required. Please configure third-party embedding endpoint credentials.");
|
|
695
|
+
if (!cfg.embedding?.provider) {
|
|
696
|
+
errors.push("embedding.provider is required.");
|
|
670
697
|
}
|
|
671
|
-
if (
|
|
672
|
-
errors.push("embedding.
|
|
698
|
+
if (!cfg.embedding?.model) {
|
|
699
|
+
errors.push("embedding.model is required.");
|
|
673
700
|
}
|
|
674
|
-
if (
|
|
675
|
-
errors.push("
|
|
701
|
+
if (!cfg.llm?.provider) {
|
|
702
|
+
errors.push("llm.provider is required.");
|
|
676
703
|
}
|
|
677
|
-
if (!cfg.llm?.
|
|
678
|
-
errors.push("llm.
|
|
704
|
+
if (!cfg.llm?.model) {
|
|
705
|
+
errors.push("llm.model is required.");
|
|
679
706
|
}
|
|
680
|
-
if (
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
errors.push("reranker.model is required. Please configure it in openclaw.json");
|
|
707
|
+
if (cfg.autoReflectIntervalMinutes !== undefined) {
|
|
708
|
+
if (!Number.isFinite(cfg.autoReflectIntervalMinutes) || cfg.autoReflectIntervalMinutes < 5) {
|
|
709
|
+
errors.push("autoReflectIntervalMinutes must be >= 5.");
|
|
710
|
+
}
|
|
685
711
|
}
|
|
686
|
-
if (
|
|
687
|
-
|
|
712
|
+
if (cfg.graphQualityMode !== undefined) {
|
|
713
|
+
if (!["off", "warn", "strict"].includes(cfg.graphQualityMode)) {
|
|
714
|
+
errors.push("graphQualityMode must be one of: off, warn, strict.");
|
|
715
|
+
}
|
|
688
716
|
}
|
|
689
|
-
if (
|
|
690
|
-
|
|
717
|
+
if (cfg.wikiProjection?.mode !== undefined) {
|
|
718
|
+
if (!["off", "incremental", "rebuild"].includes(cfg.wikiProjection.mode)) {
|
|
719
|
+
errors.push("wikiProjection.mode must be one of: off, incremental, rebuild.");
|
|
720
|
+
}
|
|
691
721
|
}
|
|
692
|
-
if (cfg.
|
|
693
|
-
|
|
722
|
+
if (cfg.wikiProjection?.maxBatch !== undefined) {
|
|
723
|
+
if (!Number.isFinite(cfg.wikiProjection.maxBatch) || cfg.wikiProjection.maxBatch < 1) {
|
|
724
|
+
errors.push("wikiProjection.maxBatch must be >= 1.");
|
|
725
|
+
}
|
|
694
726
|
}
|
|
695
727
|
if (cfg.readFusion?.channelWeights) {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
728
|
+
const weights = cfg.readFusion.channelWeights;
|
|
729
|
+
for (const [key, value] of Object.entries(weights)) {
|
|
730
|
+
if (typeof value === "number" && (!Number.isFinite(value) || value < 0)) {
|
|
731
|
+
errors.push(`readFusion.channelWeights.${key} must be >= 0.`);
|
|
700
732
|
}
|
|
701
733
|
}
|
|
702
734
|
}
|
|
703
735
|
if (cfg.readFusion?.channelTopK) {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
736
|
+
const topK = cfg.readFusion.channelTopK;
|
|
737
|
+
for (const [key, value] of Object.entries(topK)) {
|
|
738
|
+
if (typeof value === "number" && (!Number.isFinite(value) || value < 1)) {
|
|
739
|
+
errors.push(`readFusion.channelTopK.${key} must be >= 1.`);
|
|
708
740
|
}
|
|
709
741
|
}
|
|
710
742
|
}
|
|
711
|
-
if (
|
|
712
|
-
|
|
743
|
+
if (cfg.vectorChunking?.chunkSize !== undefined) {
|
|
744
|
+
if (!Number.isFinite(cfg.vectorChunking.chunkSize) || cfg.vectorChunking.chunkSize < 100) {
|
|
745
|
+
errors.push("vectorChunking.chunkSize must be >= 100.");
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (cfg.vectorChunking?.chunkOverlap !== undefined) {
|
|
749
|
+
if (!Number.isFinite(cfg.vectorChunking.chunkOverlap) || cfg.vectorChunking.chunkOverlap < 0) {
|
|
750
|
+
errors.push("vectorChunking.chunkOverlap must be >= 0.");
|
|
751
|
+
}
|
|
713
752
|
}
|
|
714
|
-
if (
|
|
715
|
-
|
|
753
|
+
if (cfg.vectorChunking?.evidenceMaxChunks !== undefined) {
|
|
754
|
+
if (!Number.isFinite(cfg.vectorChunking.evidenceMaxChunks) || cfg.vectorChunking.evidenceMaxChunks < 0) {
|
|
755
|
+
errors.push("vectorChunking.evidenceMaxChunks must be >= 0.");
|
|
756
|
+
}
|
|
716
757
|
}
|
|
717
|
-
if (cfg.
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
758
|
+
if (cfg.writePolicy) {
|
|
759
|
+
const wp = cfg.writePolicy;
|
|
760
|
+
const bounded01 = ["archiveMinConfidence", "archiveMinQualityScore", "activeMinQualityScore"];
|
|
761
|
+
for (const key of bounded01) {
|
|
762
|
+
const value = wp[key];
|
|
763
|
+
if (value !== undefined && (!Number.isFinite(value) || value < 0 || value > 1)) {
|
|
764
|
+
errors.push(`writePolicy.${key} must be between 0 and 1.`);
|
|
765
|
+
}
|
|
721
766
|
}
|
|
722
|
-
if (
|
|
723
|
-
errors.push("
|
|
767
|
+
if (wp.activeDedupTailLines !== undefined && (!Number.isFinite(wp.activeDedupTailLines) || wp.activeDedupTailLines < 20)) {
|
|
768
|
+
errors.push("writePolicy.activeDedupTailLines must be >= 20.");
|
|
724
769
|
}
|
|
725
|
-
if (
|
|
726
|
-
errors.push("
|
|
770
|
+
if (wp.activeTextMaxChars !== undefined && (!Number.isFinite(wp.activeTextMaxChars) || wp.activeTextMaxChars < 500)) {
|
|
771
|
+
errors.push("writePolicy.activeTextMaxChars must be >= 500.");
|
|
727
772
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (typeof cfg.vectorChunking.chunkSize === "number" && (!Number.isFinite(cfg.vectorChunking.chunkSize) || cfg.vectorChunking.chunkSize < 200)) {
|
|
731
|
-
errors.push("vectorChunking.chunkSize must be >= 200.");
|
|
773
|
+
if (wp.archiveSourceTextMaxChars !== undefined && (!Number.isFinite(wp.archiveSourceTextMaxChars) || wp.archiveSourceTextMaxChars < 1000)) {
|
|
774
|
+
errors.push("writePolicy.archiveSourceTextMaxChars must be >= 1000.");
|
|
732
775
|
}
|
|
733
|
-
|
|
734
|
-
|
|
776
|
+
}
|
|
777
|
+
if (cfg.syncPolicy) {
|
|
778
|
+
if (cfg.syncPolicy.includeLocalActiveInput !== undefined && typeof cfg.syncPolicy.includeLocalActiveInput !== "boolean") {
|
|
779
|
+
errors.push("syncPolicy.includeLocalActiveInput must be boolean.");
|
|
735
780
|
}
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
cfg.vectorChunking.chunkOverlap >= cfg.vectorChunking.chunkSize) {
|
|
739
|
-
errors.push("vectorChunking.chunkOverlap must be smaller than chunkSize.");
|
|
781
|
+
if (cfg.syncPolicy.graphQualityMode !== undefined && !["off", "warn", "strict"].includes(cfg.syncPolicy.graphQualityMode)) {
|
|
782
|
+
errors.push("syncPolicy.graphQualityMode must be one of: off, warn, strict.");
|
|
740
783
|
}
|
|
741
784
|
}
|
|
742
785
|
if (cfg.memoryDecay) {
|
|
743
786
|
if (typeof cfg.memoryDecay.minFloor === "number" && (!Number.isFinite(cfg.memoryDecay.minFloor) || cfg.memoryDecay.minFloor < 0 || cfg.memoryDecay.minFloor > 1)) {
|
|
744
|
-
errors.push("memoryDecay.minFloor must be
|
|
787
|
+
errors.push("memoryDecay.minFloor must be between 0 and 1.");
|
|
745
788
|
}
|
|
746
789
|
if (typeof cfg.memoryDecay.defaultHalfLifeDays === "number" && (!Number.isFinite(cfg.memoryDecay.defaultHalfLifeDays) || cfg.memoryDecay.defaultHalfLifeDays <= 0)) {
|
|
747
790
|
errors.push("memoryDecay.defaultHalfLifeDays must be > 0.");
|
|
748
791
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
for (const [key, value] of Object.entries(mapping)) {
|
|
752
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
753
|
-
errors.push(`memoryDecay.halfLifeByEventType.${key} must be > 0.`);
|
|
754
|
-
break;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
const anti = cfg.memoryDecay.antiDecay;
|
|
759
|
-
if (anti) {
|
|
792
|
+
if (cfg.memoryDecay.antiDecay) {
|
|
793
|
+
const anti = cfg.memoryDecay.antiDecay;
|
|
760
794
|
if (typeof anti.maxBoost === "number" && (!Number.isFinite(anti.maxBoost) || anti.maxBoost < 1)) {
|
|
761
795
|
errors.push("memoryDecay.antiDecay.maxBoost must be >= 1.");
|
|
762
796
|
}
|
|
@@ -768,841 +802,230 @@ function validateConfig(cfg) {
|
|
|
768
802
|
}
|
|
769
803
|
}
|
|
770
804
|
}
|
|
805
|
+
if (cfg.readTuning) {
|
|
806
|
+
const numericReadTuningFields = [
|
|
807
|
+
["readTuning.scoring.lexicalWeight", cfg.readTuning.scoring?.lexicalWeight, 0],
|
|
808
|
+
["readTuning.scoring.bm25Scale", cfg.readTuning.scoring?.bm25Scale, 0],
|
|
809
|
+
["readTuning.scoring.semanticWeight", cfg.readTuning.scoring?.semanticWeight, 0],
|
|
810
|
+
["readTuning.scoring.recencyWeight", cfg.readTuning.scoring?.recencyWeight, 0],
|
|
811
|
+
["readTuning.scoring.qualityWeight", cfg.readTuning.scoring?.qualityWeight, 0],
|
|
812
|
+
["readTuning.scoring.typeMatchWeight", cfg.readTuning.scoring?.typeMatchWeight, 0],
|
|
813
|
+
["readTuning.scoring.graphMatchWeight", cfg.readTuning.scoring?.graphMatchWeight, 0],
|
|
814
|
+
["readTuning.rrf.k", cfg.readTuning.rrf?.k, 1],
|
|
815
|
+
["readTuning.rrf.weight", cfg.readTuning.rrf?.weight, 0],
|
|
816
|
+
["readTuning.autoContext.queryMaxChars", cfg.readTuning.autoContext?.queryMaxChars, 20],
|
|
817
|
+
];
|
|
818
|
+
for (const [name, value, min] of numericReadTuningFields) {
|
|
819
|
+
if (typeof value === "number" && (!Number.isFinite(value) || value < min)) {
|
|
820
|
+
errors.push(`${name} must be >= ${min}.`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (Array.isArray(cfg.readTuning.recency?.buckets)) {
|
|
824
|
+
const buckets = cfg.readTuning.recency.buckets;
|
|
825
|
+
for (let i = 0; i < buckets.length; i += 1) {
|
|
826
|
+
const bucket = buckets[i];
|
|
827
|
+
const maxAgeHours = bucket.maxAgeHours;
|
|
828
|
+
const finiteOrInfinity = Number.isFinite(maxAgeHours) || maxAgeHours === Number.POSITIVE_INFINITY;
|
|
829
|
+
if (!finiteOrInfinity || maxAgeHours <= 0) {
|
|
830
|
+
errors.push(`readTuning.recency.buckets[${i}].maxAgeHours must be > 0.`);
|
|
831
|
+
}
|
|
832
|
+
if (!Number.isFinite(bucket.score) || bucket.score < 0) {
|
|
833
|
+
errors.push(`readTuning.recency.buckets[${i}].score must be >= 0.`);
|
|
834
|
+
}
|
|
835
|
+
if (!Number.isFinite(bucket.bonus) || bucket.bonus < 0) {
|
|
836
|
+
errors.push(`readTuning.recency.buckets[${i}].bonus must be >= 0.`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
771
841
|
return errors;
|
|
772
842
|
}
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
843
|
+
function checkOpenClawVersion() {
|
|
844
|
+
return new Promise((resolve) => {
|
|
845
|
+
try {
|
|
846
|
+
const apiObj = api;
|
|
847
|
+
const candidates = [
|
|
848
|
+
{ source: "api.openclawVersion", value: apiObj?.openclawVersion },
|
|
849
|
+
{ source: "api.gatewayVersion", value: apiObj?.gatewayVersion },
|
|
850
|
+
{ source: "api.coreVersion", value: apiObj?.coreVersion },
|
|
851
|
+
{ source: "api.openclaw.version", value: apiObj?.openclaw?.version },
|
|
852
|
+
].filter((item) => typeof item.value === "string" && item.value.trim().length > 0);
|
|
853
|
+
const selected = candidates.find((item) => {
|
|
854
|
+
const value = item.value;
|
|
855
|
+
const major = Number(value.replace(/[^0-9.]/g, "").split(".")[0] || "0");
|
|
856
|
+
// OpenClaw release versions are calendar-like (e.g. 2026.x.x),
|
|
857
|
+
// while plugin/package versions like 0.x should be ignored.
|
|
858
|
+
return Number.isFinite(major) && major >= 2000;
|
|
859
|
+
});
|
|
860
|
+
const version = selected?.value || "";
|
|
861
|
+
if (!version) {
|
|
862
|
+
logger.debug("Could not determine OpenClaw gateway/core version from API fields; skip compatibility check");
|
|
863
|
+
resolve();
|
|
788
864
|
return;
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
865
|
+
}
|
|
866
|
+
const parseVersion = (v) => {
|
|
867
|
+
const parts = v.replace(/[^0-9.]/g, "").split(".").map(Number);
|
|
868
|
+
return parts.length >= 3 ? parts : [...parts, ...Array(3 - parts.length).fill(0)];
|
|
869
|
+
};
|
|
870
|
+
const current = parseVersion(version);
|
|
871
|
+
const min = parseVersion(MIN_OPENCLAW_GATEWAY_VERSION);
|
|
872
|
+
const max = parseVersion(MAX_OPENCLAW_GATEWAY_VERSION);
|
|
873
|
+
const currentNum = current[0] * 10000 + current[1] * 100 + current[2];
|
|
874
|
+
const minNum = min[0] * 10000 + min[1] * 100 + min[2];
|
|
875
|
+
const maxNum = max[0] * 10000 + max[1] * 100 + max[2];
|
|
876
|
+
if (currentNum < minNum) {
|
|
877
|
+
logger.warn(`OpenClaw gateway/core version ${version} (from ${selected?.source || "unknown"}) is below minimum ${MIN_OPENCLAW_GATEWAY_VERSION}. Some features may not work.`);
|
|
878
|
+
}
|
|
879
|
+
else if (currentNum >= maxNum) {
|
|
880
|
+
logger.warn(`OpenClaw gateway/core version ${version} (from ${selected?.source || "unknown"}) may not be fully compatible. Maximum tested version is ${MAX_OPENCLAW_GATEWAY_VERSION}.`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
catch (e) {
|
|
884
|
+
logger.warn(`Version check failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
885
|
+
}
|
|
886
|
+
resolve();
|
|
798
887
|
});
|
|
799
888
|
}
|
|
800
|
-
function
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
fs.writeFileSync(pythonPidFilePath, String(pid), "utf-8");
|
|
889
|
+
function findOpenClawConfig() {
|
|
890
|
+
const explicitPath = (0, runtime_env_1.getEnvValue)("OPENCLAW_CONFIG_PATH");
|
|
891
|
+
if (explicitPath && fs.existsSync(explicitPath)) {
|
|
892
|
+
return explicitPath;
|
|
805
893
|
}
|
|
806
|
-
|
|
807
|
-
|
|
894
|
+
const stateDir = (0, runtime_env_1.getEnvValue)("OPENCLAW_STATE_DIR");
|
|
895
|
+
if (stateDir) {
|
|
896
|
+
const stateConfig = path.join(stateDir, "openclaw.json");
|
|
897
|
+
if (fs.existsSync(stateConfig)) {
|
|
898
|
+
return stateConfig;
|
|
899
|
+
}
|
|
808
900
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
if (fs.existsSync(pythonPidFilePath)) {
|
|
815
|
-
fs.unlinkSync(pythonPidFilePath);
|
|
901
|
+
const basePath = (0, runtime_env_1.getEnvValue)("OPENCLAW_BASE_PATH");
|
|
902
|
+
if (basePath) {
|
|
903
|
+
const baseConfig = path.join(basePath, "openclaw.json");
|
|
904
|
+
if (fs.existsSync(baseConfig)) {
|
|
905
|
+
return baseConfig;
|
|
816
906
|
}
|
|
817
907
|
}
|
|
818
|
-
|
|
819
|
-
|
|
908
|
+
const home = (0, runtime_env_1.getHomeDir)();
|
|
909
|
+
const candidates = [
|
|
910
|
+
path.join(home, ".openclaw", "openclaw.json"),
|
|
911
|
+
path.join(home, ".openclaw", "config.json"),
|
|
912
|
+
];
|
|
913
|
+
for (const c of candidates) {
|
|
914
|
+
if (fs.existsSync(c)) {
|
|
915
|
+
return c;
|
|
916
|
+
}
|
|
820
917
|
}
|
|
918
|
+
return null;
|
|
821
919
|
}
|
|
822
|
-
function
|
|
823
|
-
if (!pythonPidFilePath || !fs.existsSync(pythonPidFilePath))
|
|
824
|
-
return null;
|
|
920
|
+
function loadPluginEnabledState() {
|
|
825
921
|
try {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
922
|
+
if (!configPath)
|
|
923
|
+
return true;
|
|
924
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
925
|
+
const cfg = JSON.parse(raw);
|
|
926
|
+
const entry = cfg?.plugins?.entries?.[PLUGIN_ID];
|
|
927
|
+
if (entry && typeof entry.enabled === "boolean") {
|
|
928
|
+
return entry.enabled;
|
|
929
|
+
}
|
|
930
|
+
const allow = cfg?.plugins?.allow;
|
|
931
|
+
if (Array.isArray(allow)) {
|
|
932
|
+
return allow.includes(PLUGIN_ID);
|
|
933
|
+
}
|
|
934
|
+
return true;
|
|
829
935
|
}
|
|
830
936
|
catch {
|
|
831
|
-
return
|
|
937
|
+
return true;
|
|
832
938
|
}
|
|
833
939
|
}
|
|
834
|
-
function
|
|
835
|
-
if (
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
(0, child_process_1.execSync)(`taskkill /pid ${pid} /f /t`, { stdio: "ignore" });
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
940
|
+
function startConfigWatcher() {
|
|
941
|
+
if (configWatchInterval) {
|
|
942
|
+
clearInterval(configWatchInterval);
|
|
943
|
+
}
|
|
944
|
+
configWatchInterval = setInterval(() => {
|
|
842
945
|
try {
|
|
843
|
-
|
|
946
|
+
if (!configPath || !fs.existsSync(configPath))
|
|
947
|
+
return;
|
|
948
|
+
const currentEnabled = loadPluginEnabledState();
|
|
949
|
+
if (currentEnabled !== isEnabled) {
|
|
950
|
+
logger.info(`Plugin enabled state changed from ${isEnabled} to ${currentEnabled}`);
|
|
951
|
+
if (currentEnabled) {
|
|
952
|
+
enable().catch(e => logger.error(`Failed to enable: ${e}`));
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
disable().catch(e => logger.error(`Failed to disable: ${e}`));
|
|
956
|
+
}
|
|
957
|
+
}
|
|
844
958
|
}
|
|
845
|
-
catch {
|
|
846
|
-
|
|
847
|
-
process.kill(-pid, "SIGTERM");
|
|
959
|
+
catch (e) {
|
|
960
|
+
logger.debug(`Config watch error: ${e}`);
|
|
848
961
|
}
|
|
849
|
-
|
|
850
|
-
setTimeout(() => {
|
|
851
|
-
try {
|
|
852
|
-
process.kill(pid, "SIGKILL");
|
|
853
|
-
}
|
|
854
|
-
catch { }
|
|
855
|
-
try {
|
|
856
|
-
process.kill(-pid, "SIGKILL");
|
|
857
|
-
}
|
|
858
|
-
catch { }
|
|
859
|
-
}, 2000);
|
|
860
|
-
}
|
|
861
|
-
catch (e) {
|
|
862
|
-
logger.warn(`Failed to kill process ${pid}: ${e instanceof Error ? e.message : String(e)}`);
|
|
863
|
-
}
|
|
962
|
+
}, 5000);
|
|
864
963
|
}
|
|
865
|
-
function
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const pids = output
|
|
870
|
-
.split(/\r?\n/)
|
|
871
|
-
.filter(line => line.includes("LISTENING"))
|
|
872
|
-
.map(line => {
|
|
873
|
-
const parts = line.trim().split(/\s+/);
|
|
874
|
-
return Number(parts[parts.length - 1]);
|
|
875
|
-
})
|
|
876
|
-
.filter(pid => Number.isInteger(pid) && pid > 0);
|
|
877
|
-
for (const pid of pids) {
|
|
878
|
-
killProcessByPid(pid);
|
|
879
|
-
}
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
const output = (0, child_process_1.execSync)(`sh -lc "lsof -ti tcp:${port} 2>/dev/null || true"`, { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] });
|
|
883
|
-
const pids = output
|
|
884
|
-
.split(/\r?\n/)
|
|
885
|
-
.map(line => Number(line.trim()))
|
|
886
|
-
.filter(pid => Number.isInteger(pid) && pid > 0);
|
|
887
|
-
for (const pid of pids) {
|
|
888
|
-
killProcessByPid(pid);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
catch {
|
|
892
|
-
// ignore
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
async function checkPortInUse() {
|
|
896
|
-
const apiUrl = getBaseUrl();
|
|
897
|
-
try {
|
|
898
|
-
const response = await fetch(`${apiUrl}/health`, { signal: AbortSignal.timeout(1000) });
|
|
899
|
-
return response.ok;
|
|
900
|
-
}
|
|
901
|
-
catch {
|
|
902
|
-
return false;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
async function startPythonService() {
|
|
906
|
-
if (pythonStartPromise) {
|
|
907
|
-
return pythonStartPromise;
|
|
908
|
-
}
|
|
909
|
-
pythonStartPromise = startPythonServiceInternal().finally(() => {
|
|
910
|
-
pythonStartPromise = null;
|
|
911
|
-
});
|
|
912
|
-
return pythonStartPromise;
|
|
913
|
-
}
|
|
914
|
-
async function startPythonServiceInternal() {
|
|
915
|
-
if (!config) {
|
|
916
|
-
throw new Error("Configuration not loaded");
|
|
917
|
-
}
|
|
918
|
-
const projectRoot = findProjectRoot();
|
|
919
|
-
pythonPidFilePath = path.join(projectRoot, ".cortex-memory-python.pid");
|
|
920
|
-
const { host, port } = getApiHostAndPort();
|
|
921
|
-
const stalePid = readPythonPid();
|
|
922
|
-
if (stalePid && (!pythonProcess || pythonProcess.pid !== stalePid)) {
|
|
923
|
-
logger.info(`Found stale Python pid ${stalePid}, trying to stop it...`);
|
|
924
|
-
killProcessByPid(stalePid);
|
|
925
|
-
await sleep(800);
|
|
926
|
-
clearPythonPidFile();
|
|
927
|
-
}
|
|
928
|
-
const healthyRunning = await checkPortInUse();
|
|
929
|
-
if (healthyRunning) {
|
|
930
|
-
logger.info("Python service already running, shutting down old instance...");
|
|
931
|
-
await shutdownPythonApi();
|
|
932
|
-
await sleep(1000);
|
|
933
|
-
}
|
|
934
|
-
const occupied = await isPortListening(host, port);
|
|
935
|
-
if (occupied) {
|
|
936
|
-
logger.warn(`Port ${port} is still occupied after graceful shutdown, forcing cleanup...`);
|
|
937
|
-
freePortWithSystemTools(port);
|
|
938
|
-
await sleep(1000);
|
|
939
|
-
}
|
|
940
|
-
if (await isPortListening(host, port)) {
|
|
941
|
-
throw new Error(`Port ${port} is already in use by another process. Please stop that process and retry.`);
|
|
942
|
-
}
|
|
943
|
-
const venvDir = path.join(projectRoot, "venv");
|
|
944
|
-
const pythonCmd = process.platform === "win32"
|
|
945
|
-
? path.join(venvDir, "Scripts", "python.exe")
|
|
946
|
-
: path.join(venvDir, "bin", "python");
|
|
947
|
-
if (!fs.existsSync(pythonCmd)) {
|
|
948
|
-
throw new Error("Python environment not found. Please run 'npm install' first.");
|
|
949
|
-
}
|
|
950
|
-
const errors = validateConfig(config);
|
|
951
|
-
if (errors.length > 0) {
|
|
952
|
-
throw new Error(`Configuration errors:\n${errors.join("\n")}`);
|
|
953
|
-
}
|
|
954
|
-
logger.info("Starting Cortex Memory Python service...");
|
|
955
|
-
const env = {
|
|
956
|
-
...(0, runtime_env_1.getProcessEnvCopy)(),
|
|
957
|
-
CORTEX_MEMORY_EMBEDDING_PROVIDER: config.embedding.provider,
|
|
958
|
-
CORTEX_MEMORY_EMBEDDING_MODEL: config.embedding.model,
|
|
959
|
-
CORTEX_MEMORY_LLM_PROVIDER: config.llm.provider,
|
|
960
|
-
CORTEX_MEMORY_LLM_MODEL: config.llm.model,
|
|
961
|
-
CORTEX_MEMORY_RERANKER_PROVIDER: config.reranker.provider || "",
|
|
962
|
-
CORTEX_MEMORY_RERANKER_MODEL: config.reranker.model,
|
|
963
|
-
CORTEX_MEMORY_DB_PATH: config.dbPath || path.join((0, runtime_env_1.getHomeDir)(), ".openclaw", "agents", "main", "lancedb_store"),
|
|
964
|
-
};
|
|
965
|
-
if (config.embedding.apiKey) {
|
|
966
|
-
env.CORTEX_MEMORY_EMBEDDING_API_KEY = config.embedding.apiKey;
|
|
967
|
-
}
|
|
968
|
-
if (config.embedding.baseURL) {
|
|
969
|
-
env.CORTEX_MEMORY_EMBEDDING_BASE_URL = config.embedding.baseURL;
|
|
970
|
-
}
|
|
971
|
-
if (config.embedding.dimensions) {
|
|
972
|
-
env.CORTEX_MEMORY_EMBEDDING_DIMENSIONS = String(config.embedding.dimensions);
|
|
973
|
-
}
|
|
974
|
-
if (config.llm.apiKey) {
|
|
975
|
-
env.CORTEX_MEMORY_LLM_API_KEY = config.llm.apiKey;
|
|
976
|
-
}
|
|
977
|
-
if (config.llm.baseURL) {
|
|
978
|
-
env.CORTEX_MEMORY_LLM_BASE_URL = config.llm.baseURL;
|
|
979
|
-
}
|
|
980
|
-
if (config.reranker.apiKey) {
|
|
981
|
-
env.CORTEX_MEMORY_RERANKER_API_KEY = config.reranker.apiKey;
|
|
982
|
-
}
|
|
983
|
-
if (config.reranker.baseURL) {
|
|
984
|
-
env.CORTEX_MEMORY_RERANKER_ENDPOINT = config.reranker.baseURL;
|
|
985
|
-
}
|
|
986
|
-
return new Promise((resolve, reject) => {
|
|
987
|
-
pythonProcess = (0, child_process_1.spawn)(pythonCmd, ["-m", "api.server"], {
|
|
988
|
-
cwd: projectRoot,
|
|
989
|
-
detached: false,
|
|
990
|
-
windowsHide: true,
|
|
991
|
-
env: { ...env, PYTHONWARNINGS: "ignore::RuntimeWarning" },
|
|
992
|
-
});
|
|
993
|
-
if (pythonProcess.pid) {
|
|
994
|
-
writePythonPid(pythonProcess.pid);
|
|
995
|
-
}
|
|
996
|
-
let started = false;
|
|
997
|
-
let stderrBuffer = "";
|
|
998
|
-
let settled = false;
|
|
999
|
-
let startupTimeout = null;
|
|
1000
|
-
const resolveOnce = () => {
|
|
1001
|
-
if (settled)
|
|
1002
|
-
return;
|
|
1003
|
-
settled = true;
|
|
1004
|
-
started = true;
|
|
1005
|
-
if (startupTimeout) {
|
|
1006
|
-
clearTimeout(startupTimeout);
|
|
1007
|
-
startupTimeout = null;
|
|
1008
|
-
}
|
|
1009
|
-
resolve();
|
|
1010
|
-
};
|
|
1011
|
-
const rejectOnce = (error) => {
|
|
1012
|
-
if (settled)
|
|
1013
|
-
return;
|
|
1014
|
-
settled = true;
|
|
1015
|
-
if (startupTimeout) {
|
|
1016
|
-
clearTimeout(startupTimeout);
|
|
1017
|
-
startupTimeout = null;
|
|
1018
|
-
}
|
|
1019
|
-
reject(error);
|
|
1020
|
-
};
|
|
1021
|
-
pythonProcess.stdout?.on("data", (data) => {
|
|
1022
|
-
const output = data.toString();
|
|
1023
|
-
if (!output.toLowerCase().includes("key") && !output.toLowerCase().includes("token")) {
|
|
1024
|
-
logger.info(`[Python] ${output.trim()}`);
|
|
1025
|
-
}
|
|
1026
|
-
if (output.includes("Cortex Memory API started") || output.includes("Application startup complete")) {
|
|
1027
|
-
resolveOnce();
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
pythonProcess.stderr?.on("data", (data) => {
|
|
1031
|
-
const output = data.toString();
|
|
1032
|
-
stderrBuffer += output;
|
|
1033
|
-
if (!output.toLowerCase().includes("key") && !output.toLowerCase().includes("token")) {
|
|
1034
|
-
logger.warn(`[Python] ${output.trim()}`);
|
|
1035
|
-
}
|
|
1036
|
-
if (output.includes("Cortex Memory API started") ||
|
|
1037
|
-
output.includes("Application startup complete") ||
|
|
1038
|
-
output.includes("Uvicorn running on")) {
|
|
1039
|
-
resolveOnce();
|
|
1040
|
-
}
|
|
1041
|
-
});
|
|
1042
|
-
pythonProcess.on("error", (error) => {
|
|
1043
|
-
logger.error("Failed to start Python service:", error.message);
|
|
1044
|
-
rejectOnce(error);
|
|
1045
|
-
});
|
|
1046
|
-
pythonProcess.on("exit", (code) => {
|
|
1047
|
-
clearPythonPidFile();
|
|
1048
|
-
pythonProcess = null;
|
|
1049
|
-
if (!started && code !== 0 && !isShuttingDown) {
|
|
1050
|
-
rejectOnce(new Error(`Python service exited with code ${code}. Stderr: ${stderrBuffer.slice(-500)}`));
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
startupTimeout = setTimeout(() => {
|
|
1054
|
-
if (!started) {
|
|
1055
|
-
const tail = stderrBuffer ? `\nLast stderr: ${stderrBuffer.slice(-500)}` : "";
|
|
1056
|
-
killPythonProcess();
|
|
1057
|
-
rejectOnce(new Error(`Timeout waiting for Python service to start (300s)${tail}`));
|
|
1058
|
-
}
|
|
1059
|
-
}, 300000);
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
async function shutdownPythonApi() {
|
|
1063
|
-
const apiUrl = getBaseUrl();
|
|
1064
|
-
try {
|
|
1065
|
-
await fetch(`${apiUrl}/shutdown`, { method: "POST", signal: AbortSignal.timeout(2000) });
|
|
1066
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1067
|
-
}
|
|
1068
|
-
catch {
|
|
1069
|
-
// ignore
|
|
964
|
+
function stopConfigWatcher() {
|
|
965
|
+
if (configWatchInterval) {
|
|
966
|
+
clearInterval(configWatchInterval);
|
|
967
|
+
configWatchInterval = null;
|
|
1070
968
|
}
|
|
1071
969
|
}
|
|
1072
|
-
function
|
|
1073
|
-
|
|
1074
|
-
const pidFromFile = readPythonPid();
|
|
1075
|
-
const pid = directPid || pidFromFile;
|
|
1076
|
-
if (!pid)
|
|
970
|
+
function startAutoReflectScheduler() {
|
|
971
|
+
if (!config?.autoReflect) {
|
|
1077
972
|
return;
|
|
1078
|
-
try {
|
|
1079
|
-
killProcessByPid(pid);
|
|
1080
|
-
}
|
|
1081
|
-
catch (e) {
|
|
1082
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1083
|
-
logger.warn(`Failed to kill Python process: ${message}`);
|
|
1084
|
-
}
|
|
1085
|
-
finally {
|
|
1086
|
-
pythonProcess = null;
|
|
1087
|
-
clearPythonPidFile();
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
async function stopPythonServiceAsync() {
|
|
1091
|
-
if (pythonStartPromise) {
|
|
1092
|
-
try {
|
|
1093
|
-
await pythonStartPromise;
|
|
1094
|
-
}
|
|
1095
|
-
catch {
|
|
1096
|
-
// ignore
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
await shutdownPythonApi();
|
|
1100
|
-
killPythonProcess();
|
|
1101
|
-
}
|
|
1102
|
-
function stopPythonService() {
|
|
1103
|
-
stopPythonServiceAsync();
|
|
1104
|
-
}
|
|
1105
|
-
function getBaseUrl() {
|
|
1106
|
-
return config?.apiUrl ?? "http://localhost:8765";
|
|
1107
|
-
}
|
|
1108
|
-
async function waitForService(maxAttempts = 30) {
|
|
1109
|
-
const apiUrl = getBaseUrl();
|
|
1110
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
1111
|
-
try {
|
|
1112
|
-
const response = await fetch(`${apiUrl}/health`, { signal: AbortSignal.timeout(1000) });
|
|
1113
|
-
if (response.ok)
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
catch { }
|
|
1117
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1118
|
-
}
|
|
1119
|
-
throw new Error("Service failed to become ready");
|
|
1120
|
-
}
|
|
1121
|
-
function formatApiError(error) {
|
|
1122
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1123
|
-
const lower = message.toLowerCase();
|
|
1124
|
-
if (lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("fetch failed")) {
|
|
1125
|
-
const err = ERROR_CODES.CONNECTION_REFUSED;
|
|
1126
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
1127
|
-
}
|
|
1128
|
-
if (lower.includes("abort") || lower.includes("timed out")) {
|
|
1129
|
-
const err = ERROR_CODES.TIMEOUT;
|
|
1130
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
1131
|
-
}
|
|
1132
|
-
if (lower.includes("404") || lower.includes("not found")) {
|
|
1133
|
-
const err = ERROR_CODES.NOT_FOUND;
|
|
1134
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
1135
|
-
}
|
|
1136
|
-
if (lower.includes("400") || lower.includes("invalid")) {
|
|
1137
|
-
const err = ERROR_CODES.INVALID_INPUT;
|
|
1138
|
-
return `${err.message} (${err.code}). ${err.suggestion}`;
|
|
1139
|
-
}
|
|
1140
|
-
const err = ERROR_CODES.SERVICE_ERROR;
|
|
1141
|
-
return `${err.message} (${err.code}). Details: ${message}`;
|
|
1142
|
-
}
|
|
1143
|
-
const pendingRequests = new Map();
|
|
1144
|
-
const requestDebounceMs = 100;
|
|
1145
|
-
function getRequestKey(endpoint, method, body) {
|
|
1146
|
-
const bodyHash = body ? JSON.stringify(body).slice(0, 100) : "";
|
|
1147
|
-
return `${method}:${endpoint}:${bodyHash}`;
|
|
1148
|
-
}
|
|
1149
|
-
async function apiCallWithRetry(endpoint, method = "GET", body, options) {
|
|
1150
|
-
const { maxRetries = 3, baseDelay = 1000, timeout = 30000, skipDebounce = false } = options || {};
|
|
1151
|
-
if (!skipDebounce) {
|
|
1152
|
-
const requestKey = getRequestKey(endpoint, method, body);
|
|
1153
|
-
const pending = pendingRequests.get(requestKey);
|
|
1154
|
-
if (pending) {
|
|
1155
|
-
logger.debug(`Reusing pending request for ${endpoint}`);
|
|
1156
|
-
return pending.promise;
|
|
1157
|
-
}
|
|
1158
|
-
const requestPromise = apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout)
|
|
1159
|
-
.finally(() => {
|
|
1160
|
-
setTimeout(() => pendingRequests.delete(requestKey), requestDebounceMs);
|
|
1161
|
-
});
|
|
1162
|
-
pendingRequests.set(requestKey, {
|
|
1163
|
-
promise: requestPromise
|
|
1164
|
-
});
|
|
1165
|
-
return await requestPromise;
|
|
1166
|
-
}
|
|
1167
|
-
return apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout);
|
|
1168
|
-
}
|
|
1169
|
-
async function apiCallInternal(endpoint, method, body, maxRetries, baseDelay, timeout) {
|
|
1170
|
-
let lastError = null;
|
|
1171
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1172
|
-
try {
|
|
1173
|
-
return await apiCall(endpoint, method, body, timeout);
|
|
1174
|
-
}
|
|
1175
|
-
catch (error) {
|
|
1176
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1177
|
-
const isRetryable = lastError.message.includes("E001") ||
|
|
1178
|
-
lastError.message.includes("E002") ||
|
|
1179
|
-
lastError.message.includes("timeout") ||
|
|
1180
|
-
lastError.message.includes("ECONNREFUSED") ||
|
|
1181
|
-
lastError.message.includes("ENOTFOUND");
|
|
1182
|
-
if (attempt < maxRetries - 1 && isRetryable) {
|
|
1183
|
-
const delay = baseDelay * Math.pow(2, attempt);
|
|
1184
|
-
logger.warn(`API call failed (attempt ${attempt + 1}/${maxRetries}), retrying in ${delay}ms: ${lastError.message.split(".")[0]}`);
|
|
1185
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
throw lastError;
|
|
1190
|
-
}
|
|
1191
|
-
async function apiCall(endpoint, method = "GET", body, timeout = 30000) {
|
|
1192
|
-
const url = `${getBaseUrl()}${endpoint}`;
|
|
1193
|
-
const controller = new AbortController();
|
|
1194
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1195
|
-
const options = {
|
|
1196
|
-
method,
|
|
1197
|
-
headers: { "Content-Type": "application/json" },
|
|
1198
|
-
signal: controller.signal,
|
|
1199
|
-
};
|
|
1200
|
-
if (body)
|
|
1201
|
-
options.body = JSON.stringify(body);
|
|
1202
|
-
try {
|
|
1203
|
-
const response = await fetch(url, options);
|
|
1204
|
-
const text = await response.text();
|
|
1205
|
-
if (!response.ok) {
|
|
1206
|
-
try {
|
|
1207
|
-
const errorData = JSON.parse(text);
|
|
1208
|
-
throw new Error(errorData.error || errorData.detail || `HTTP ${response.status}`);
|
|
1209
|
-
}
|
|
1210
|
-
catch {
|
|
1211
|
-
throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
if (!text)
|
|
1215
|
-
return {};
|
|
1216
|
-
try {
|
|
1217
|
-
return JSON.parse(text);
|
|
1218
|
-
}
|
|
1219
|
-
catch {
|
|
1220
|
-
throw new Error("Invalid JSON response");
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
catch (error) {
|
|
1224
|
-
throw new Error(formatApiError(error));
|
|
1225
|
-
}
|
|
1226
|
-
finally {
|
|
1227
|
-
clearTimeout(timeoutId);
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
async function searchMemoryWithFallback(args, context) {
|
|
1231
|
-
if (!args || !args.query) {
|
|
1232
|
-
logger.error(`search_memory called with invalid args: ${JSON.stringify(args)}`);
|
|
1233
|
-
return { success: false, error: ERROR_CODES.INVALID_INPUT.message + " Missing 'query' parameter.", errorCode: ERROR_CODES.INVALID_INPUT.code };
|
|
1234
|
-
}
|
|
1235
|
-
if (!isEnabled) {
|
|
1236
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1237
|
-
logger.info("Using builtin memory (plugin disabled)");
|
|
1238
|
-
try {
|
|
1239
|
-
const results = await builtinMemory.search(args.query, args.top_k || 3);
|
|
1240
|
-
return { success: true, data: results };
|
|
1241
|
-
}
|
|
1242
|
-
catch (error) {
|
|
1243
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1244
|
-
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1248
|
-
}
|
|
1249
|
-
try {
|
|
1250
|
-
const result = await apiCallWithRetry("/search", "POST", {
|
|
1251
|
-
query: args.query,
|
|
1252
|
-
top_k: args.top_k || 3,
|
|
1253
|
-
});
|
|
1254
|
-
return { success: true, data: result.results };
|
|
1255
|
-
}
|
|
1256
|
-
catch (error) {
|
|
1257
|
-
const message = formatApiError(error);
|
|
1258
|
-
logger.error(`search_memory failed: ${message}`);
|
|
1259
|
-
return { success: false, error: message };
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
async function storeEventWithFallback(args, context) {
|
|
1263
|
-
if (!isEnabled) {
|
|
1264
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1265
|
-
logger.info("Using builtin memory (plugin disabled)");
|
|
1266
|
-
try {
|
|
1267
|
-
const id = await builtinMemory.store(args.summary, {
|
|
1268
|
-
entities: args.entities,
|
|
1269
|
-
outcome: args.outcome,
|
|
1270
|
-
relations: args.relations
|
|
1271
|
-
});
|
|
1272
|
-
return { success: true, data: { event_id: id } };
|
|
1273
|
-
}
|
|
1274
|
-
catch (error) {
|
|
1275
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1276
|
-
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1280
|
-
}
|
|
1281
|
-
try {
|
|
1282
|
-
const result = await apiCallWithRetry("/event", "POST", {
|
|
1283
|
-
summary: args.summary,
|
|
1284
|
-
entities: args.entities,
|
|
1285
|
-
outcome: args.outcome,
|
|
1286
|
-
relations: args.relations,
|
|
1287
|
-
});
|
|
1288
|
-
return { success: true, data: result };
|
|
1289
|
-
}
|
|
1290
|
-
catch (error) {
|
|
1291
|
-
const message = formatApiError(error);
|
|
1292
|
-
logger.error(`store_event failed: ${message}`);
|
|
1293
|
-
return { success: false, error: message };
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
async function queryGraph(args, _context) {
|
|
1297
|
-
if (!args || !args.entity) {
|
|
1298
|
-
logger.error(`query_graph called with invalid args: ${JSON.stringify(args)}`);
|
|
1299
|
-
return { success: false, error: ERROR_CODES.INVALID_INPUT.message + " Missing 'entity' parameter.", errorCode: ERROR_CODES.INVALID_INPUT.code };
|
|
1300
|
-
}
|
|
1301
|
-
if (!isEnabled) {
|
|
1302
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1303
|
-
}
|
|
1304
|
-
try {
|
|
1305
|
-
const result = await apiCallWithRetry("/graph/query", "POST", { entity: args.entity });
|
|
1306
|
-
return { success: true, data: result.graph };
|
|
1307
|
-
}
|
|
1308
|
-
catch (error) {
|
|
1309
|
-
const message = formatApiError(error);
|
|
1310
|
-
logger.error(`query_graph failed: ${message}`);
|
|
1311
|
-
return { success: false, error: message };
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
async function getHotContext(args, _context) {
|
|
1315
|
-
if (!isEnabled) {
|
|
1316
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1317
|
-
}
|
|
1318
|
-
try {
|
|
1319
|
-
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 20;
|
|
1320
|
-
const result = await apiCallWithRetry(`/hot-context?limit=${limit}`, "GET");
|
|
1321
|
-
return { success: true, data: result.context };
|
|
1322
|
-
}
|
|
1323
|
-
catch (error) {
|
|
1324
|
-
const message = formatApiError(error);
|
|
1325
|
-
logger.error(`get_hot_context failed: ${message}`);
|
|
1326
|
-
return { success: false, error: message };
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
async function getAutoContext(args, context) {
|
|
1330
|
-
if (!isEnabled) {
|
|
1331
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1332
|
-
}
|
|
1333
|
-
const now = Date.now();
|
|
1334
|
-
const result = {};
|
|
1335
|
-
const sessionId = resolveSessionId(context);
|
|
1336
|
-
clearStaleAutoSearchCache(now);
|
|
1337
|
-
const sessionCache = autoSearchCacheBySession.get(sessionId);
|
|
1338
|
-
if (sessionCache) {
|
|
1339
|
-
result.auto_search = {
|
|
1340
|
-
query: sessionCache.query,
|
|
1341
|
-
results: sessionCache.results,
|
|
1342
|
-
age_seconds: Math.floor((now - sessionCache.timestamp) / 1000),
|
|
1343
|
-
};
|
|
1344
|
-
}
|
|
1345
|
-
if (args.include_hot !== false) {
|
|
1346
|
-
try {
|
|
1347
|
-
const hotResult = await apiCallWithRetry("/hot-context", "GET");
|
|
1348
|
-
result.hot_context = hotResult.context;
|
|
1349
|
-
}
|
|
1350
|
-
catch (error) {
|
|
1351
|
-
logger.debug(`Failed to get hot context: ${formatApiError(error)}`);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
if (!result.auto_search && !result.hot_context) {
|
|
1355
|
-
return {
|
|
1356
|
-
success: true,
|
|
1357
|
-
data: {
|
|
1358
|
-
message: "No session-scoped auto-search results cached and hot context unavailable",
|
|
1359
|
-
suggestion: "Send a user message in this session or call get_hot_context."
|
|
1360
|
-
}
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
return { success: true, data: result };
|
|
1364
|
-
}
|
|
1365
|
-
async function reflectMemory(_args, _context) {
|
|
1366
|
-
if (!isEnabled) {
|
|
1367
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1368
|
-
}
|
|
1369
|
-
try {
|
|
1370
|
-
await apiCallWithRetry("/reflect", "POST", undefined, {
|
|
1371
|
-
timeout: 120000,
|
|
1372
|
-
maxRetries: 2,
|
|
1373
|
-
});
|
|
1374
|
-
return { success: true };
|
|
1375
|
-
}
|
|
1376
|
-
catch (error) {
|
|
1377
|
-
const message = formatApiError(error);
|
|
1378
|
-
logger.error(`reflect_memory failed: ${message}`);
|
|
1379
|
-
return { success: false, error: message };
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
async function syncMemory(_args, _context) {
|
|
1383
|
-
if (!isEnabled) {
|
|
1384
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1385
973
|
}
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
maxRetries: 2,
|
|
1390
|
-
});
|
|
1391
|
-
return { success: true };
|
|
1392
|
-
}
|
|
1393
|
-
catch (error) {
|
|
1394
|
-
const message = formatApiError(error);
|
|
1395
|
-
logger.error(`sync_memory failed: ${message}`);
|
|
1396
|
-
return { success: false, error: message };
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
async function promoteMemory(_args, _context) {
|
|
1400
|
-
if (!isEnabled) {
|
|
1401
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1402
|
-
}
|
|
1403
|
-
try {
|
|
1404
|
-
await apiCallWithRetry("/promote", "POST", undefined, {
|
|
1405
|
-
timeout: 120000,
|
|
1406
|
-
maxRetries: 2,
|
|
1407
|
-
});
|
|
1408
|
-
return { success: true };
|
|
1409
|
-
}
|
|
1410
|
-
catch (error) {
|
|
1411
|
-
const message = formatApiError(error);
|
|
1412
|
-
logger.error(`promote_memory failed: ${message}`);
|
|
1413
|
-
return { success: false, error: message };
|
|
974
|
+
const intervalMinutes = Math.max(5, config.autoReflectIntervalMinutes ?? 30);
|
|
975
|
+
if (autoReflectInterval) {
|
|
976
|
+
clearInterval(autoReflectInterval);
|
|
1414
977
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
if (
|
|
978
|
+
lastAutoReflectArchiveMarker = getArchiveMarker();
|
|
979
|
+
lastAutoReflectRunAt = Date.now();
|
|
980
|
+
autoReflectInterval = setInterval(async () => {
|
|
981
|
+
if (!isEnabled)
|
|
982
|
+
return;
|
|
983
|
+
const currentMarker = getArchiveMarker();
|
|
984
|
+
if (currentMarker !== lastAutoReflectArchiveMarker) {
|
|
985
|
+
lastAutoReflectArchiveMarker = currentMarker;
|
|
1419
986
|
try {
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
987
|
+
const result = await resolveEngine().reflectMemory({}, { agentId: "scheduler", workspaceId: "default" });
|
|
988
|
+
if (result.success) {
|
|
989
|
+
logger.info("Auto-reflect completed successfully");
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
logger.warn(`Auto-reflect failed: ${result.error}`);
|
|
993
|
+
}
|
|
1426
994
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
}
|
|
1430
|
-
try {
|
|
1431
|
-
await apiCallWithRetry(`/memory/${args.memory_id}`, "DELETE");
|
|
1432
|
-
return { success: true };
|
|
1433
|
-
}
|
|
1434
|
-
catch (error) {
|
|
1435
|
-
const message = formatApiError(error);
|
|
1436
|
-
logger.error(`delete_memory failed: ${message}`);
|
|
1437
|
-
return { success: false, error: message };
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
async function updateMemory(args, _context) {
|
|
1441
|
-
if (!isEnabled) {
|
|
1442
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1443
|
-
}
|
|
1444
|
-
try {
|
|
1445
|
-
await apiCallWithRetry(`/memory/${args.memory_id}`, "PATCH", {
|
|
1446
|
-
text: args.text,
|
|
1447
|
-
type: args.type,
|
|
1448
|
-
weight: args.weight,
|
|
1449
|
-
});
|
|
1450
|
-
return { success: true };
|
|
1451
|
-
}
|
|
1452
|
-
catch (error) {
|
|
1453
|
-
const message = formatApiError(error);
|
|
1454
|
-
logger.error(`update_memory failed: ${message}`);
|
|
1455
|
-
return { success: false, error: message };
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
async function cleanupMemories(args, _context) {
|
|
1459
|
-
if (!isEnabled) {
|
|
1460
|
-
return { success: false, error: ERROR_CODES.PLUGIN_DISABLED.message, errorCode: ERROR_CODES.PLUGIN_DISABLED.code };
|
|
1461
|
-
}
|
|
1462
|
-
try {
|
|
1463
|
-
const result = await apiCallWithRetry("/cleanup", "POST", {
|
|
1464
|
-
days_old: args.days_old || 90,
|
|
1465
|
-
memory_type: args.memory_type,
|
|
1466
|
-
});
|
|
1467
|
-
return { success: true, data: { deletedCount: result.deleted_count } };
|
|
1468
|
-
}
|
|
1469
|
-
catch (error) {
|
|
1470
|
-
const message = formatApiError(error);
|
|
1471
|
-
logger.error(`cleanup_memories failed: ${message}`);
|
|
1472
|
-
return { success: false, error: message };
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
async function runDiagnostics(_args, _context) {
|
|
1476
|
-
if (!isEnabled) {
|
|
1477
|
-
return {
|
|
1478
|
-
success: true,
|
|
1479
|
-
data: {
|
|
1480
|
-
status: "disabled",
|
|
1481
|
-
message: "Cortex Memory plugin is disabled",
|
|
1482
|
-
suggestion: "Enable the plugin using 'openclaw plugins enable cortex-memory'"
|
|
995
|
+
catch (e) {
|
|
996
|
+
logger.error(`Auto-reflect error: ${e instanceof Error ? e.message : String(e)}`);
|
|
1483
997
|
}
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
try {
|
|
1487
|
-
const result = await apiCallWithRetry("/doctor", "GET");
|
|
1488
|
-
return { success: true, data: result };
|
|
1489
|
-
}
|
|
1490
|
-
catch (error) {
|
|
1491
|
-
const message = formatApiError(error);
|
|
1492
|
-
logger.error(`diagnostics failed: ${message}`);
|
|
1493
|
-
return { success: false, error: message };
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
async function getPluginStatus(_args, _context) {
|
|
1497
|
-
return {
|
|
1498
|
-
success: true,
|
|
1499
|
-
data: {
|
|
1500
|
-
enabled: isEnabled,
|
|
1501
|
-
service_running: pythonProcess !== null,
|
|
1502
|
-
fallback_enabled: config?.fallbackToBuiltin ?? true,
|
|
1503
|
-
builtin_memory_available: builtinMemory !== null,
|
|
1504
|
-
engine_mode: config?.engineMode ?? "python",
|
|
1505
998
|
}
|
|
1506
|
-
};
|
|
999
|
+
}, intervalMinutes * 60 * 1000);
|
|
1000
|
+
logger.info(`Auto-reflect scheduler started (interval: ${intervalMinutes} minutes)`);
|
|
1507
1001
|
}
|
|
1508
|
-
|
|
1509
|
-
if (
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
if (!normalized)
|
|
1513
|
-
return;
|
|
1514
|
-
const { text, role, source } = normalized;
|
|
1515
|
-
const sessionId = resolveSessionId(context, payload);
|
|
1516
|
-
try {
|
|
1517
|
-
const writeResult = await apiCallWithRetry("/write", "POST", {
|
|
1518
|
-
text,
|
|
1519
|
-
source,
|
|
1520
|
-
role,
|
|
1521
|
-
session_id: sessionId
|
|
1522
|
-
});
|
|
1523
|
-
if (writeResult.status === "ok") {
|
|
1524
|
-
logger.info(`Stored ${role} message for session ${sessionId}`);
|
|
1525
|
-
}
|
|
1526
|
-
else {
|
|
1527
|
-
logger.debug(`Write skipped for session ${sessionId}: ${writeResult.reason || writeResult.status || "unknown"}`);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
catch (error) {
|
|
1531
|
-
logger.warn(`Failed to store message: ${formatApiError(error)}`);
|
|
1532
|
-
}
|
|
1533
|
-
if (role === "user" && text.length > 5) {
|
|
1534
|
-
try {
|
|
1535
|
-
const searchResult = await apiCallWithRetry("/search", "POST", {
|
|
1536
|
-
query: text,
|
|
1537
|
-
top_k: 3,
|
|
1538
|
-
session_id: sessionId,
|
|
1539
|
-
});
|
|
1540
|
-
if (searchResult.results && searchResult.results.length > 0) {
|
|
1541
|
-
setSessionAutoSearchCache(sessionId, text, searchResult.results);
|
|
1542
|
-
logger.info(`Auto-search cached ${searchResult.results.length} results for context`);
|
|
1543
|
-
}
|
|
1544
|
-
else if (searchResult.skipped) {
|
|
1545
|
-
logger.debug(`Auto-search skipped for session ${sessionId}: ${searchResult.reason || "query filtered"}`);
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
catch (error) {
|
|
1549
|
-
logger.debug(`Auto-search skipped: ${formatApiError(error)}`);
|
|
1550
|
-
}
|
|
1002
|
+
function stopAutoReflectScheduler() {
|
|
1003
|
+
if (autoReflectInterval) {
|
|
1004
|
+
clearInterval(autoReflectInterval);
|
|
1005
|
+
autoReflectInterval = null;
|
|
1551
1006
|
}
|
|
1552
1007
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
return;
|
|
1556
|
-
const sessionId = resolveSessionId(context, payload);
|
|
1557
|
-
try {
|
|
1558
|
-
const endResult = await apiCallWithRetry("/session-end", "POST", {
|
|
1559
|
-
session_id: sessionId,
|
|
1560
|
-
sync_records: config?.autoSync ?? true,
|
|
1561
|
-
});
|
|
1562
|
-
logger.info(`Session ${sessionId} ended, generated ${endResult.events_generated} events`);
|
|
1563
|
-
}
|
|
1564
|
-
catch (error) {
|
|
1565
|
-
logger.warn(`Failed to end session: ${formatApiError(error)}`);
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
async function onTimerPythonHandler(payload, _context) {
|
|
1569
|
-
if (!isEnabled)
|
|
1570
|
-
return;
|
|
1571
|
-
const data = payload;
|
|
1572
|
-
const action = data.action;
|
|
1573
|
-
try {
|
|
1574
|
-
if (action === "sync") {
|
|
1575
|
-
await apiCallWithRetry("/sync", "POST", undefined, {
|
|
1576
|
-
timeout: 300000,
|
|
1577
|
-
maxRetries: 2,
|
|
1578
|
-
});
|
|
1579
|
-
logger.info("Scheduled sync complete");
|
|
1580
|
-
}
|
|
1581
|
-
else if (action === "reflect" || (config?.autoReflect && !action)) {
|
|
1582
|
-
await apiCallWithRetry("/reflect", "POST", undefined, {
|
|
1583
|
-
timeout: 120000,
|
|
1584
|
-
maxRetries: 2,
|
|
1585
|
-
});
|
|
1586
|
-
logger.info("Scheduled reflection complete");
|
|
1587
|
-
}
|
|
1588
|
-
else if (action === "promote") {
|
|
1589
|
-
await apiCallWithRetry("/promote", "POST", undefined, {
|
|
1590
|
-
timeout: 120000,
|
|
1591
|
-
maxRetries: 2,
|
|
1592
|
-
});
|
|
1593
|
-
logger.info("Scheduled promotion complete");
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
catch (error) {
|
|
1597
|
-
logger.warn(`Timer action failed: ${formatApiError(error)}`);
|
|
1598
|
-
}
|
|
1008
|
+
function logLifecycle(event, data) {
|
|
1009
|
+
logger.info(`[Lifecycle] ${event}${data ? `: ${JSON.stringify(sanitizeForLogging(data))}` : ""}`);
|
|
1599
1010
|
}
|
|
1600
1011
|
async function onMessageHandler(payload, context) {
|
|
1601
1012
|
const sessionId = resolveSessionId(context, payload);
|
|
1602
1013
|
if (isInternalSession(sessionId)) {
|
|
1603
1014
|
return;
|
|
1604
1015
|
}
|
|
1605
|
-
|
|
1016
|
+
const existing = messageHookInFlightBySession.get(sessionId);
|
|
1017
|
+
if (existing) {
|
|
1018
|
+
logger.debug(`onMessage hook dedup: skip while previous run is in-flight session=${sessionId}`);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
let task;
|
|
1022
|
+
task = resolveEngine().onMessage(payload, context).finally(() => {
|
|
1023
|
+
if (messageHookInFlightBySession.get(sessionId) === task) {
|
|
1024
|
+
messageHookInFlightBySession.delete(sessionId);
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
messageHookInFlightBySession.set(sessionId, task);
|
|
1028
|
+
await runWithTimeout(task, HOOK_GUARD_TIMEOUT_MS, "onMessage hook");
|
|
1606
1029
|
}
|
|
1607
1030
|
async function onSessionEndHandler(payload, context) {
|
|
1608
1031
|
const sessionId = resolveSessionId(context, payload);
|
|
@@ -1617,65 +1040,196 @@ async function onTimerHandler(payload, context) {
|
|
|
1617
1040
|
function registerTools() {
|
|
1618
1041
|
if (!api)
|
|
1619
1042
|
return;
|
|
1043
|
+
const apiObj = api;
|
|
1044
|
+
logger.info(`registerTools API capability: registerTool=${typeof apiObj.registerTool === "function"}, registerTools=${typeof apiObj.registerTools === "function"}, tools.register=${typeof apiObj.tools?.register === "function"}`);
|
|
1620
1045
|
const tools = [
|
|
1621
1046
|
{
|
|
1622
|
-
name: "search_memory",
|
|
1623
|
-
description: "Search long-term memory for relevant information",
|
|
1047
|
+
name: "search_memory",
|
|
1048
|
+
description: "Search long-term memory for relevant information",
|
|
1049
|
+
parameters: {
|
|
1050
|
+
type: "object",
|
|
1051
|
+
properties: {
|
|
1052
|
+
query: { type: "string", description: "Search query" },
|
|
1053
|
+
top_k: { type: "integer", description: "Number of results to return" },
|
|
1054
|
+
fusion_mode: {
|
|
1055
|
+
type: "string",
|
|
1056
|
+
enum: ["auto", "authoritative", "candidates", "off"],
|
|
1057
|
+
description: "LLM fusion behavior: auto honors config, authoritative returns fused answer only, candidates returns fusion plus ranked candidates, off disables fusion",
|
|
1058
|
+
},
|
|
1059
|
+
track_hits: {
|
|
1060
|
+
type: "boolean",
|
|
1061
|
+
description: "Whether to update anti-decay hit statistics; set false for diagnostics or benchmarks",
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
required: ["query"],
|
|
1065
|
+
additionalProperties: false,
|
|
1066
|
+
},
|
|
1067
|
+
execute: async (params) => {
|
|
1068
|
+
logger.info(`search_memory execute called with params: ${JSON.stringify(params)}`);
|
|
1069
|
+
logger.info(`params.args: ${JSON.stringify(params.args)}`);
|
|
1070
|
+
const args = params.args || params;
|
|
1071
|
+
logger.info(`args after extraction: ${JSON.stringify(args)}`);
|
|
1072
|
+
return resolveEngine().searchMemory(args, params.context);
|
|
1073
|
+
},
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
name: "store_event",
|
|
1077
|
+
description: "Store a new event in memory",
|
|
1078
|
+
parameters: {
|
|
1079
|
+
type: "object",
|
|
1080
|
+
properties: {
|
|
1081
|
+
summary: { type: "string", description: "Event summary" },
|
|
1082
|
+
cause: { type: "string", description: "What triggered the event (task/request/problem statement)" },
|
|
1083
|
+
process: { type: "string", description: "How the task was handled (steps/attempts/iterations)" },
|
|
1084
|
+
result: { type: "string", description: "Final result and acceptance outcome" },
|
|
1085
|
+
entities: {
|
|
1086
|
+
type: "array",
|
|
1087
|
+
description: "Involved entities",
|
|
1088
|
+
items: {
|
|
1089
|
+
oneOf: [
|
|
1090
|
+
{ type: "string" },
|
|
1091
|
+
{
|
|
1092
|
+
type: "object",
|
|
1093
|
+
properties: {
|
|
1094
|
+
id: { type: "string" },
|
|
1095
|
+
name: { type: "string" },
|
|
1096
|
+
type: { type: "string" },
|
|
1097
|
+
},
|
|
1098
|
+
additionalProperties: false,
|
|
1099
|
+
},
|
|
1100
|
+
],
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
entity_types: {
|
|
1104
|
+
type: "object",
|
|
1105
|
+
description: "Entity type map, key is entity name and value is type",
|
|
1106
|
+
additionalProperties: { type: "string" },
|
|
1107
|
+
},
|
|
1108
|
+
outcome: { type: "string", description: "Event outcome" },
|
|
1109
|
+
relations: {
|
|
1110
|
+
type: "array",
|
|
1111
|
+
description: "Entity relationships",
|
|
1112
|
+
items: {
|
|
1113
|
+
oneOf: [
|
|
1114
|
+
{ type: "string" },
|
|
1115
|
+
{
|
|
1116
|
+
type: "object",
|
|
1117
|
+
properties: {
|
|
1118
|
+
source: { type: "string" },
|
|
1119
|
+
target: { type: "string" },
|
|
1120
|
+
type: { type: "string" },
|
|
1121
|
+
evidence_span: { type: "string" },
|
|
1122
|
+
confidence: { type: "number" },
|
|
1123
|
+
},
|
|
1124
|
+
required: ["source", "target"],
|
|
1125
|
+
additionalProperties: false,
|
|
1126
|
+
},
|
|
1127
|
+
],
|
|
1128
|
+
}
|
|
1129
|
+
},
|
|
1130
|
+
},
|
|
1131
|
+
required: ["summary"],
|
|
1132
|
+
additionalProperties: false,
|
|
1133
|
+
},
|
|
1134
|
+
execute: async (params) => {
|
|
1135
|
+
const args = params.args || params;
|
|
1136
|
+
return resolveEngine().storeEvent(args, params.context);
|
|
1137
|
+
},
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
name: "query_graph",
|
|
1141
|
+
description: "Query memory graph for entity relationships",
|
|
1142
|
+
parameters: {
|
|
1143
|
+
type: "object",
|
|
1144
|
+
properties: {
|
|
1145
|
+
entity: { type: "string", description: "Entity name" },
|
|
1146
|
+
rel: { type: "string", description: "Optional relation type filter" },
|
|
1147
|
+
dir: {
|
|
1148
|
+
type: "string",
|
|
1149
|
+
description: "Relation direction filter",
|
|
1150
|
+
enum: ["incoming", "outgoing", "both"],
|
|
1151
|
+
},
|
|
1152
|
+
path_to: { type: "string", description: "Find path from entity to this target entity" },
|
|
1153
|
+
max_depth: { type: "integer", description: "Path query max depth (2~4)" },
|
|
1154
|
+
},
|
|
1155
|
+
required: ["entity"],
|
|
1156
|
+
additionalProperties: false,
|
|
1157
|
+
},
|
|
1158
|
+
execute: async (params) => {
|
|
1159
|
+
const args = params.args || params;
|
|
1160
|
+
return resolveEngine().queryGraph(args, params.context);
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
name: "export_graph_view",
|
|
1165
|
+
description: "Export status-aware graph view and optionally write wiki graph snapshots",
|
|
1166
|
+
parameters: {
|
|
1167
|
+
type: "object",
|
|
1168
|
+
properties: {
|
|
1169
|
+
write_snapshot: {
|
|
1170
|
+
type: "boolean",
|
|
1171
|
+
description: "When true (default), write wiki/graph/view.json, timeline.jsonl and Mermaid network snapshots",
|
|
1172
|
+
},
|
|
1173
|
+
},
|
|
1174
|
+
required: [],
|
|
1175
|
+
additionalProperties: false,
|
|
1176
|
+
},
|
|
1177
|
+
execute: async (params) => {
|
|
1178
|
+
const args = params.args || params;
|
|
1179
|
+
return resolveEngine().exportGraphView(args, params.context);
|
|
1180
|
+
},
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
name: "lint_memory_wiki",
|
|
1184
|
+
description: "Run wiki memory lint checks for graph consistency, evidence coverage, page quality, and structured repair guidance",
|
|
1624
1185
|
parameters: {
|
|
1625
1186
|
type: "object",
|
|
1626
|
-
properties: {
|
|
1627
|
-
|
|
1628
|
-
top_k: { type: "integer", description: "Number of results to return" },
|
|
1629
|
-
},
|
|
1630
|
-
required: ["query"],
|
|
1187
|
+
properties: {},
|
|
1188
|
+
required: [],
|
|
1631
1189
|
additionalProperties: false,
|
|
1632
1190
|
},
|
|
1633
1191
|
execute: async (params) => {
|
|
1634
1192
|
const args = params.args || params;
|
|
1635
|
-
return resolveEngine().
|
|
1193
|
+
return resolveEngine().lintMemoryWiki(args, params.context);
|
|
1636
1194
|
},
|
|
1637
1195
|
},
|
|
1638
1196
|
{
|
|
1639
|
-
name: "
|
|
1640
|
-
description: "
|
|
1197
|
+
name: "list_graph_conflicts",
|
|
1198
|
+
description: "List pending/handled graph memory conflicts that require user confirmation",
|
|
1641
1199
|
parameters: {
|
|
1642
1200
|
type: "object",
|
|
1643
1201
|
properties: {
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
description: "
|
|
1648
|
-
items: { type: "string" }
|
|
1649
|
-
},
|
|
1650
|
-
outcome: { type: "string", description: "Event outcome" },
|
|
1651
|
-
relations: {
|
|
1652
|
-
type: "array",
|
|
1653
|
-
description: "Entity relationships",
|
|
1654
|
-
items: { type: "string" }
|
|
1202
|
+
status: {
|
|
1203
|
+
type: "string",
|
|
1204
|
+
enum: ["pending", "accepted", "rejected", "all"],
|
|
1205
|
+
description: "Filter conflict status",
|
|
1655
1206
|
},
|
|
1207
|
+
limit: { type: "integer", description: "Maximum returned conflicts" },
|
|
1656
1208
|
},
|
|
1657
|
-
required: [
|
|
1209
|
+
required: [],
|
|
1658
1210
|
additionalProperties: false,
|
|
1659
1211
|
},
|
|
1660
1212
|
execute: async (params) => {
|
|
1661
1213
|
const args = params.args || params;
|
|
1662
|
-
return resolveEngine().
|
|
1214
|
+
return resolveEngine().listGraphConflicts(args, params.context);
|
|
1663
1215
|
},
|
|
1664
1216
|
},
|
|
1665
1217
|
{
|
|
1666
|
-
name: "
|
|
1667
|
-
description: "
|
|
1218
|
+
name: "resolve_graph_conflict",
|
|
1219
|
+
description: "Resolve a graph conflict by accepting or rejecting the new candidate fact",
|
|
1668
1220
|
parameters: {
|
|
1669
1221
|
type: "object",
|
|
1670
1222
|
properties: {
|
|
1671
|
-
|
|
1223
|
+
conflict_id: { type: "string", description: "Conflict ID from list_graph_conflicts" },
|
|
1224
|
+
action: { type: "string", enum: ["accept", "reject"], description: "Resolution action" },
|
|
1225
|
+
note: { type: "string", description: "Optional note for audit trail" },
|
|
1672
1226
|
},
|
|
1673
|
-
required: ["
|
|
1227
|
+
required: ["conflict_id", "action"],
|
|
1674
1228
|
additionalProperties: false,
|
|
1675
1229
|
},
|
|
1676
1230
|
execute: async (params) => {
|
|
1677
1231
|
const args = params.args || params;
|
|
1678
|
-
return resolveEngine().
|
|
1232
|
+
return resolveEngine().resolveGraphConflict(args, params.context);
|
|
1679
1233
|
},
|
|
1680
1234
|
},
|
|
1681
1235
|
{
|
|
@@ -1775,7 +1329,7 @@ function registerTools() {
|
|
|
1775
1329
|
},
|
|
1776
1330
|
},
|
|
1777
1331
|
{
|
|
1778
|
-
name:
|
|
1332
|
+
name: TOOL_NAME_CORTEX_DIAGNOSTICS,
|
|
1779
1333
|
description: "Check memory system status",
|
|
1780
1334
|
parameters: {
|
|
1781
1335
|
type: "object",
|
|
@@ -1785,43 +1339,242 @@ function registerTools() {
|
|
|
1785
1339
|
},
|
|
1786
1340
|
execute: async (params) => {
|
|
1787
1341
|
const args = params.args || params;
|
|
1788
|
-
|
|
1342
|
+
const result = await resolveEngine().runDiagnostics(args, params.context);
|
|
1343
|
+
return withToolVisibilityDiagnostics(result, params.context);
|
|
1344
|
+
},
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
name: TOOL_NAME_DIAGNOSTICS_LEGACY,
|
|
1348
|
+
description: "Legacy alias for cortex_diagnostics",
|
|
1349
|
+
optional: true,
|
|
1350
|
+
parameters: {
|
|
1351
|
+
type: "object",
|
|
1352
|
+
properties: {},
|
|
1353
|
+
required: [],
|
|
1354
|
+
additionalProperties: false,
|
|
1355
|
+
},
|
|
1356
|
+
execute: async (params) => {
|
|
1357
|
+
const args = params.args || params;
|
|
1358
|
+
const result = await resolveEngine().runDiagnostics(args, params.context);
|
|
1359
|
+
return withToolVisibilityDiagnostics(result, params.context);
|
|
1789
1360
|
},
|
|
1790
1361
|
},
|
|
1791
1362
|
];
|
|
1363
|
+
let successCount = 0;
|
|
1792
1364
|
for (const tool of tools) {
|
|
1793
|
-
|
|
1794
|
-
|
|
1365
|
+
try {
|
|
1366
|
+
registerToolCompat(tool);
|
|
1367
|
+
registeredTools.push(tool.name);
|
|
1368
|
+
successCount += 1;
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
logger.error(`Failed to register tool ${tool.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
logger.info(`registerTools completed: ${successCount}/${tools.length} tools registered`);
|
|
1375
|
+
}
|
|
1376
|
+
function sanitizeToolParametersSchemaValue(schema) {
|
|
1377
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
1378
|
+
return {};
|
|
1379
|
+
}
|
|
1380
|
+
const source = schema;
|
|
1381
|
+
const target = {};
|
|
1382
|
+
if (typeof source.type === "string") {
|
|
1383
|
+
target.type = source.type;
|
|
1795
1384
|
}
|
|
1385
|
+
if (typeof source.description === "string" && source.description.trim()) {
|
|
1386
|
+
target.description = source.description;
|
|
1387
|
+
}
|
|
1388
|
+
if (Array.isArray(source.enum)) {
|
|
1389
|
+
const values = source.enum.filter(item => typeof item === "string" || typeof item === "number" || typeof item === "boolean" || item === null);
|
|
1390
|
+
if (values.length > 0) {
|
|
1391
|
+
target.enum = values;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
if (source.properties && typeof source.properties === "object" && !Array.isArray(source.properties)) {
|
|
1395
|
+
const sanitizedProperties = {};
|
|
1396
|
+
for (const [key, value] of Object.entries(source.properties)) {
|
|
1397
|
+
sanitizedProperties[key] = sanitizeToolParametersSchemaValue(value);
|
|
1398
|
+
}
|
|
1399
|
+
target.properties = sanitizedProperties;
|
|
1400
|
+
}
|
|
1401
|
+
if (Array.isArray(source.required)) {
|
|
1402
|
+
const required = source.required.filter(item => typeof item === "string");
|
|
1403
|
+
if (required.length > 0) {
|
|
1404
|
+
target.required = required;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (source.items && typeof source.items === "object" && !Array.isArray(source.items)) {
|
|
1408
|
+
target.items = sanitizeToolParametersSchemaValue(source.items);
|
|
1409
|
+
}
|
|
1410
|
+
if (typeof source.additionalProperties === "boolean") {
|
|
1411
|
+
target.additionalProperties = source.additionalProperties;
|
|
1412
|
+
}
|
|
1413
|
+
else if (source.additionalProperties && typeof source.additionalProperties === "object") {
|
|
1414
|
+
target.additionalProperties = true;
|
|
1415
|
+
}
|
|
1416
|
+
return target;
|
|
1417
|
+
}
|
|
1418
|
+
function sanitizeToolParametersSchema(schema) {
|
|
1419
|
+
const sanitized = sanitizeToolParametersSchemaValue(schema);
|
|
1420
|
+
if (sanitized.type !== "object") {
|
|
1421
|
+
sanitized.type = "object";
|
|
1422
|
+
}
|
|
1423
|
+
if (!sanitized.properties || typeof sanitized.properties !== "object" || Array.isArray(sanitized.properties)) {
|
|
1424
|
+
sanitized.properties = {};
|
|
1425
|
+
}
|
|
1426
|
+
if (!Array.isArray(sanitized.required)) {
|
|
1427
|
+
sanitized.required = [];
|
|
1428
|
+
}
|
|
1429
|
+
if (typeof sanitized.additionalProperties !== "boolean") {
|
|
1430
|
+
sanitized.additionalProperties = false;
|
|
1431
|
+
}
|
|
1432
|
+
return sanitized;
|
|
1796
1433
|
}
|
|
1797
1434
|
function registerToolCompat(tool) {
|
|
1798
1435
|
if (!api)
|
|
1799
1436
|
return;
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1437
|
+
const normalizeContext = (value) => {
|
|
1438
|
+
const contextObj = asRecord(value) || {};
|
|
1439
|
+
return {
|
|
1440
|
+
agentId: firstString([contextObj.agentId, contextObj.agent_id]) || "unknown-agent",
|
|
1441
|
+
workspaceId: firstString([contextObj.workspaceId, contextObj.workspace_id]) || "default",
|
|
1442
|
+
sessionId: firstString([contextObj.sessionId, contextObj.session_id]) || undefined,
|
|
1443
|
+
};
|
|
1444
|
+
};
|
|
1445
|
+
const normalizeInvocation = (...params) => {
|
|
1446
|
+
logger.info(`normalizeInvocation called with params: ${JSON.stringify(params)}`);
|
|
1447
|
+
if (params.length === 1) {
|
|
1448
|
+
const first = params[0];
|
|
1449
|
+
const firstObj = asRecord(first);
|
|
1450
|
+
if (firstObj && ("context" in firstObj || "args" in firstObj)) {
|
|
1451
|
+
const explicitArgs = asRecord(firstObj.args);
|
|
1452
|
+
if (explicitArgs) {
|
|
1453
|
+
logger.info(`normalizeInvocation: single param with explicit args: ${JSON.stringify(explicitArgs)}`);
|
|
1454
|
+
return {
|
|
1455
|
+
args: explicitArgs,
|
|
1456
|
+
context: normalizeContext(firstObj.context),
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
const directArgs = { ...firstObj };
|
|
1460
|
+
delete directArgs.context;
|
|
1461
|
+
delete directArgs.args;
|
|
1462
|
+
logger.info(`normalizeInvocation: single param with direct args: ${JSON.stringify(directArgs)}`);
|
|
1463
|
+
return {
|
|
1464
|
+
args: directArgs,
|
|
1465
|
+
context: normalizeContext(firstObj.context),
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
if (firstObj) {
|
|
1469
|
+
logger.info(`normalizeInvocation: single param as args: ${JSON.stringify(firstObj)}`);
|
|
1470
|
+
return {
|
|
1471
|
+
args: firstObj,
|
|
1472
|
+
context: normalizeContext(undefined),
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1805
1476
|
const first = params[0];
|
|
1806
1477
|
const second = params[1];
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1478
|
+
const third = params[2];
|
|
1479
|
+
const firstObj = asRecord(first);
|
|
1480
|
+
const secondObj = asRecord(second);
|
|
1481
|
+
if (typeof first === "string" && secondObj) {
|
|
1482
|
+
logger.info(`normalizeInvocation: first is string (tool call ID), second is args: ${JSON.stringify(secondObj)}`);
|
|
1483
|
+
return {
|
|
1484
|
+
args: secondObj,
|
|
1485
|
+
context: normalizeContext(third),
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
if (firstObj && ("context" in firstObj || "args" in firstObj)) {
|
|
1489
|
+
const explicitArgs = asRecord(firstObj.args);
|
|
1490
|
+
if (explicitArgs) {
|
|
1491
|
+
logger.info(`normalizeInvocation: first has explicit args: ${JSON.stringify(explicitArgs)}`);
|
|
1492
|
+
return {
|
|
1493
|
+
args: explicitArgs,
|
|
1494
|
+
context: normalizeContext(firstObj.context),
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
const directArgs = { ...firstObj };
|
|
1498
|
+
delete directArgs.context;
|
|
1499
|
+
delete directArgs.args;
|
|
1500
|
+
logger.info(`normalizeInvocation: first has direct args: ${JSON.stringify(directArgs)}`);
|
|
1501
|
+
return {
|
|
1502
|
+
args: directArgs,
|
|
1503
|
+
context: normalizeContext(firstObj.context),
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
if (firstObj && Object.keys(firstObj).length > 0) {
|
|
1507
|
+
logger.info(`normalizeInvocation: first is args, second is context: ${JSON.stringify(firstObj)}`);
|
|
1508
|
+
return {
|
|
1509
|
+
args: firstObj,
|
|
1510
|
+
context: normalizeContext(second),
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
logger.info(`normalizeInvocation: fallback to firstObj as args: ${JSON.stringify(firstObj)}`);
|
|
1514
|
+
return {
|
|
1515
|
+
args: firstObj || {},
|
|
1516
|
+
context: normalizeContext(second),
|
|
1517
|
+
};
|
|
1518
|
+
};
|
|
1519
|
+
const invoke = async (...params) => {
|
|
1520
|
+
const traceId = createToolTraceId(tool.name);
|
|
1521
|
+
const startedAt = Date.now();
|
|
1522
|
+
logger.info(`[ToolTrace] start traceId=${traceId} tool=${tool.name} paramCount=${params.length}`);
|
|
1523
|
+
const normalized = normalizeInvocation(...params);
|
|
1524
|
+
logger.debug(`[ToolTrace] normalized traceId=${traceId} tool=${tool.name} args=${formatUnknownForLog(sanitizeForLogging(normalized.args))} context=${formatUnknownForLog(sanitizeForLogging(normalized.context))}`);
|
|
1525
|
+
try {
|
|
1526
|
+
const result = await tool.execute({
|
|
1527
|
+
args: normalized.args,
|
|
1528
|
+
context: normalized.context,
|
|
1811
1529
|
});
|
|
1530
|
+
const durationMs = Date.now() - startedAt;
|
|
1531
|
+
logger.info(`[ToolTrace] success traceId=${traceId} tool=${tool.name} durationMs=${durationMs} resultSuccess=${result.success}`);
|
|
1532
|
+
if (!result.success) {
|
|
1533
|
+
logger.error(`[ToolTrace] tool_failure traceId=${traceId} tool=${tool.name} error=${truncateForLog(result.error || "unknown_error")} errorCode=${result.errorCode || "none"} args=${formatUnknownForLog(sanitizeForLogging(normalized.args))}`);
|
|
1534
|
+
}
|
|
1535
|
+
return toAgentToolResult(result, traceId);
|
|
1536
|
+
}
|
|
1537
|
+
catch (error) {
|
|
1538
|
+
const durationMs = Date.now() - startedAt;
|
|
1539
|
+
const message = toErrorMessage(error);
|
|
1540
|
+
logger.error(`[ToolTrace] exception traceId=${traceId} tool=${tool.name} durationMs=${durationMs} message=${truncateForLog(message)} args=${formatUnknownForLog(sanitizeForLogging(normalized.args))} context=${formatUnknownForLog(sanitizeForLogging(normalized.context))}`);
|
|
1541
|
+
if (error instanceof Error && error.stack) {
|
|
1542
|
+
logger.error(`[ToolTrace] stack traceId=${traceId} tool=${tool.name} ${truncateForLog(error.stack, 4000)}`);
|
|
1543
|
+
}
|
|
1544
|
+
return toAgentToolResult({
|
|
1545
|
+
success: false,
|
|
1546
|
+
error: `Tool execution failed [traceId=${traceId}]: ${message}`,
|
|
1547
|
+
errorCode: "TOOL_EXECUTION_EXCEPTION",
|
|
1548
|
+
}, traceId);
|
|
1812
1549
|
}
|
|
1813
|
-
return execute({
|
|
1814
|
-
args: first || {},
|
|
1815
|
-
context: (second || {}),
|
|
1816
|
-
});
|
|
1817
1550
|
};
|
|
1818
|
-
|
|
1551
|
+
const payload = {
|
|
1819
1552
|
name: tool.name,
|
|
1820
1553
|
description: tool.description,
|
|
1821
|
-
parameters: tool.parameters,
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1554
|
+
parameters: sanitizeToolParametersSchema(tool.parameters),
|
|
1555
|
+
...(tool.optional ? { optional: true } : {}),
|
|
1556
|
+
execute: invoke,
|
|
1557
|
+
handler: invoke,
|
|
1558
|
+
};
|
|
1559
|
+
const apiObj = api;
|
|
1560
|
+
if (typeof apiObj.registerTool === "function") {
|
|
1561
|
+
if (tool.optional) {
|
|
1562
|
+
apiObj.registerTool(payload, { optional: true });
|
|
1563
|
+
}
|
|
1564
|
+
else {
|
|
1565
|
+
apiObj.registerTool(payload);
|
|
1566
|
+
}
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (typeof apiObj.registerTools === "function") {
|
|
1570
|
+
apiObj.registerTools([payload]);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
if (typeof apiObj.tools?.register === "function") {
|
|
1574
|
+
apiObj.tools.register(payload);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
throw new Error("No supported tool registration API found");
|
|
1825
1578
|
}
|
|
1826
1579
|
function unregisterTools() {
|
|
1827
1580
|
if (!api || !api.unregisterTool)
|
|
@@ -1836,6 +1589,110 @@ function unregisterTools() {
|
|
|
1836
1589
|
}
|
|
1837
1590
|
registeredTools = [];
|
|
1838
1591
|
}
|
|
1592
|
+
function registerFallbackTools() {
|
|
1593
|
+
if (!api || !builtinMemory)
|
|
1594
|
+
return;
|
|
1595
|
+
for (const name of ["search_memory", "store_event", "cortex_memory_status"]) {
|
|
1596
|
+
try {
|
|
1597
|
+
if (api.unregisterTool) {
|
|
1598
|
+
api.unregisterTool(name);
|
|
1599
|
+
logger.info(`Unregistered existing tool ${name} before registering fallback`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
catch (e) {
|
|
1603
|
+
// ignore
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
registerToolCompat({
|
|
1607
|
+
name: "search_memory",
|
|
1608
|
+
description: "Search memory (using builtin system - Cortex Memory disabled)",
|
|
1609
|
+
parameters: {
|
|
1610
|
+
type: "object",
|
|
1611
|
+
properties: {
|
|
1612
|
+
query: { type: "string", description: "Search query" },
|
|
1613
|
+
top_k: { type: "integer", description: "Number of results" },
|
|
1614
|
+
fusion_mode: { type: "string", enum: ["auto", "authoritative", "candidates", "off"], description: "Ignored by builtin fallback" },
|
|
1615
|
+
track_hits: { type: "boolean", description: "Ignored by builtin fallback" },
|
|
1616
|
+
},
|
|
1617
|
+
required: ["query"],
|
|
1618
|
+
additionalProperties: false,
|
|
1619
|
+
},
|
|
1620
|
+
execute: async (params) => {
|
|
1621
|
+
const args = (params.args || params);
|
|
1622
|
+
const query = args.query || "";
|
|
1623
|
+
const topK = args.top_k || 5;
|
|
1624
|
+
try {
|
|
1625
|
+
const results = await builtinMemory.search(query, topK);
|
|
1626
|
+
return { success: true, data: results };
|
|
1627
|
+
}
|
|
1628
|
+
catch (error) {
|
|
1629
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1630
|
+
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1631
|
+
}
|
|
1632
|
+
},
|
|
1633
|
+
});
|
|
1634
|
+
registeredFallbackTools.push("search_memory");
|
|
1635
|
+
registerToolCompat({
|
|
1636
|
+
name: "store_event",
|
|
1637
|
+
description: "Store event (using builtin system - Cortex Memory disabled)",
|
|
1638
|
+
parameters: {
|
|
1639
|
+
type: "object",
|
|
1640
|
+
properties: {
|
|
1641
|
+
summary: { type: "string", description: "Event summary" },
|
|
1642
|
+
},
|
|
1643
|
+
required: ["summary"],
|
|
1644
|
+
additionalProperties: false,
|
|
1645
|
+
},
|
|
1646
|
+
execute: async (params) => {
|
|
1647
|
+
const args = (params.args || params);
|
|
1648
|
+
const summary = args.summary || "";
|
|
1649
|
+
try {
|
|
1650
|
+
const id = await builtinMemory.store(summary);
|
|
1651
|
+
return { success: true, data: { id } };
|
|
1652
|
+
}
|
|
1653
|
+
catch (error) {
|
|
1654
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1655
|
+
return { success: false, error: `Builtin memory error: ${message}` };
|
|
1656
|
+
}
|
|
1657
|
+
},
|
|
1658
|
+
});
|
|
1659
|
+
registeredFallbackTools.push("store_event");
|
|
1660
|
+
registerToolCompat({
|
|
1661
|
+
name: "cortex_memory_status",
|
|
1662
|
+
description: "Get the current status of the Cortex Memory plugin",
|
|
1663
|
+
parameters: {
|
|
1664
|
+
type: "object",
|
|
1665
|
+
properties: {},
|
|
1666
|
+
required: [],
|
|
1667
|
+
additionalProperties: false,
|
|
1668
|
+
},
|
|
1669
|
+
execute: async (_params) => {
|
|
1670
|
+
return {
|
|
1671
|
+
success: true,
|
|
1672
|
+
data: {
|
|
1673
|
+
enabled: isEnabled,
|
|
1674
|
+
fallback_enabled: config?.fallbackToBuiltin ?? true,
|
|
1675
|
+
builtin_memory_available: builtinMemory !== null,
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
},
|
|
1679
|
+
});
|
|
1680
|
+
registeredFallbackTools.push("cortex_memory_status");
|
|
1681
|
+
logger.info(`Registered ${registeredFallbackTools.length} fallback tools`);
|
|
1682
|
+
}
|
|
1683
|
+
function unregisterFallbackTools() {
|
|
1684
|
+
if (!api || !api.unregisterTool)
|
|
1685
|
+
return;
|
|
1686
|
+
for (const name of registeredFallbackTools) {
|
|
1687
|
+
try {
|
|
1688
|
+
api.unregisterTool(name);
|
|
1689
|
+
}
|
|
1690
|
+
catch (e) {
|
|
1691
|
+
logger.warn(`Failed to unregister fallback tool ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
registeredFallbackTools = [];
|
|
1695
|
+
}
|
|
1839
1696
|
function registerHooks() {
|
|
1840
1697
|
if (!api)
|
|
1841
1698
|
return;
|
|
@@ -1896,27 +1753,15 @@ function setupProcessHandlers() {
|
|
|
1896
1753
|
isShuttingDown = true;
|
|
1897
1754
|
logger.info(`Received ${signal}, shutting down...`);
|
|
1898
1755
|
stopConfigWatcher();
|
|
1899
|
-
|
|
1900
|
-
process.exit(0);
|
|
1901
|
-
return;
|
|
1902
|
-
}
|
|
1903
|
-
shutdownPythonApi().then(() => {
|
|
1904
|
-
killPythonProcess();
|
|
1905
|
-
process.exit(0);
|
|
1906
|
-
}).catch(() => {
|
|
1907
|
-
killPythonProcess();
|
|
1908
|
-
process.exit(0);
|
|
1909
|
-
});
|
|
1756
|
+
process.exit(0);
|
|
1910
1757
|
};
|
|
1911
1758
|
process.on("exit", () => {
|
|
1912
|
-
killPythonProcess();
|
|
1913
1759
|
stopConfigWatcher();
|
|
1914
1760
|
});
|
|
1915
1761
|
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
1916
1762
|
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
1917
1763
|
process.on("uncaughtException", (err) => {
|
|
1918
1764
|
logger.error("Uncaught exception:", err.message);
|
|
1919
|
-
killPythonProcess();
|
|
1920
1765
|
stopConfigWatcher();
|
|
1921
1766
|
process.exit(1);
|
|
1922
1767
|
});
|
|
@@ -1930,10 +1775,6 @@ async function enable() {
|
|
|
1930
1775
|
logLifecycle("enable_start");
|
|
1931
1776
|
try {
|
|
1932
1777
|
unregisterFallbackTools();
|
|
1933
|
-
if (shouldUsePythonRuntime()) {
|
|
1934
|
-
await startPythonService();
|
|
1935
|
-
await waitForService();
|
|
1936
|
-
}
|
|
1937
1778
|
isEnabled = true;
|
|
1938
1779
|
registerTools();
|
|
1939
1780
|
registerHooks();
|
|
@@ -1957,12 +1798,9 @@ async function disable() {
|
|
|
1957
1798
|
logLifecycle("disable_start");
|
|
1958
1799
|
unregisterHooks();
|
|
1959
1800
|
unregisterTools();
|
|
1960
|
-
unregisterFallbackTools();
|
|
1961
1801
|
stopAutoReflectScheduler();
|
|
1962
|
-
if (shouldUsePythonRuntime()) {
|
|
1963
|
-
await stopPythonServiceAsync();
|
|
1964
|
-
}
|
|
1965
1802
|
isEnabled = false;
|
|
1803
|
+
messageHookInFlightBySession.clear();
|
|
1966
1804
|
memoryEngine = null;
|
|
1967
1805
|
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
1968
1806
|
logger.info("Falling back to OpenClaw builtin memory system");
|
|
@@ -1972,68 +1810,9 @@ async function disable() {
|
|
|
1972
1810
|
logger.info("Cortex Memory plugin disabled successfully");
|
|
1973
1811
|
logLifecycle("disable_success", { fallbackEnabled: registeredFallbackTools.length > 0 });
|
|
1974
1812
|
}
|
|
1975
|
-
function registerFallbackTools() {
|
|
1976
|
-
if (!api || !builtinMemory)
|
|
1977
|
-
return;
|
|
1978
|
-
registerToolCompat({
|
|
1979
|
-
name: "search_memory",
|
|
1980
|
-
description: "Search memory (using builtin system - Cortex Memory disabled)",
|
|
1981
|
-
parameters: {
|
|
1982
|
-
type: "object",
|
|
1983
|
-
properties: {
|
|
1984
|
-
query: { type: "string", description: "Search query" },
|
|
1985
|
-
top_k: { type: "integer", description: "Number of results" },
|
|
1986
|
-
},
|
|
1987
|
-
required: ["query"],
|
|
1988
|
-
additionalProperties: false,
|
|
1989
|
-
},
|
|
1990
|
-
execute: async ({ args, context }) => searchMemoryWithFallback((args || {}), context),
|
|
1991
|
-
});
|
|
1992
|
-
registeredFallbackTools.push("search_memory");
|
|
1993
|
-
registerToolCompat({
|
|
1994
|
-
name: "store_event",
|
|
1995
|
-
description: "Store event (using builtin system - Cortex Memory disabled)",
|
|
1996
|
-
parameters: {
|
|
1997
|
-
type: "object",
|
|
1998
|
-
properties: {
|
|
1999
|
-
summary: { type: "string", description: "Event summary" },
|
|
2000
|
-
},
|
|
2001
|
-
required: ["summary"],
|
|
2002
|
-
additionalProperties: false,
|
|
2003
|
-
},
|
|
2004
|
-
execute: async ({ args, context }) => storeEventWithFallback((args || {}), context),
|
|
2005
|
-
});
|
|
2006
|
-
registeredFallbackTools.push("store_event");
|
|
2007
|
-
registerToolCompat({
|
|
2008
|
-
name: "cortex_memory_status",
|
|
2009
|
-
description: "Get the current status of the Cortex Memory plugin",
|
|
2010
|
-
parameters: {
|
|
2011
|
-
type: "object",
|
|
2012
|
-
properties: {},
|
|
2013
|
-
required: [],
|
|
2014
|
-
additionalProperties: false,
|
|
2015
|
-
},
|
|
2016
|
-
execute: async ({ args, context }) => getPluginStatus(args || {}, context),
|
|
2017
|
-
});
|
|
2018
|
-
registeredFallbackTools.push("cortex_memory_status");
|
|
2019
|
-
}
|
|
2020
|
-
function unregisterFallbackTools() {
|
|
2021
|
-
if (!api || !api.unregisterTool)
|
|
2022
|
-
return;
|
|
2023
|
-
for (const name of registeredFallbackTools) {
|
|
2024
|
-
try {
|
|
2025
|
-
api.unregisterTool(name);
|
|
2026
|
-
}
|
|
2027
|
-
catch (e) {
|
|
2028
|
-
logger.warn(`Failed to unregister fallback tool ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
registeredFallbackTools = [];
|
|
2032
|
-
}
|
|
2033
1813
|
function getStatus() {
|
|
2034
1814
|
return {
|
|
2035
|
-
enabled: isEnabled
|
|
2036
|
-
serviceRunning: pythonProcess !== null
|
|
1815
|
+
enabled: isEnabled
|
|
2037
1816
|
};
|
|
2038
1817
|
}
|
|
2039
1818
|
async function unregister() {
|
|
@@ -2044,25 +1823,19 @@ async function unregister() {
|
|
|
2044
1823
|
unregisterHooks();
|
|
2045
1824
|
unregisterTools();
|
|
2046
1825
|
unregisterFallbackTools();
|
|
2047
|
-
if (shouldUsePythonRuntime()) {
|
|
2048
|
-
await stopPythonServiceAsync();
|
|
2049
|
-
}
|
|
2050
|
-
else {
|
|
2051
|
-
killPythonProcess();
|
|
2052
|
-
}
|
|
2053
1826
|
isEnabled = false;
|
|
2054
1827
|
isInitializing = false;
|
|
2055
1828
|
isRegistered = false;
|
|
2056
1829
|
api = null;
|
|
2057
1830
|
config = null;
|
|
2058
1831
|
autoSearchCacheBySession.clear();
|
|
2059
|
-
|
|
1832
|
+
messageHookInFlightBySession.clear();
|
|
2060
1833
|
memoryEngine = null;
|
|
1834
|
+
builtinMemory = null;
|
|
2061
1835
|
registeredTools = [];
|
|
2062
1836
|
registeredHooks = [];
|
|
2063
1837
|
registeredFallbackTools = [];
|
|
2064
1838
|
registeredHookHandlers.clear();
|
|
2065
|
-
stopAutoReflectScheduler();
|
|
2066
1839
|
configPath = null;
|
|
2067
1840
|
logger.info("Cortex Memory plugin unregistered successfully");
|
|
2068
1841
|
logLifecycle("unregister_success");
|
|
@@ -2073,12 +1846,13 @@ function register(pluginApi, userConfig) {
|
|
|
2073
1846
|
}
|
|
2074
1847
|
isInitializing = true;
|
|
2075
1848
|
api = pluginApi;
|
|
2076
|
-
logger = api.getLogger?.() || createConsoleLogger();
|
|
1849
|
+
logger = api.logger || api.getLogger?.() || createConsoleLogger();
|
|
2077
1850
|
const apiPluginConfig = api.pluginConfig || {};
|
|
2078
1851
|
const openclawConfig = api.config || {};
|
|
2079
1852
|
const pluginEntry = openclawConfig?.plugins?.entries?.[PLUGIN_ID];
|
|
2080
1853
|
const pluginConfig = Object.keys(apiPluginConfig).length > 0 ? apiPluginConfig : (pluginEntry?.config || {});
|
|
2081
1854
|
const effectiveConfig = userConfig || pluginConfig || {};
|
|
1855
|
+
const resolvedDbPath = resolveConfiguredMemoryRoot(typeof effectiveConfig.dbPath === "string" ? effectiveConfig.dbPath : undefined);
|
|
2082
1856
|
const embeddingConfigRaw = (effectiveConfig.embedding || { provider: "openai-compatible", model: "" });
|
|
2083
1857
|
const llmConfigRaw = (effectiveConfig.llm || { provider: "openai", model: "" });
|
|
2084
1858
|
const rerankerConfigRaw = (effectiveConfig.reranker || { provider: "", model: "" });
|
|
@@ -2098,10 +1872,16 @@ function register(pluginApi, userConfig) {
|
|
|
2098
1872
|
embedding: embeddingConfig,
|
|
2099
1873
|
llm: llmConfig,
|
|
2100
1874
|
reranker: rerankerConfig,
|
|
2101
|
-
dbPath:
|
|
1875
|
+
dbPath: resolvedDbPath,
|
|
2102
1876
|
autoSync: effectiveConfig.autoSync ?? defaultConfig.autoSync,
|
|
2103
1877
|
autoReflect: effectiveConfig.autoReflect ?? defaultConfig.autoReflect,
|
|
2104
1878
|
autoReflectIntervalMinutes: effectiveConfig.autoReflectIntervalMinutes ?? defaultConfig.autoReflectIntervalMinutes,
|
|
1879
|
+
graphQualityMode: effectiveConfig.graphQualityMode ?? defaultConfig.graphQualityMode,
|
|
1880
|
+
wikiProjection: {
|
|
1881
|
+
enabled: effectiveConfig.wikiProjection?.enabled ?? defaultConfig.wikiProjection?.enabled,
|
|
1882
|
+
mode: effectiveConfig.wikiProjection?.mode ?? defaultConfig.wikiProjection?.mode,
|
|
1883
|
+
maxBatch: effectiveConfig.wikiProjection?.maxBatch ?? defaultConfig.wikiProjection?.maxBatch,
|
|
1884
|
+
},
|
|
2105
1885
|
readFusion: {
|
|
2106
1886
|
enabled: effectiveConfig.readFusion?.enabled ?? defaultConfig.readFusion?.enabled,
|
|
2107
1887
|
maxCandidates: effectiveConfig.readFusion?.maxCandidates ?? defaultConfig.readFusion?.maxCandidates,
|
|
@@ -2120,6 +1900,19 @@ function register(pluginApi, userConfig) {
|
|
|
2120
1900
|
vectorChunking: {
|
|
2121
1901
|
chunkSize: effectiveConfig.vectorChunking?.chunkSize ?? defaultConfig.vectorChunking?.chunkSize,
|
|
2122
1902
|
chunkOverlap: effectiveConfig.vectorChunking?.chunkOverlap ?? defaultConfig.vectorChunking?.chunkOverlap,
|
|
1903
|
+
evidenceMaxChunks: effectiveConfig.vectorChunking?.evidenceMaxChunks ?? defaultConfig.vectorChunking?.evidenceMaxChunks,
|
|
1904
|
+
},
|
|
1905
|
+
writePolicy: {
|
|
1906
|
+
archiveMinConfidence: effectiveConfig.writePolicy?.archiveMinConfidence ?? defaultConfig.writePolicy?.archiveMinConfidence,
|
|
1907
|
+
archiveMinQualityScore: effectiveConfig.writePolicy?.archiveMinQualityScore ?? defaultConfig.writePolicy?.archiveMinQualityScore,
|
|
1908
|
+
activeMinQualityScore: effectiveConfig.writePolicy?.activeMinQualityScore ?? defaultConfig.writePolicy?.activeMinQualityScore,
|
|
1909
|
+
activeDedupTailLines: effectiveConfig.writePolicy?.activeDedupTailLines ?? defaultConfig.writePolicy?.activeDedupTailLines,
|
|
1910
|
+
activeTextMaxChars: effectiveConfig.writePolicy?.activeTextMaxChars ?? defaultConfig.writePolicy?.activeTextMaxChars,
|
|
1911
|
+
archiveSourceTextMaxChars: effectiveConfig.writePolicy?.archiveSourceTextMaxChars ?? defaultConfig.writePolicy?.archiveSourceTextMaxChars,
|
|
1912
|
+
},
|
|
1913
|
+
syncPolicy: {
|
|
1914
|
+
includeLocalActiveInput: effectiveConfig.syncPolicy?.includeLocalActiveInput ?? defaultConfig.syncPolicy?.includeLocalActiveInput,
|
|
1915
|
+
graphQualityMode: effectiveConfig.syncPolicy?.graphQualityMode ?? defaultConfig.syncPolicy?.graphQualityMode,
|
|
2123
1916
|
},
|
|
2124
1917
|
memoryDecay: {
|
|
2125
1918
|
enabled: effectiveConfig.memoryDecay?.enabled ?? defaultConfig.memoryDecay?.enabled,
|
|
@@ -2133,10 +1926,30 @@ function register(pluginApi, userConfig) {
|
|
|
2133
1926
|
recentWindowDays: effectiveConfig.memoryDecay?.antiDecay?.recentWindowDays ?? defaultConfig.memoryDecay?.antiDecay?.recentWindowDays,
|
|
2134
1927
|
},
|
|
2135
1928
|
},
|
|
1929
|
+
readTuning: {
|
|
1930
|
+
scoring: {
|
|
1931
|
+
lexicalWeight: effectiveConfig.readTuning?.scoring?.lexicalWeight ?? defaultConfig.readTuning?.scoring?.lexicalWeight,
|
|
1932
|
+
bm25Scale: effectiveConfig.readTuning?.scoring?.bm25Scale ?? defaultConfig.readTuning?.scoring?.bm25Scale,
|
|
1933
|
+
semanticWeight: effectiveConfig.readTuning?.scoring?.semanticWeight ?? defaultConfig.readTuning?.scoring?.semanticWeight,
|
|
1934
|
+
recencyWeight: effectiveConfig.readTuning?.scoring?.recencyWeight ?? defaultConfig.readTuning?.scoring?.recencyWeight,
|
|
1935
|
+
qualityWeight: effectiveConfig.readTuning?.scoring?.qualityWeight ?? defaultConfig.readTuning?.scoring?.qualityWeight,
|
|
1936
|
+
typeMatchWeight: effectiveConfig.readTuning?.scoring?.typeMatchWeight ?? defaultConfig.readTuning?.scoring?.typeMatchWeight,
|
|
1937
|
+
graphMatchWeight: effectiveConfig.readTuning?.scoring?.graphMatchWeight ?? defaultConfig.readTuning?.scoring?.graphMatchWeight,
|
|
1938
|
+
},
|
|
1939
|
+
rrf: {
|
|
1940
|
+
k: effectiveConfig.readTuning?.rrf?.k ?? defaultConfig.readTuning?.rrf?.k,
|
|
1941
|
+
weight: effectiveConfig.readTuning?.rrf?.weight ?? defaultConfig.readTuning?.rrf?.weight,
|
|
1942
|
+
},
|
|
1943
|
+
recency: {
|
|
1944
|
+
buckets: effectiveConfig.readTuning?.recency?.buckets ?? defaultConfig.readTuning?.recency?.buckets,
|
|
1945
|
+
},
|
|
1946
|
+
autoContext: {
|
|
1947
|
+
queryMaxChars: effectiveConfig.readTuning?.autoContext?.queryMaxChars ?? defaultConfig.readTuning?.autoContext?.queryMaxChars,
|
|
1948
|
+
lightweightSearch: effectiveConfig.readTuning?.autoContext?.lightweightSearch ?? defaultConfig.readTuning?.autoContext?.lightweightSearch,
|
|
1949
|
+
},
|
|
1950
|
+
},
|
|
2136
1951
|
enabled: effectiveConfig.enabled ?? defaultConfig.enabled,
|
|
2137
|
-
fallbackToBuiltin: effectiveConfig.fallbackToBuiltin ??
|
|
2138
|
-
apiUrl: effectiveConfig.apiUrl ?? "http://localhost:8765",
|
|
2139
|
-
engineMode: "ts",
|
|
1952
|
+
fallbackToBuiltin: effectiveConfig.fallbackToBuiltin ?? true,
|
|
2140
1953
|
};
|
|
2141
1954
|
memoryEngine = null;
|
|
2142
1955
|
if (api.getBuiltinMemory) {
|
|
@@ -2155,8 +1968,12 @@ function register(pluginApi, userConfig) {
|
|
|
2155
1968
|
reranker: { model: config.reranker.model },
|
|
2156
1969
|
enabled: config.enabled,
|
|
2157
1970
|
fallbackToBuiltin: config.fallbackToBuiltin,
|
|
2158
|
-
engineMode: config.engineMode,
|
|
2159
1971
|
});
|
|
1972
|
+
logger.info(`Runtime config snapshot: ${JSON.stringify(safeConfig)}`);
|
|
1973
|
+
const configErrors = validateConfig(config);
|
|
1974
|
+
if (configErrors.length > 0) {
|
|
1975
|
+
logger.warn(`Cortex Memory config validation warnings: ${configErrors.join(" | ")}`);
|
|
1976
|
+
}
|
|
2160
1977
|
checkOpenClawVersion().catch(e => logger.warn(`Version check failed: ${e}`));
|
|
2161
1978
|
configPath = findOpenClawConfig();
|
|
2162
1979
|
if (configPath) {
|
|
@@ -2169,40 +1986,23 @@ function register(pluginApi, userConfig) {
|
|
|
2169
1986
|
isInitializing = false;
|
|
2170
1987
|
isRegistered = true;
|
|
2171
1988
|
logger.info("Cortex Memory plugin registered successfully");
|
|
2172
|
-
logger.info(`Cortex Memory engine mode: ${resolveEngine().mode}`);
|
|
2173
1989
|
logLifecycle("register_success", {
|
|
2174
|
-
engineMode: config.engineMode,
|
|
2175
1990
|
enabled: isEnabled,
|
|
2176
|
-
hasBuiltinFallback: Boolean(builtinMemory),
|
|
2177
1991
|
});
|
|
2178
1992
|
if (isEnabled) {
|
|
2179
1993
|
registerTools();
|
|
2180
1994
|
registerHooks();
|
|
2181
1995
|
startAutoReflectScheduler();
|
|
2182
|
-
initializeAsync().catch(error => {
|
|
2183
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2184
|
-
logger.error(`Failed to initialize Cortex Memory: ${message}`);
|
|
2185
|
-
if (config?.fallbackToBuiltin && builtinMemory) {
|
|
2186
|
-
unregisterHooks();
|
|
2187
|
-
unregisterTools();
|
|
2188
|
-
logger.info("Falling back to builtin memory");
|
|
2189
|
-
isEnabled = false;
|
|
2190
|
-
registerFallbackTools();
|
|
2191
|
-
logLifecycle("fallback_after_init_error", { fallbackTools: registeredFallbackTools.length, error: message });
|
|
2192
|
-
}
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
else if (config?.fallbackToBuiltin && builtinMemory) {
|
|
2196
|
-
registerFallbackTools();
|
|
2197
|
-
logLifecycle("fallback_registered_on_start", { fallbackTools: registeredFallbackTools.length });
|
|
2198
1996
|
}
|
|
2199
1997
|
}
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
1998
|
+
const pluginEntry = definePluginEntryCompat({
|
|
1999
|
+
id: PLUGIN_ID,
|
|
2000
|
+
name: "Cortex Memory",
|
|
2001
|
+
register,
|
|
2002
|
+
unregister,
|
|
2003
|
+
enable,
|
|
2004
|
+
disable,
|
|
2005
|
+
getStatus,
|
|
2006
|
+
});
|
|
2007
|
+
exports.default = pluginEntry;
|
|
2208
2008
|
//# sourceMappingURL=index.js.map
|