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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +296 -203
  3. package/SIGNATURE.md +7 -0
  4. package/SKILL.md +92 -268
  5. package/dist/index.d.ts +100 -22
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1249 -1252
  8. package/dist/index.js.map +1 -1
  9. package/dist/openclaw.plugin.json +501 -16
  10. package/dist/src/dedup/three_stage_deduplicator.d.ts +25 -0
  11. package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -0
  12. package/dist/src/dedup/three_stage_deduplicator.js +224 -0
  13. package/dist/src/dedup/three_stage_deduplicator.js.map +1 -0
  14. package/dist/src/engine/memory_engine.d.ts +6 -1
  15. package/dist/src/engine/memory_engine.d.ts.map +1 -1
  16. package/dist/src/engine/ts_engine.d.ts +242 -0
  17. package/dist/src/engine/ts_engine.d.ts.map +1 -1
  18. package/dist/src/engine/ts_engine.js +1468 -52
  19. package/dist/src/engine/ts_engine.js.map +1 -1
  20. package/dist/src/engine/types.d.ts +29 -0
  21. package/dist/src/engine/types.d.ts.map +1 -1
  22. package/dist/src/graph/ontology.d.ts +125 -0
  23. package/dist/src/graph/ontology.d.ts.map +1 -0
  24. package/dist/src/graph/ontology.js +1237 -0
  25. package/dist/src/graph/ontology.js.map +1 -0
  26. package/dist/src/net/http_post.d.ts +17 -0
  27. package/dist/src/net/http_post.d.ts.map +1 -0
  28. package/dist/src/net/http_post.js +56 -0
  29. package/dist/src/net/http_post.js.map +1 -0
  30. package/dist/src/quality/llm_output_validator.d.ts +66 -0
  31. package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
  32. package/dist/src/quality/llm_output_validator.js +659 -0
  33. package/dist/src/quality/llm_output_validator.js.map +1 -0
  34. package/dist/src/reflect/reflector.d.ts +7 -0
  35. package/dist/src/reflect/reflector.d.ts.map +1 -1
  36. package/dist/src/reflect/reflector.js +352 -8
  37. package/dist/src/reflect/reflector.js.map +1 -1
  38. package/dist/src/rules/rule_store.d.ts.map +1 -1
  39. package/dist/src/rules/rule_store.js +75 -16
  40. package/dist/src/rules/rule_store.js.map +1 -1
  41. package/dist/src/session/session_end.d.ts +33 -0
  42. package/dist/src/session/session_end.d.ts.map +1 -1
  43. package/dist/src/session/session_end.js +67 -64
  44. package/dist/src/session/session_end.js.map +1 -1
  45. package/dist/src/store/archive_store.d.ts +136 -0
  46. package/dist/src/store/archive_store.d.ts.map +1 -0
  47. package/dist/src/store/archive_store.js +635 -0
  48. package/dist/src/store/archive_store.js.map +1 -0
  49. package/dist/src/store/embedding_utils.d.ts +32 -0
  50. package/dist/src/store/embedding_utils.d.ts.map +1 -0
  51. package/dist/src/store/embedding_utils.js +173 -0
  52. package/dist/src/store/embedding_utils.js.map +1 -0
  53. package/dist/src/store/graph_memory_store.d.ts +114 -0
  54. package/dist/src/store/graph_memory_store.d.ts.map +1 -0
  55. package/dist/src/store/graph_memory_store.js +841 -0
  56. package/dist/src/store/graph_memory_store.js.map +1 -0
  57. package/dist/src/store/read_store.d.ts +89 -0
  58. package/dist/src/store/read_store.d.ts.map +1 -1
  59. package/dist/src/store/read_store.js +2459 -28
  60. package/dist/src/store/read_store.js.map +1 -1
  61. package/dist/src/store/vector_store.d.ts +45 -0
  62. package/dist/src/store/vector_store.d.ts.map +1 -0
  63. package/dist/src/store/vector_store.js +202 -0
  64. package/dist/src/store/vector_store.js.map +1 -0
  65. package/dist/src/store/write_store.d.ts +54 -0
  66. package/dist/src/store/write_store.d.ts.map +1 -1
  67. package/dist/src/store/write_store.js +284 -6
  68. package/dist/src/store/write_store.js.map +1 -1
  69. package/dist/src/sync/session_sync.d.ts +119 -2
  70. package/dist/src/sync/session_sync.d.ts.map +1 -1
  71. package/dist/src/sync/session_sync.js +2377 -31
  72. package/dist/src/sync/session_sync.js.map +1 -1
  73. package/dist/src/utils/runtime_env.d.ts +4 -0
  74. package/dist/src/utils/runtime_env.d.ts.map +1 -0
  75. package/dist/src/utils/runtime_env.js +51 -0
  76. package/dist/src/utils/runtime_env.js.map +1 -0
  77. package/dist/src/wiki/wiki_linter.d.ts +25 -0
  78. package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
  79. package/dist/src/wiki/wiki_linter.js +268 -0
  80. package/dist/src/wiki/wiki_linter.js.map +1 -0
  81. package/dist/src/wiki/wiki_logger.d.ts +10 -0
  82. package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
  83. package/dist/src/wiki/wiki_logger.js +78 -0
  84. package/dist/src/wiki/wiki_logger.js.map +1 -0
  85. package/dist/src/wiki/wiki_maintainer.d.ts +36 -0
  86. package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
  87. package/dist/src/wiki/wiki_maintainer.js +38 -0
  88. package/dist/src/wiki/wiki_maintainer.js.map +1 -0
  89. package/dist/src/wiki/wiki_projector.d.ts +33 -0
  90. package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
  91. package/dist/src/wiki/wiki_projector.js +633 -0
  92. package/dist/src/wiki/wiki_projector.js.map +1 -0
  93. package/dist/src/wiki/wiki_queue.d.ts +29 -0
  94. package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
  95. package/dist/src/wiki/wiki_queue.js +137 -0
  96. package/dist/src/wiki/wiki_queue.js.map +1 -0
  97. package/openclaw.plugin.json +501 -16
  98. package/package.json +58 -7
  99. package/schema/graph.schema.yaml +330 -0
  100. package/scripts/cli.js +19 -14
  101. package/scripts/repair-memory.js +321 -0
  102. package/scripts/uninstall.js +22 -5
  103. package/skills/cortex-memory/SKILL.md +49 -0
  104. package/skills/cortex-memory/references/agent-manual.md +115 -0
  105. package/skills/cortex-memory/references/configuration.md +92 -0
  106. package/skills/cortex-memory/references/publish-checklist.md +46 -0
  107. package/skills/cortex-memory/references/system-prompt-template.md +27 -0
  108. package/skills/cortex-memory/references/tools.md +181 -0
  109. package/skills/cortex-memory/scripts/smoke-check.ps1 +56 -0
  110. 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 MIN_OPENCLAW_VERSION = "2026.3.8";
