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