mr-memory 3.7.5 → 3.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +115 -8
- package/index.ts +144 -8
- package/memoryrouter-api.js +11 -0
- package/memoryrouter-api.ts +11 -0
- package/openclaw.plugin.json +8 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* BYOK — provider API keys never touch MemoryRouter.
|
|
9
9
|
*/
|
|
10
10
|
import { readFile, lstat, writeFile, copyFile, mkdir } from "node:fs/promises";
|
|
11
|
-
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory } from "./memoryrouter-api.js";
|
|
11
|
+
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory, warmupMemory } from "./memoryrouter-api.js";
|
|
12
12
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
13
13
|
import os from "node:os";
|
|
14
14
|
const DEFAULT_ENDPOINT = "https://api.memoryrouter.ai";
|
|
@@ -267,7 +267,7 @@ const WORKSPACE_FILES = [
|
|
|
267
267
|
"TOOLS.md", "AGENTS.md", "SOUL.md", "BOOTSTRAP.md",
|
|
268
268
|
];
|
|
269
269
|
function openClawConfigPath() {
|
|
270
|
-
return process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
270
|
+
return process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
271
271
|
}
|
|
272
272
|
async function readOpenClawConfigFile() {
|
|
273
273
|
try {
|
|
@@ -292,6 +292,44 @@ async function writeOpenClawConfigFile(config) {
|
|
|
292
292
|
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
293
293
|
`, "utf8");
|
|
294
294
|
}
|
|
295
|
+
// ──────────────────────────────────────────────────────
|
|
296
|
+
// Full-replace: disable OpenClaw's native memory search so MemoryRouter
|
|
297
|
+
// owns the memory_search tool (no name conflict, no native embeddings 429).
|
|
298
|
+
// memoryFlush (durable MEMORY.md writes) is intentionally left untouched.
|
|
299
|
+
// Global + subagents: writes agents.defaults.memorySearch.enabled.
|
|
300
|
+
// ──────────────────────────────────────────────────────
|
|
301
|
+
async function setNativeMemorySearchEnabled(enabled) {
|
|
302
|
+
const config = await readOpenClawConfigFile();
|
|
303
|
+
config.agents ??= {};
|
|
304
|
+
config.agents.defaults ??= {};
|
|
305
|
+
config.agents.defaults.memorySearch ??= {};
|
|
306
|
+
if (config.agents.defaults.memorySearch.enabled === enabled) return false; // no-op
|
|
307
|
+
config.agents.defaults.memorySearch.enabled = enabled;
|
|
308
|
+
await writeOpenClawConfigFile(config);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Boot-time self-heal: if a key is configured but native search is still on,
|
|
313
|
+
// flip it off so already-installed users are fixed on restart after updating
|
|
314
|
+
// (no need to re-run `openclaw mr <key>`).
|
|
315
|
+
async function selfHealNativeMemorySearch(api) {
|
|
316
|
+
try {
|
|
317
|
+
const config = await readOpenClawConfigFile();
|
|
318
|
+
const ms = config?.agents?.defaults?.memorySearch;
|
|
319
|
+
if (ms?.enabled === false) return; // already disabled — nothing to do
|
|
320
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
321
|
+
if (changed) {
|
|
322
|
+
api.logger.info?.(
|
|
323
|
+
"memoryrouter: disabled native memory search (full-replace). Restart gateway to apply.",
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
api.logger.info?.(
|
|
328
|
+
`memoryrouter: self-heal skipped — ${err instanceof Error ? err.message : String(err)}`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
295
333
|
async function updateOpenClawPluginEntry(api, patch) {
|
|
296
334
|
const config = await readOpenClawConfigFile();
|
|
297
335
|
config.plugins ??= {};
|
|
@@ -319,6 +357,47 @@ async function setPluginEnabled(api, enabled) {
|
|
|
319
357
|
}
|
|
320
358
|
await updateOpenClawPluginEntry(api, { enabled });
|
|
321
359
|
}
|
|
360
|
+
async function mergePluginConfig(api, patch) {
|
|
361
|
+
const current = await readOpenClawConfigFile();
|
|
362
|
+
const existing = current.plugins?.entries?.[api.id]?.config;
|
|
363
|
+
await setPluginConfig(api, {
|
|
364
|
+
...(existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {}),
|
|
365
|
+
...patch,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
// Keep mr-memory as a first-class plugin config entry without clobbering user settings.
|
|
369
|
+
// OpenClaw doctor validates plugin entries against discoverable manifests, so the entry
|
|
370
|
+
// must be present, enabled, and allowlisted when an allowlist exists.
|
|
371
|
+
async function ensureDoctorSafePluginEntry(api) {
|
|
372
|
+
try {
|
|
373
|
+
const config = await readOpenClawConfigFile();
|
|
374
|
+
const plugins = config.plugins ?? {};
|
|
375
|
+
const entries = plugins.entries ?? {};
|
|
376
|
+
const existingEntry = entries[api.id] && typeof entries[api.id] === "object" && !Array.isArray(entries[api.id])
|
|
377
|
+
? entries[api.id]
|
|
378
|
+
: {};
|
|
379
|
+
const nextPlugins = {
|
|
380
|
+
...plugins,
|
|
381
|
+
entries: {
|
|
382
|
+
...entries,
|
|
383
|
+
[api.id]: {
|
|
384
|
+
...existingEntry,
|
|
385
|
+
enabled: true,
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
if (Array.isArray(plugins.allow) && !plugins.allow.includes(api.id)) {
|
|
390
|
+
nextPlugins.allow = [...plugins.allow, api.id];
|
|
391
|
+
}
|
|
392
|
+
const next = { ...config, plugins: nextPlugins };
|
|
393
|
+
if (JSON.stringify(next.plugins) === JSON.stringify(config.plugins))
|
|
394
|
+
return;
|
|
395
|
+
await writeOpenClawConfigFile(next);
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
api.logger.info?.(`memoryrouter: doctor-safe registration skipped — ${err instanceof Error ? err.message : String(err)}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
322
401
|
/**
|
|
323
402
|
* Read all workspace files and return as a single text blob for token counting.
|
|
324
403
|
*/
|
|
@@ -396,6 +475,11 @@ const memoryRouterPlugin = {
|
|
|
396
475
|
return memoryKey;
|
|
397
476
|
}
|
|
398
477
|
const hasAnyKey = !!(memoryKey || (agentKeys && Object.keys(agentKeys).length > 0));
|
|
478
|
+
void (async () => {
|
|
479
|
+
await ensureDoctorSafePluginEntry(api);
|
|
480
|
+
if (hasAnyKey)
|
|
481
|
+
await selfHealNativeMemorySearch(api);
|
|
482
|
+
})();
|
|
399
483
|
if (hasAnyKey) {
|
|
400
484
|
if (memoryKey)
|
|
401
485
|
api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
@@ -563,6 +647,8 @@ If setting up MemoryRouter for another agent:
|
|
|
563
647
|
if (data.context) {
|
|
564
648
|
const elapsed = Date.now() - startMs;
|
|
565
649
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
|
|
650
|
+
if (elapsed > 2000 && data.metrics)
|
|
651
|
+
log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
566
652
|
const wrapped = wrapForInjection(data.context);
|
|
567
653
|
return { appendSystemContext: wrapped };
|
|
568
654
|
}
|
|
@@ -664,6 +750,8 @@ If setting up MemoryRouter for another agent:
|
|
|
664
750
|
if (data.context) {
|
|
665
751
|
const elapsed = Date.now() - startMs;
|
|
666
752
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
|
|
753
|
+
if (elapsed > 2000 && data.metrics)
|
|
754
|
+
log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
667
755
|
const wrapped = wrapForInjection(data.context);
|
|
668
756
|
return { appendSystemContext: wrapped };
|
|
669
757
|
}
|
|
@@ -879,8 +967,14 @@ If setting up MemoryRouter for another agent:
|
|
|
879
967
|
return;
|
|
880
968
|
}
|
|
881
969
|
try {
|
|
882
|
-
await
|
|
970
|
+
await mergePluginConfig(api, { key });
|
|
883
971
|
await setPluginEnabled(api, true);
|
|
972
|
+
try {
|
|
973
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
974
|
+
if (changed) console.log(` • Native memory search disabled — MemoryRouter now owns memory_search`);
|
|
975
|
+
} catch (err) {
|
|
976
|
+
console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
977
|
+
}
|
|
884
978
|
console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
|
|
885
979
|
console.log(` • memory_search + memory_get tools registered via plugin`);
|
|
886
980
|
console.log(`\nRun: openclaw mr upload to upload your memories`);
|
|
@@ -913,6 +1007,13 @@ If setting up MemoryRouter for another agent:
|
|
|
913
1007
|
// This avoids the catch-22 where disabling the plugin removes the `mr` command
|
|
914
1008
|
await setPluginConfig(api, {});
|
|
915
1009
|
// Note: we intentionally do NOT call setPluginEnabled(api, false)
|
|
1010
|
+
// Restore native memory search so the user isn't left with NO memory search.
|
|
1011
|
+
try {
|
|
1012
|
+
const changed = await setNativeMemorySearchEnabled(true);
|
|
1013
|
+
if (changed) console.log(" • Native memory search restored");
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
console.error(` ⚠ Could not restore native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
1016
|
+
}
|
|
916
1017
|
console.log("✓ MemoryRouter disabled (key cleared).");
|
|
917
1018
|
console.log(" • CLI still available — run `openclaw mr <key>` to re-enable");
|
|
918
1019
|
}
|
|
@@ -942,7 +1043,7 @@ If setting up MemoryRouter for another agent:
|
|
|
942
1043
|
return;
|
|
943
1044
|
}
|
|
944
1045
|
try {
|
|
945
|
-
await
|
|
1046
|
+
await mergePluginConfig(api, { key: memoryKey, density: name });
|
|
946
1047
|
console.log(`✓ Memory density set to ${name}`);
|
|
947
1048
|
}
|
|
948
1049
|
catch (err) {
|
|
@@ -960,11 +1061,8 @@ If setting up MemoryRouter for another agent:
|
|
|
960
1061
|
}
|
|
961
1062
|
const newLogging = !logging;
|
|
962
1063
|
try {
|
|
963
|
-
await
|
|
1064
|
+
await mergePluginConfig(api, {
|
|
964
1065
|
key: memoryKey,
|
|
965
|
-
endpoint: cfg?.endpoint,
|
|
966
|
-
density,
|
|
967
|
-
mode,
|
|
968
1066
|
logging: newLogging,
|
|
969
1067
|
});
|
|
970
1068
|
console.log(`✓ Logging ${newLogging ? "ON" : "OFF"} (restart gateway to apply)`);
|
|
@@ -1117,8 +1215,17 @@ If setting up MemoryRouter for another agent:
|
|
|
1117
1215
|
start: () => {
|
|
1118
1216
|
if (memoryKey)
|
|
1119
1217
|
api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
1218
|
+
if (hasAnyKey && !warmupTimer) {
|
|
1219
|
+
pingWarmup("startup");
|
|
1220
|
+
warmupTimer = setInterval(() => pingWarmup("keepalive"), 4 * 60 * 1000);
|
|
1221
|
+
warmupTimer.unref?.();
|
|
1222
|
+
}
|
|
1120
1223
|
},
|
|
1121
1224
|
stop: () => {
|
|
1225
|
+
if (warmupTimer) {
|
|
1226
|
+
clearInterval(warmupTimer);
|
|
1227
|
+
warmupTimer = undefined;
|
|
1228
|
+
}
|
|
1122
1229
|
api.logger.info?.("memoryrouter: stopped");
|
|
1123
1230
|
},
|
|
1124
1231
|
});
|
package/index.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { readFile, readdir, stat, lstat, writeFile, copyFile, mkdir } from "node:fs/promises";
|
|
12
|
-
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory } from "./memoryrouter-api.js";
|
|
12
|
+
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory, warmupMemory } from "./memoryrouter-api.js";
|
|
13
13
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import os from "node:os";
|
|
@@ -296,7 +296,7 @@ type CompatApi = OpenClawPluginApi & {
|
|
|
296
296
|
type JsonObject = Record<string, any>;
|
|
297
297
|
|
|
298
298
|
function openClawConfigPath(): string {
|
|
299
|
-
return process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
299
|
+
return process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
async function readOpenClawConfigFile(): Promise<JsonObject> {
|
|
@@ -320,6 +320,44 @@ async function writeOpenClawConfigFile(config: JsonObject): Promise<void> {
|
|
|
320
320
|
`, "utf8");
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// ──────────────────────────────────────────────────────
|
|
324
|
+
// Full-replace: disable OpenClaw's native memory search so MemoryRouter
|
|
325
|
+
// owns the memory_search tool (no name conflict, no native embeddings 429).
|
|
326
|
+
// memoryFlush (durable MEMORY.md writes) is intentionally left untouched.
|
|
327
|
+
// Global + subagents: writes agents.defaults.memorySearch.enabled.
|
|
328
|
+
// ──────────────────────────────────────────────────────
|
|
329
|
+
async function setNativeMemorySearchEnabled(enabled: boolean): Promise<boolean> {
|
|
330
|
+
const config = await readOpenClawConfigFile();
|
|
331
|
+
config.agents ??= {};
|
|
332
|
+
config.agents.defaults ??= {};
|
|
333
|
+
config.agents.defaults.memorySearch ??= {};
|
|
334
|
+
if (config.agents.defaults.memorySearch.enabled === enabled) return false; // no-op
|
|
335
|
+
config.agents.defaults.memorySearch.enabled = enabled;
|
|
336
|
+
await writeOpenClawConfigFile(config);
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Boot-time self-heal: if a key is configured but native search is still on,
|
|
341
|
+
// flip it off so already-installed users are fixed on restart after updating
|
|
342
|
+
// (no need to re-run `openclaw mr <key>`).
|
|
343
|
+
async function selfHealNativeMemorySearch(api: OpenClawPluginApi): Promise<void> {
|
|
344
|
+
try {
|
|
345
|
+
const config = await readOpenClawConfigFile();
|
|
346
|
+
const ms = config?.agents?.defaults?.memorySearch;
|
|
347
|
+
if (ms?.enabled === false) return; // already disabled — nothing to do
|
|
348
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
349
|
+
if (changed) {
|
|
350
|
+
api.logger.info?.(
|
|
351
|
+
"memoryrouter: disabled native memory search (full-replace). Restart gateway to apply.",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
api.logger.info?.(
|
|
356
|
+
`memoryrouter: self-heal skipped — ${err instanceof Error ? err.message : String(err)}`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
323
361
|
async function updateOpenClawPluginEntry(api: OpenClawPluginApi, patch: JsonObject): Promise<void> {
|
|
324
362
|
const config = await readOpenClawConfigFile();
|
|
325
363
|
config.plugins ??= {};
|
|
@@ -350,6 +388,47 @@ async function setPluginEnabled(api: OpenClawPluginApi, enabled: boolean): Promi
|
|
|
350
388
|
await updateOpenClawPluginEntry(api, { enabled });
|
|
351
389
|
}
|
|
352
390
|
|
|
391
|
+
async function mergePluginConfig(api: OpenClawPluginApi, patch: Record<string, unknown>): Promise<void> {
|
|
392
|
+
const current = await readOpenClawConfigFile();
|
|
393
|
+
const existing = current.plugins?.entries?.[api.id]?.config;
|
|
394
|
+
await setPluginConfig(api, {
|
|
395
|
+
...(existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {}),
|
|
396
|
+
...patch,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Keep mr-memory as a first-class plugin config entry without clobbering user settings.
|
|
401
|
+
// OpenClaw doctor validates plugin entries against discoverable manifests, so the entry
|
|
402
|
+
// must be present, enabled, and allowlisted when an allowlist exists.
|
|
403
|
+
async function ensureDoctorSafePluginEntry(api: OpenClawPluginApi): Promise<void> {
|
|
404
|
+
try {
|
|
405
|
+
const config = await readOpenClawConfigFile();
|
|
406
|
+
const plugins = config.plugins ?? {};
|
|
407
|
+
const entries = plugins.entries ?? {};
|
|
408
|
+
const existingEntry = entries[api.id] && typeof entries[api.id] === "object" && !Array.isArray(entries[api.id])
|
|
409
|
+
? entries[api.id]
|
|
410
|
+
: {};
|
|
411
|
+
const nextPlugins: JsonObject = {
|
|
412
|
+
...plugins,
|
|
413
|
+
entries: {
|
|
414
|
+
...entries,
|
|
415
|
+
[api.id]: {
|
|
416
|
+
...existingEntry,
|
|
417
|
+
enabled: true,
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
if (Array.isArray(plugins.allow) && !plugins.allow.includes(api.id)) {
|
|
422
|
+
nextPlugins.allow = [...plugins.allow, api.id];
|
|
423
|
+
}
|
|
424
|
+
const next = { ...config, plugins: nextPlugins };
|
|
425
|
+
if (JSON.stringify(next.plugins) === JSON.stringify(config.plugins)) return;
|
|
426
|
+
await writeOpenClawConfigFile(next);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
api.logger.info?.(`memoryrouter: doctor-safe registration skipped — ${err instanceof Error ? err.message : String(err)}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
353
432
|
/**
|
|
354
433
|
* Read all workspace files and return as a single text blob for token counting.
|
|
355
434
|
*/
|
|
@@ -422,6 +501,40 @@ const memoryRouterPlugin = {
|
|
|
422
501
|
}
|
|
423
502
|
|
|
424
503
|
const hasAnyKey = !!(memoryKey || (agentKeys && Object.keys(agentKeys).length > 0));
|
|
504
|
+
let warmupTimer: ReturnType<typeof setInterval> | undefined;
|
|
505
|
+
|
|
506
|
+
function configuredKeys(): string[] {
|
|
507
|
+
const keys = new Set<string>();
|
|
508
|
+
if (memoryKey) keys.add(memoryKey);
|
|
509
|
+
if (agentKeys) {
|
|
510
|
+
for (const key of Object.values(agentKeys)) {
|
|
511
|
+
if (key) keys.add(key);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return Array.from(keys);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function pingWarmup(reason: string): void {
|
|
518
|
+
for (const key of configuredKeys()) {
|
|
519
|
+
warmupMemory(endpoint, key, embeddings)
|
|
520
|
+
.then(async (res) => {
|
|
521
|
+
if (!res.ok) {
|
|
522
|
+
log(`memoryrouter: warmup ${reason} failed (${res.status})`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const data = await res.json().catch(() => null) as any;
|
|
526
|
+
log(`memoryrouter: warmup ${reason} ok (${data?.totalVectors ?? 0} vectors, ${data?.warmupTimeMs ?? "?"}ms)`);
|
|
527
|
+
})
|
|
528
|
+
.catch((err) => {
|
|
529
|
+
log(`memoryrouter: warmup ${reason} error — ${err instanceof Error ? err.message : String(err)}`);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
void (async () => {
|
|
535
|
+
await ensureDoctorSafePluginEntry(api);
|
|
536
|
+
if (hasAnyKey) await selfHealNativeMemorySearch(api);
|
|
537
|
+
})();
|
|
425
538
|
|
|
426
539
|
if (hasAnyKey) {
|
|
427
540
|
if (memoryKey) api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
@@ -592,11 +705,13 @@ If setting up MemoryRouter for another agent:
|
|
|
592
705
|
memories_found?: number;
|
|
593
706
|
tokens_billed?: number;
|
|
594
707
|
memory_tokens?: number;
|
|
708
|
+
metrics?: Record<string, unknown>;
|
|
595
709
|
};
|
|
596
710
|
|
|
597
711
|
if (data.context) {
|
|
598
712
|
const elapsed = Date.now() - startMs;
|
|
599
713
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
|
|
714
|
+
if (elapsed > 2000 && data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
600
715
|
const wrapped = wrapForInjection(data.context);
|
|
601
716
|
return { appendSystemContext: wrapped };
|
|
602
717
|
}
|
|
@@ -704,11 +819,13 @@ If setting up MemoryRouter for another agent:
|
|
|
704
819
|
memories_found?: number;
|
|
705
820
|
tokens_billed?: number;
|
|
706
821
|
memory_tokens?: number;
|
|
822
|
+
metrics?: Record<string, unknown>;
|
|
707
823
|
};
|
|
708
824
|
|
|
709
825
|
if (data.context) {
|
|
710
826
|
const elapsed = Date.now() - startMs;
|
|
711
827
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
|
|
828
|
+
if (elapsed > 2000 && data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
712
829
|
const wrapped = wrapForInjection(data.context);
|
|
713
830
|
return { appendSystemContext: wrapped };
|
|
714
831
|
}
|
|
@@ -933,8 +1050,14 @@ If setting up MemoryRouter for another agent:
|
|
|
933
1050
|
return;
|
|
934
1051
|
}
|
|
935
1052
|
try {
|
|
936
|
-
await
|
|
1053
|
+
await mergePluginConfig(api, { key });
|
|
937
1054
|
await setPluginEnabled(api, true);
|
|
1055
|
+
try {
|
|
1056
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
1057
|
+
if (changed) console.log(` • Native memory search disabled — MemoryRouter now owns memory_search`);
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
1060
|
+
}
|
|
938
1061
|
console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
|
|
939
1062
|
console.log(` • memory_search + memory_get tools registered via plugin`);
|
|
940
1063
|
console.log(`\nRun: openclaw mr upload to upload your memories`);
|
|
@@ -966,6 +1089,13 @@ If setting up MemoryRouter for another agent:
|
|
|
966
1089
|
// This avoids the catch-22 where disabling the plugin removes the `mr` command
|
|
967
1090
|
await setPluginConfig(api, {});
|
|
968
1091
|
// Note: we intentionally do NOT call setPluginEnabled(api, false)
|
|
1092
|
+
// Restore native memory search so the user isn't left with NO memory search.
|
|
1093
|
+
try {
|
|
1094
|
+
const changed = await setNativeMemorySearchEnabled(true);
|
|
1095
|
+
if (changed) console.log(" • Native memory search restored");
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
console.error(` ⚠ Could not restore native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
1098
|
+
}
|
|
969
1099
|
console.log("✓ MemoryRouter disabled (key cleared).");
|
|
970
1100
|
console.log(" • CLI still available — run `openclaw mr <key>` to re-enable");
|
|
971
1101
|
} catch (err) {
|
|
@@ -993,7 +1123,7 @@ If setting up MemoryRouter for another agent:
|
|
|
993
1123
|
.action(async () => {
|
|
994
1124
|
if (!memoryKey) { console.error("Not configured. Run: openclaw mr <key>"); return; }
|
|
995
1125
|
try {
|
|
996
|
-
await
|
|
1126
|
+
await mergePluginConfig(api, { key: memoryKey, density: name });
|
|
997
1127
|
console.log(`✓ Memory density set to ${name}`);
|
|
998
1128
|
} catch (err) {
|
|
999
1129
|
console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1011,11 +1141,8 @@ If setting up MemoryRouter for another agent:
|
|
|
1011
1141
|
}
|
|
1012
1142
|
const newLogging = !logging;
|
|
1013
1143
|
try {
|
|
1014
|
-
await
|
|
1144
|
+
await mergePluginConfig(api, {
|
|
1015
1145
|
key: memoryKey,
|
|
1016
|
-
endpoint: cfg?.endpoint,
|
|
1017
|
-
density,
|
|
1018
|
-
mode,
|
|
1019
1146
|
logging: newLogging,
|
|
1020
1147
|
});
|
|
1021
1148
|
console.log(`✓ Logging ${newLogging ? "ON" : "OFF"} (restart gateway to apply)`);
|
|
@@ -1184,8 +1311,17 @@ If setting up MemoryRouter for another agent:
|
|
|
1184
1311
|
id: "mr-memory",
|
|
1185
1312
|
start: () => {
|
|
1186
1313
|
if (memoryKey) api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
1314
|
+
if (hasAnyKey && !warmupTimer) {
|
|
1315
|
+
pingWarmup("startup");
|
|
1316
|
+
warmupTimer = setInterval(() => pingWarmup("keepalive"), 4 * 60 * 1000);
|
|
1317
|
+
warmupTimer.unref?.();
|
|
1318
|
+
}
|
|
1187
1319
|
},
|
|
1188
1320
|
stop: () => {
|
|
1321
|
+
if (warmupTimer) {
|
|
1322
|
+
clearInterval(warmupTimer);
|
|
1323
|
+
warmupTimer = undefined;
|
|
1324
|
+
}
|
|
1189
1325
|
api.logger.info?.("memoryrouter: stopped");
|
|
1190
1326
|
},
|
|
1191
1327
|
});
|
package/memoryrouter-api.js
CHANGED
|
@@ -47,3 +47,14 @@ export async function deleteMemories(endpoint, key, embeddings) {
|
|
|
47
47
|
headers: { Authorization: `Bearer ${key}` },
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
export async function warmupMemory(endpoint, key, embeddings) {
|
|
52
|
+
return fetch(`${endpoint}/v1/memory/warmup`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Bearer ${key}`,
|
|
56
|
+
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
57
|
+
},
|
|
58
|
+
signal: AbortSignal.timeout(15000),
|
|
59
|
+
});
|
|
60
|
+
}
|
package/memoryrouter-api.ts
CHANGED
|
@@ -58,3 +58,14 @@ export async function deleteMemories(endpoint: string, key: string, embeddings?:
|
|
|
58
58
|
headers: { Authorization: `Bearer ${key}` },
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
export async function warmupMemory(endpoint: string, key: string, embeddings?: string): Promise<Response> {
|
|
63
|
+
return fetch(`${endpoint}/v1/memory/warmup`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: `Bearer ${key}`,
|
|
67
|
+
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
68
|
+
},
|
|
69
|
+
signal: AbortSignal.timeout(15000),
|
|
70
|
+
});
|
|
71
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -46,6 +46,14 @@
|
|
|
46
46
|
"type": "string",
|
|
47
47
|
"enum": ["bge", "qwen"],
|
|
48
48
|
"description": "Embedding model: bge (1024d, default) or qwen (4096d)"
|
|
49
|
+
},
|
|
50
|
+
"agentKeys": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"pattern": "^mk[_-]"
|
|
55
|
+
},
|
|
56
|
+
"description": "Optional per-agent MemoryRouter keys, keyed by OpenClaw agent id"
|
|
49
57
|
}
|
|
50
58
|
},
|
|
51
59
|
"required": []
|