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