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