88
- const MAX_OPENCLAW_VERSION = "2027.0.0";
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
- function shouldUsePythonRuntime() {
123
- return false;
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
- const projectRoot = findProjectRoot();
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
- const selectedMode = "ts";
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: sessionSync.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
- sessionSync,
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 sanitized = {};
612
+ const result = {};
274
613
  for (const [key, value] of Object.entries(obj)) {
275
- const isSensitive = SENSITIVE_KEYS.some(k => key.toUpperCase().includes(k));
276
- if (isSensitive) {
277
- sanitized[key] = "***REDACTED***";
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
- sanitized[key] = sanitizeForLogging(value);
617
+ else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
618
+ result[key] = sanitizeForLogging(value);
281
619
  }
282
620
  else {
283
- sanitized[key] = value;
621
+ result[key] = value;
284
622
  }
285
623
  }
286
- return sanitized;
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 value of values) {
296
- if (typeof value === "string" && value.trim()) {
297
- return value.trim();
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 normalizeIncomingMessage(payload) {
303
- const data = asRecord(payload);
304
- if (!data) {
305
- return null;
677
+ function validateConfig(cfg) {
678
+ const errors = [];
679
+ if (!cfg.embedding?.provider) {
680
+ errors.push("embedding.provider is required.");
306
681
  }
307
- const message = asRecord(data.message);
308
- const eventData = asRecord(data.data);
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 (!text) {
356
- return null;
685
+ if (!cfg.llm?.provider) {
686
+ errors.push("llm.provider is required.");
357
687
  }
358
- return { text, role, source };
359
- }
360
- function resolveSessionId(context, payload) {
361
- const fromContext = typeof context.sessionId === "string" ? context.sessionId.trim() : "";
362
- if (fromContext)
363
- return fromContext;
364
- const data = asRecord(payload);
365
- const dataChat = data ? asRecord(data.chat) : null;
366
- const message = data ? asRecord(data.message) : null;
367
- const messageChat = message ? asRecord(message.chat) : null;
368
- const eventData = data ? asRecord(data.data) : null;
369
- const eventChat = eventData ? asRecord(eventData.chat) : null;
370
- const update = data ? asRecord(data.update) : null;
371
- const updateMessage = update ? asRecord(update.message) : null;
372
- const updateChat = updateMessage ? asRecord(updateMessage.chat) : null;
373
- const direct = firstString([
374
- data?.sessionId,
375
- data?.session_id,
376
- data?.conversationId,
377
- data?.conversation_id,
378
- data?.threadId,
379
- data?.thread_id,
380
- message?.sessionId,
381
- eventData?.sessionId,
382
- ]);
383
- if (direct)
384
- return direct;
385
- const chatId = firstString([
386
- data?.chatId,
387
- data?.chat_id,
388
- dataChat?.id,
389
- messageChat?.id,
390
- eventChat?.id,
391
- updateChat?.id,
392
- ]);
393
- if (chatId)
394
- return `chat:${chatId}`;
395
- return `fallback:${context.workspaceId || "default"}:${context.agentId || "agent"}`;
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
- if (compareVersions(version, MAX_OPENCLAW_VERSION) >= 0) {
418
- throw new Error(`Incompatible OpenClaw version: ${version}. Maximum supported: <${MAX_OPENCLAW_VERSION}`);
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
- else {
423
- logger.warn("Could not determine OpenClaw version, proceeding with caution");
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
- catch (e) {
427
- const message = e instanceof Error ? e.message : String(e);
428
- logger.warn(`Version check warning: ${message}`);
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
- function findProjectRoot() {
432
- if (api && api.rootDir) {
433
- return api.rootDir;
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
- let current = process.cwd();
436
- while (current !== path.dirname(current)) {
437
- if (fs.existsSync(path.join(current, "openclaw.plugin.json")) &&
438
- fs.existsSync(path.join(current, "package.json"))) {
439
- return current;
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
- throw new Error("Cannot find project root directory");
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 possiblePaths = [
447
- path.join(process.cwd(), "openclaw.json"),
448
- path.join(process.env.USERPROFILE || process.env.HOME || "", ".openclaw", "openclaw.json"),
449
- path.join(process.env.OPENCLAW_BASE_PATH || "", "openclaw.json"),
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 p of possiblePaths) {
452
- if (fs.existsSync(p)) {
453
- return p;
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
- const content = fs.readFileSync(configPath, "utf-8");
464
- const openclawConfig = JSON.parse(content);
465
- const pluginEntry = openclawConfig?.plugins?.entries?.[PLUGIN_ID];
466
- if (pluginEntry && typeof pluginEntry === "object") {
467
- return pluginEntry.enabled !== false;
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
- const legacyPluginConfig = openclawConfig?.plugins?.["cortex-memory"];
470
- return legacyPluginConfig?.enabled !== false;
915
+ return true;
471
916
  }
472
- catch (e) {
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
- const newState = loadPluginEnabledState();
484
- if (newState !== lastEnabledState) {
485
- lastEnabledState = newState;
486
- if (newState && !isEnabled) {
487
- logger.info("Detected config change: enabling Cortex Memory plugin");
488
- enable();
489
- }
490
- else if (!newState && isEnabled) {
491
- logger.info("Detected config change: disabling Cortex Memory plugin");
492
- disable();
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 || autoReflectInterval) {
952
+ if (!config?.autoReflect) {
505
953
  return;
506
954
  }
507
- autoReflectInterval = setInterval(() => {
508
- if (!isEnabled) {
509
- return;
510
- }
511
- const schedulerContext = {
512
- agentId: "cortex-memory-scheduler",
513
- workspaceId: "system",
514
- };
515
- const marker = getArchiveMarker();
516
- const now = Date.now();
517
- if (marker === "missing" || marker === "error") {
518
- return;
519
- }
520
- if (marker === lastAutoReflectArchiveMarker) {
521
- return;
522
- }
523
- if (now - lastAutoReflectRunAt < 5 * 60 * 1000) {
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
- resolveEngine().reflectMemory({}, schedulerContext)
527
- .then(result => {
528
- if (result.success) {
529
- lastAutoReflectArchiveMarker = marker;
530
- lastAutoReflectRunAt = now;
531
- logger.info("Scheduled reflection complete");
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
- else {
534
- logger.warn(`Auto-reflect failed: ${result.error ?? "unknown_error"}`);
976
+ catch (e) {
977
+ logger.error(`Auto-reflect error: ${e instanceof Error ? e.message : String(e)}`);
535
978
  }
536
- })
537
- .catch(error => logger.warn(`Auto-reflect failed: ${String(error)}`));
538
- }, 5 * 60 * 1000);
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 validateConfig(cfg) {
547
- const errors = [];
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 isPortListening(host, port, timeoutMs = 700) {
578
- return new Promise(resolve => {
579
- const socket = new net.Socket();
580
- let settled = false;
581
- const finalize = (value) => {
582
- if (settled)
583
- return;
584
- settled = true;
585
- socket.destroy();
586
- resolve(value);
587
- };
588
- socket.setTimeout(timeoutMs);
589
- socket.once("connect", () => finalize(true));
590
- socket.once("timeout", () => finalize(false));
591
- socket.once("error", () => finalize(false));
592
- socket.connect(port, host);
593
- });
594
- }
595
- function writePythonPid(pid) {
596
- if (!pythonPidFilePath)
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 clearPythonPidFile() {
606
- if (!pythonPidFilePath)
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 readPythonPid() {
618
- if (!pythonPidFilePath || !fs.existsSync(pythonPidFilePath))
619
- return null;
620
- try {
621
- const raw = fs.readFileSync(pythonPidFilePath, "utf-8").trim();
622
- const pid = Number(raw);
623
- return Number.isInteger(pid) && pid > 0 ? pid : null;
624
- }
625
- catch {
626
- return null;
627
- }
1006
+ async function onTimerHandler(payload, context) {
1007
+ await resolveEngine().onTimer(payload, context);
628
1008
  }
629
- function killProcessByPid(pid) {
630
- if (!pid)
631
- return;
632
- try {
633
- if (process.platform === "win32") {
634
- (0, child_process_1.execSync)(`taskkill /pid ${pid} /f /t`, { stdio: "ignore" });
635
- return;
636
- }
637
- try {
638
- process.kill(pid, "SIGTERM");
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().searchMemory(args, params.context);
1153
+ return resolveEngine().lintMemoryWiki(args, params.context);
1429
1154
  },
1430
1155
  },
1431
1156
  {
1432
- name: "store_event",
1433
- description: "Store a new event in memory",
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
- summary: { type: "string", description: "Event summary" },
1438
- entities: {
1439
- type: "array",
1440
- description: "Involved entities",
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: ["summary"],
1169
+ required: [],
1451
1170
  additionalProperties: false,
1452
1171
  },
1453
1172
  execute: async (params) => {
1454
1173
  const args = params.args || params;
1455
- return resolveEngine().storeEvent(args, params.context);
1174
+ return resolveEngine().listGraphConflicts(args, params.context);
1456
1175
  },
1457
1176
  },
1458
1177
  {
1459
- name: "query_graph",
1460
- description: "Query memory graph for entity relationships",
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
- entity: { type: "string", description: "Entity name" }
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: ["entity"],
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().queryGraph(args, params.context);
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
- return resolveEngine().runDiagnostics(args, params.context);
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
- registerToolCompat(tool);
1567
- registeredTools.push(tool.name);
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 execute = async (params) => tool.execute({
1574
- args: params?.args || {},
1575
- context: params.context,
1576
- });
1577
- const handler = async (...params) => {
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
- if (first && typeof first === "object" && "context" in first) {
1581
- return execute({
1582
- args: first.args || {},
1583
- context: first.context,
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
- api.registerTool({
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
- if (!shouldUsePythonRuntime()) {
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: effectiveConfig.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 ?? defaultConfig.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
- logger.info(`Cortex Memory engine mode: ${resolveEngine().mode}`);
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