mr-memory 3.7.9 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/index.js +128 -36
- package/index.ts +78 -8
- package/openclaw.plugin.json +14 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@ Use it when you want OpenClaw memory that does not restart from zero every sessi
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
openclaw plugins install npm:mr-memory@3.
|
|
12
|
+
openclaw plugins install npm:mr-memory@3.8.0
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The explicit `npm:` source and version make the audited install deterministic. `mr-memory@3.
|
|
15
|
+
The explicit `npm:` source and version make the audited install deterministic. `mr-memory@3.8.0` is published on npm. A ClawHub listing has not been verified and is not required for this path.
|
|
16
16
|
|
|
17
17
|
## Setup
|
|
18
18
|
|
|
@@ -46,6 +46,16 @@ When enabled, relay hooks retrieve memory before agent/model input and capture t
|
|
|
46
46
|
|
|
47
47
|
Only direct user-to-AI conversation is stored. Tool use, subagent work, and internal processing are excluded. Historical workspace/session upload is a separate explicit `openclaw mr upload` operation; installing or enabling the relay does not imply an import already happened.
|
|
48
48
|
|
|
49
|
+
### Keep-warm
|
|
50
|
+
|
|
51
|
+
Retrieval is fastest against a warm vault, so the plugin keeps yours warm for **3 minutes after each message**. Every message re-opens that window; once it lapses with no new activity the warmup timer is cleared entirely, so an idle agent sends **no** background warmup traffic. There is no startup ping and no always-on keepalive.
|
|
52
|
+
|
|
53
|
+
Tune it with `keepWarmMs` (milliseconds, default `180000`; `0` disables warmup pings):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{ "plugins": { "entries": { "mr-memory": { "config": { "keepWarmMs": 180000 } } } } }
|
|
57
|
+
```
|
|
58
|
+
|
|
49
59
|
## Config
|
|
50
60
|
|
|
51
61
|
After `openclaw mr <key>`, config is stored at:
|
package/index.js
CHANGED
|
@@ -12,6 +12,15 @@ import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemo
|
|
|
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";
|
|
15
|
+
/** Keep-warm defaults.
|
|
16
|
+
*
|
|
17
|
+
* Warmth only matters in the minutes right after real activity, so warmup
|
|
18
|
+
* pings are activity-scoped instead of always-on: every message opens a
|
|
19
|
+
* KEEP_WARM window, pings fire at KEEP_WARM_PING_INTERVAL_MS inside it, and
|
|
20
|
+
* the timer is cleared completely once the window closes. An idle gateway
|
|
21
|
+
* sends zero warmup traffic. */
|
|
22
|
+
const DEFAULT_KEEP_WARM_MS = 3 * 60 * 1000; // 3 minutes after the last message
|
|
23
|
+
const KEEP_WARM_PING_INTERVAL_MS = 60 * 1000; // cadence inside the window
|
|
15
24
|
/** Strip media-attached references and OpenClaw media instruction text so
|
|
16
25
|
* old screenshots/photos/audio don't get stored and re-injected forever. */
|
|
17
26
|
const MEDIA_ATTACHED_RE = /\[media attached[^\]]*\]/gi;
|
|
@@ -299,37 +308,34 @@ async function writeOpenClawConfigFile(config) {
|
|
|
299
308
|
// Global + subagents: writes agents.defaults.memorySearch.enabled.
|
|
300
309
|
// ──────────────────────────────────────────────────────
|
|
301
310
|
async function setNativeMemorySearchEnabled(enabled) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
311
|
+
const config = await readOpenClawConfigFile();
|
|
312
|
+
config.agents ??= {};
|
|
313
|
+
config.agents.defaults ??= {};
|
|
314
|
+
config.agents.defaults.memorySearch ??= {};
|
|
315
|
+
if (config.agents.defaults.memorySearch.enabled === enabled)
|
|
316
|
+
return false; // no-op
|
|
317
|
+
config.agents.defaults.memorySearch.enabled = enabled;
|
|
318
|
+
await writeOpenClawConfigFile(config);
|
|
319
|
+
return true;
|
|
310
320
|
}
|
|
311
|
-
|
|
312
321
|
// Boot-time self-heal: if a key is configured but native search is still on,
|
|
313
322
|
// flip it off so already-installed users are fixed on restart after updating
|
|
314
323
|
// (no need to re-run `openclaw mr <key>`).
|
|
315
324
|
async function selfHealNativeMemorySearch(api) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
+
try {
|
|
326
|
+
const config = await readOpenClawConfigFile();
|
|
327
|
+
const ms = config?.agents?.defaults?.memorySearch;
|
|
328
|
+
if (ms?.enabled === false)
|
|
329
|
+
return; // already disabled — nothing to do
|
|
330
|
+
const changed = await setNativeMemorySearchEnabled(false);
|
|
331
|
+
if (changed) {
|
|
332
|
+
api.logger.info?.("memoryrouter: disabled native memory search (full-replace). Restart gateway to apply.");
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
api.logger.info?.(`memoryrouter: self-heal skipped — ${err instanceof Error ? err.message : String(err)}`);
|
|
325
337
|
}
|
|
326
|
-
} catch (err) {
|
|
327
|
-
api.logger.info?.(
|
|
328
|
-
`memoryrouter: self-heal skipped — ${err instanceof Error ? err.message : String(err)}`,
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
338
|
}
|
|
332
|
-
|
|
333
339
|
async function updateOpenClawPluginEntry(api, patch) {
|
|
334
340
|
const config = await readOpenClawConfigFile();
|
|
335
341
|
config.plugins ??= {};
|
|
@@ -475,6 +481,80 @@ const memoryRouterPlugin = {
|
|
|
475
481
|
return memoryKey;
|
|
476
482
|
}
|
|
477
483
|
const hasAnyKey = !!(memoryKey || (agentKeys && Object.keys(agentKeys).length > 0));
|
|
484
|
+
// Keep-warm window: 0 (or negative) disables warmup pings entirely.
|
|
485
|
+
const keepWarmMs = typeof cfg?.keepWarmMs === "number" && cfg.keepWarmMs >= 0
|
|
486
|
+
? cfg.keepWarmMs
|
|
487
|
+
: DEFAULT_KEEP_WARM_MS;
|
|
488
|
+
let warmupTimer;
|
|
489
|
+
// undefined = no activity yet (never compare against 0; a 0 timestamp is a
|
|
490
|
+
// valid clock value and must not read as "no activity").
|
|
491
|
+
let lastActivityAt;
|
|
492
|
+
// Starts true so keep-warm works even if the host never calls service.start();
|
|
493
|
+
// service.stop() flips it off so no timer can outlive the plugin.
|
|
494
|
+
let serviceRunning = true;
|
|
495
|
+
function configuredKeys() {
|
|
496
|
+
const keys = new Set();
|
|
497
|
+
if (memoryKey)
|
|
498
|
+
keys.add(memoryKey);
|
|
499
|
+
if (agentKeys) {
|
|
500
|
+
for (const key of Object.values(agentKeys)) {
|
|
501
|
+
if (key)
|
|
502
|
+
keys.add(key);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return Array.from(keys);
|
|
506
|
+
}
|
|
507
|
+
function pingWarmup(reason) {
|
|
508
|
+
for (const key of configuredKeys()) {
|
|
509
|
+
warmupMemory(endpoint, key, embeddings)
|
|
510
|
+
.then(async (res) => {
|
|
511
|
+
if (!res.ok) {
|
|
512
|
+
log(`memoryrouter: warmup ${reason} failed (${res.status})`);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const data = await res.json().catch(() => null);
|
|
516
|
+
log(`memoryrouter: warmup ${reason} ok (${data?.totalVectors ?? 0} vectors, ${data?.warmupTimeMs ?? "?"}ms)`);
|
|
517
|
+
})
|
|
518
|
+
.catch((err) => {
|
|
519
|
+
log(`memoryrouter: warmup ${reason} error — ${err instanceof Error ? err.message : String(err)}`);
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function stopWarmLoop() {
|
|
524
|
+
if (warmupTimer) {
|
|
525
|
+
clearInterval(warmupTimer);
|
|
526
|
+
warmupTimer = undefined;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/** True while we're still inside the post-message keep-warm window. */
|
|
530
|
+
function withinWarmWindow() {
|
|
531
|
+
if (keepWarmMs <= 0 || lastActivityAt === undefined)
|
|
532
|
+
return false;
|
|
533
|
+
return Date.now() - lastActivityAt <= keepWarmMs;
|
|
534
|
+
}
|
|
535
|
+
/** Called on every message-driven activity (prepare / ingest).
|
|
536
|
+
* Opens (or extends) the keep-warm window and makes sure the loop runs.
|
|
537
|
+
* Once the window lapses with no new activity the interval is cleared, so
|
|
538
|
+
* an idle gateway sends no warmup traffic at all. */
|
|
539
|
+
function noteActivity() {
|
|
540
|
+
if (!hasAnyKey || keepWarmMs <= 0 || !serviceRunning)
|
|
541
|
+
return;
|
|
542
|
+
lastActivityAt = Date.now();
|
|
543
|
+
if (warmupTimer)
|
|
544
|
+
return; // window extended; existing loop keeps running
|
|
545
|
+
// No immediate ping: the prepare/ingest request that triggered this call
|
|
546
|
+
// already warmed the vault server-side. Pings only cover the idle gaps
|
|
547
|
+
// inside the window.
|
|
548
|
+
warmupTimer = setInterval(() => {
|
|
549
|
+
if (!withinWarmWindow()) {
|
|
550
|
+
log("memoryrouter: keep-warm window elapsed — pings stopped until next message");
|
|
551
|
+
stopWarmLoop();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
pingWarmup("keepalive");
|
|
555
|
+
}, KEEP_WARM_PING_INTERVAL_MS);
|
|
556
|
+
warmupTimer.unref?.();
|
|
557
|
+
}
|
|
478
558
|
void (async () => {
|
|
479
559
|
await ensureDoctorSafePluginEntry(api);
|
|
480
560
|
if (hasAnyKey)
|
|
@@ -628,6 +708,8 @@ If setting up MemoryRouter for another agent:
|
|
|
628
708
|
contextPayload.push({ role: "user", content: taskPrompt });
|
|
629
709
|
}
|
|
630
710
|
}
|
|
711
|
+
// Message activity — open/extend the keep-warm window.
|
|
712
|
+
noteActivity();
|
|
631
713
|
const res = await prepareMemory(endpoint, activeKey, {
|
|
632
714
|
messages: contextPayload,
|
|
633
715
|
density,
|
|
@@ -728,6 +810,8 @@ If setting up MemoryRouter for another agent:
|
|
|
728
810
|
}
|
|
729
811
|
}
|
|
730
812
|
// 4. Call /v1/memory/prepare (no session_id — always search core vault)
|
|
813
|
+
// Message activity — open/extend the keep-warm window.
|
|
814
|
+
noteActivity();
|
|
731
815
|
const res = await prepareMemory(endpoint, activeKey, {
|
|
732
816
|
messages: contextPayload,
|
|
733
817
|
density,
|
|
@@ -834,6 +918,8 @@ If setting up MemoryRouter for another agent:
|
|
|
834
918
|
log("memoryrouter: skipped ingest — system noise filtered out");
|
|
835
919
|
return;
|
|
836
920
|
}
|
|
921
|
+
// Message activity — open/extend the keep-warm window.
|
|
922
|
+
noteActivity();
|
|
837
923
|
// Fire and forget — don't block message delivery waiting for ingest
|
|
838
924
|
ingestMemory(endpoint, activeKey, {
|
|
839
925
|
messages: toStore,
|
|
@@ -971,8 +1057,10 @@ If setting up MemoryRouter for another agent:
|
|
|
971
1057
|
await setPluginEnabled(api, true);
|
|
972
1058
|
try {
|
|
973
1059
|
const changed = await setNativeMemorySearchEnabled(false);
|
|
974
|
-
if (changed)
|
|
975
|
-
|
|
1060
|
+
if (changed)
|
|
1061
|
+
console.log(` • Native memory search disabled — MemoryRouter now owns memory_search`);
|
|
1062
|
+
}
|
|
1063
|
+
catch (err) {
|
|
976
1064
|
console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
977
1065
|
}
|
|
978
1066
|
console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
|
|
@@ -1010,8 +1098,10 @@ If setting up MemoryRouter for another agent:
|
|
|
1010
1098
|
// Restore native memory search so the user isn't left with NO memory search.
|
|
1011
1099
|
try {
|
|
1012
1100
|
const changed = await setNativeMemorySearchEnabled(true);
|
|
1013
|
-
if (changed)
|
|
1014
|
-
|
|
1101
|
+
if (changed)
|
|
1102
|
+
console.log(" • Native memory search restored");
|
|
1103
|
+
}
|
|
1104
|
+
catch (err) {
|
|
1015
1105
|
console.error(` ⚠ Could not restore native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
1016
1106
|
}
|
|
1017
1107
|
console.log("✓ MemoryRouter disabled (key cleared).");
|
|
@@ -1215,17 +1305,19 @@ If setting up MemoryRouter for another agent:
|
|
|
1215
1305
|
start: () => {
|
|
1216
1306
|
if (memoryKey)
|
|
1217
1307
|
api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1308
|
+
serviceRunning = true;
|
|
1309
|
+
// No startup ping and no always-on keepalive: warmth is driven by real
|
|
1310
|
+
// activity. The first message opens the keep-warm window.
|
|
1311
|
+
if (hasAnyKey) {
|
|
1312
|
+
log(keepWarmMs > 0
|
|
1313
|
+
? `memoryrouter: keep-warm armed (${Math.round(keepWarmMs / 1000)}s after each message)`
|
|
1314
|
+
: "memoryrouter: keep-warm disabled (keepWarmMs: 0)");
|
|
1222
1315
|
}
|
|
1223
1316
|
},
|
|
1224
1317
|
stop: () => {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
}
|
|
1318
|
+
serviceRunning = false;
|
|
1319
|
+
lastActivityAt = undefined;
|
|
1320
|
+
stopWarmLoop();
|
|
1229
1321
|
api.logger.info?.("memoryrouter: stopped");
|
|
1230
1322
|
},
|
|
1231
1323
|
});
|
package/index.ts
CHANGED
|
@@ -17,6 +17,16 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
|
17
17
|
|
|
18
18
|
const DEFAULT_ENDPOINT = "https://api.memoryrouter.ai";
|
|
19
19
|
|
|
20
|
+
/** Keep-warm defaults.
|
|
21
|
+
*
|
|
22
|
+
* Warmth only matters in the minutes right after real activity, so warmup
|
|
23
|
+
* pings are activity-scoped instead of always-on: every message opens a
|
|
24
|
+
* KEEP_WARM window, pings fire at KEEP_WARM_PING_INTERVAL_MS inside it, and
|
|
25
|
+
* the timer is cleared completely once the window closes. An idle gateway
|
|
26
|
+
* sends zero warmup traffic. */
|
|
27
|
+
const DEFAULT_KEEP_WARM_MS = 3 * 60 * 1000; // 3 minutes after the last message
|
|
28
|
+
const KEEP_WARM_PING_INTERVAL_MS = 60 * 1000; // cadence inside the window
|
|
29
|
+
|
|
20
30
|
/** Strip media-attached references and OpenClaw media instruction text so
|
|
21
31
|
* old screenshots/photos/audio don't get stored and re-injected forever. */
|
|
22
32
|
const MEDIA_ATTACHED_RE = /\[media attached[^\]]*\]/gi;
|
|
@@ -282,6 +292,9 @@ type MemoryRouterConfig = {
|
|
|
282
292
|
logging?: boolean;
|
|
283
293
|
embeddings?: string;
|
|
284
294
|
agentKeys?: Record<string, string>;
|
|
295
|
+
/** How long to keep the vault warm after the last message, in ms.
|
|
296
|
+
* Default 180000 (3 minutes). Set 0 to disable warmup pings entirely. */
|
|
297
|
+
keepWarmMs?: number;
|
|
285
298
|
};
|
|
286
299
|
|
|
287
300
|
// ──────────────────────────────────────────────────────
|
|
@@ -501,7 +514,18 @@ const memoryRouterPlugin = {
|
|
|
501
514
|
}
|
|
502
515
|
|
|
503
516
|
const hasAnyKey = !!(memoryKey || (agentKeys && Object.keys(agentKeys).length > 0));
|
|
517
|
+
|
|
518
|
+
// Keep-warm window: 0 (or negative) disables warmup pings entirely.
|
|
519
|
+
const keepWarmMs = typeof cfg?.keepWarmMs === "number" && cfg.keepWarmMs >= 0
|
|
520
|
+
? cfg.keepWarmMs
|
|
521
|
+
: DEFAULT_KEEP_WARM_MS;
|
|
504
522
|
let warmupTimer: ReturnType<typeof setInterval> | undefined;
|
|
523
|
+
// undefined = no activity yet (never compare against 0; a 0 timestamp is a
|
|
524
|
+
// valid clock value and must not read as "no activity").
|
|
525
|
+
let lastActivityAt: number | undefined;
|
|
526
|
+
// Starts true so keep-warm works even if the host never calls service.start();
|
|
527
|
+
// service.stop() flips it off so no timer can outlive the plugin.
|
|
528
|
+
let serviceRunning = true;
|
|
505
529
|
|
|
506
530
|
function configuredKeys(): string[] {
|
|
507
531
|
const keys = new Set<string>();
|
|
@@ -531,6 +555,41 @@ const memoryRouterPlugin = {
|
|
|
531
555
|
}
|
|
532
556
|
}
|
|
533
557
|
|
|
558
|
+
function stopWarmLoop(): void {
|
|
559
|
+
if (warmupTimer) {
|
|
560
|
+
clearInterval(warmupTimer);
|
|
561
|
+
warmupTimer = undefined;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** True while we're still inside the post-message keep-warm window. */
|
|
566
|
+
function withinWarmWindow(): boolean {
|
|
567
|
+
if (keepWarmMs <= 0 || lastActivityAt === undefined) return false;
|
|
568
|
+
return Date.now() - lastActivityAt <= keepWarmMs;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** Called on every message-driven activity (prepare / ingest).
|
|
572
|
+
* Opens (or extends) the keep-warm window and makes sure the loop runs.
|
|
573
|
+
* Once the window lapses with no new activity the interval is cleared, so
|
|
574
|
+
* an idle gateway sends no warmup traffic at all. */
|
|
575
|
+
function noteActivity(): void {
|
|
576
|
+
if (!hasAnyKey || keepWarmMs <= 0 || !serviceRunning) return;
|
|
577
|
+
lastActivityAt = Date.now();
|
|
578
|
+
if (warmupTimer) return; // window extended; existing loop keeps running
|
|
579
|
+
// No immediate ping: the prepare/ingest request that triggered this call
|
|
580
|
+
// already warmed the vault server-side. Pings only cover the idle gaps
|
|
581
|
+
// inside the window.
|
|
582
|
+
warmupTimer = setInterval(() => {
|
|
583
|
+
if (!withinWarmWindow()) {
|
|
584
|
+
log("memoryrouter: keep-warm window elapsed — pings stopped until next message");
|
|
585
|
+
stopWarmLoop();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
pingWarmup("keepalive");
|
|
589
|
+
}, KEEP_WARM_PING_INTERVAL_MS);
|
|
590
|
+
warmupTimer.unref?.();
|
|
591
|
+
}
|
|
592
|
+
|
|
534
593
|
void (async () => {
|
|
535
594
|
await ensureDoctorSafePluginEntry(api);
|
|
536
595
|
if (hasAnyKey) await selfHealNativeMemorySearch(api);
|
|
@@ -684,6 +743,9 @@ If setting up MemoryRouter for another agent:
|
|
|
684
743
|
}
|
|
685
744
|
|
|
686
745
|
|
|
746
|
+
// Message activity — open/extend the keep-warm window.
|
|
747
|
+
noteActivity();
|
|
748
|
+
|
|
687
749
|
const res = await prepareMemory(endpoint, activeKey, {
|
|
688
750
|
messages: contextPayload,
|
|
689
751
|
density,
|
|
@@ -795,6 +857,9 @@ If setting up MemoryRouter for another agent:
|
|
|
795
857
|
|
|
796
858
|
// 4. Call /v1/memory/prepare (no session_id — always search core vault)
|
|
797
859
|
|
|
860
|
+
// Message activity — open/extend the keep-warm window.
|
|
861
|
+
noteActivity();
|
|
862
|
+
|
|
798
863
|
const res = await prepareMemory(endpoint, activeKey, {
|
|
799
864
|
messages: contextPayload,
|
|
800
865
|
density,
|
|
@@ -914,6 +979,9 @@ If setting up MemoryRouter for another agent:
|
|
|
914
979
|
return;
|
|
915
980
|
}
|
|
916
981
|
|
|
982
|
+
// Message activity — open/extend the keep-warm window.
|
|
983
|
+
noteActivity();
|
|
984
|
+
|
|
917
985
|
// Fire and forget — don't block message delivery waiting for ingest
|
|
918
986
|
ingestMemory(endpoint, activeKey, {
|
|
919
987
|
messages: toStore,
|
|
@@ -1311,17 +1379,19 @@ If setting up MemoryRouter for another agent:
|
|
|
1311
1379
|
id: "mr-memory",
|
|
1312
1380
|
start: () => {
|
|
1313
1381
|
if (memoryKey) api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1382
|
+
serviceRunning = true;
|
|
1383
|
+
// No startup ping and no always-on keepalive: warmth is driven by real
|
|
1384
|
+
// activity. The first message opens the keep-warm window.
|
|
1385
|
+
if (hasAnyKey) {
|
|
1386
|
+
log(keepWarmMs > 0
|
|
1387
|
+
? `memoryrouter: keep-warm armed (${Math.round(keepWarmMs / 1000)}s after each message)`
|
|
1388
|
+
: "memoryrouter: keep-warm disabled (keepWarmMs: 0)");
|
|
1318
1389
|
}
|
|
1319
1390
|
},
|
|
1320
1391
|
stop: () => {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
}
|
|
1392
|
+
serviceRunning = false;
|
|
1393
|
+
lastActivityAt = undefined;
|
|
1394
|
+
stopWarmLoop();
|
|
1325
1395
|
api.logger.info?.("memoryrouter: stopped");
|
|
1326
1396
|
},
|
|
1327
1397
|
});
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "mr-memory",
|
|
3
|
+
"name": "MemoryRouter Memory",
|
|
4
|
+
"description": "Persistent memory for OpenClaw, powered by MemoryRouter. Memory that survives sessions, compaction, and model changes.",
|
|
5
|
+
"icon": "https://memoryrouter.ai/logo.png",
|
|
3
6
|
"uiHints": {
|
|
4
7
|
"key": {
|
|
5
8
|
"label": "Memory Key",
|
|
@@ -18,6 +21,12 @@
|
|
|
18
21
|
"placeholder": "high",
|
|
19
22
|
"advanced": true,
|
|
20
23
|
"help": "Amount of memory to inject: low (40), high (80), xhigh (160)"
|
|
24
|
+
},
|
|
25
|
+
"keepWarmMs": {
|
|
26
|
+
"label": "Keep Warm Window (ms)",
|
|
27
|
+
"placeholder": "180000",
|
|
28
|
+
"advanced": true,
|
|
29
|
+
"help": "How long to keep your vault warm after each message. Default 180000 (3 min). Set 0 to disable warmup pings."
|
|
21
30
|
}
|
|
22
31
|
},
|
|
23
32
|
"configSchema": {
|
|
@@ -47,6 +56,11 @@
|
|
|
47
56
|
"enum": ["bge", "qwen"],
|
|
48
57
|
"description": "Embedding model: bge (1024d, default) or qwen (4096d)"
|
|
49
58
|
},
|
|
59
|
+
"keepWarmMs": {
|
|
60
|
+
"type": "integer",
|
|
61
|
+
"minimum": 0,
|
|
62
|
+
"description": "Keep the vault warm for this many ms after each message. Default 180000 (3 minutes). 0 disables warmup pings entirely."
|
|
63
|
+
},
|
|
50
64
|
"agentKeys": {
|
|
51
65
|
"type": "object",
|
|
52
66
|
"additionalProperties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mr-memory",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Persistent memory for OpenClaw, powered by MemoryRouter. Relay-based AI agent memory across sessions, compaction, and model changes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -44,7 +44,14 @@
|
|
|
44
44
|
"openclaw": {
|
|
45
45
|
"extensions": [
|
|
46
46
|
"./index.js"
|
|
47
|
-
]
|
|
47
|
+
],
|
|
48
|
+
"compat": {
|
|
49
|
+
"pluginApi": ">=2026.3.7"
|
|
50
|
+
},
|
|
51
|
+
"install": {
|
|
52
|
+
"npmSpec": "mr-memory",
|
|
53
|
+
"minHostVersion": ">=2026.3.7"
|
|
54
|
+
}
|
|
48
55
|
},
|
|
49
56
|
"main": "./index.js",
|
|
50
57
|
"bugs": {
|