mr-memory 3.13.1 → 3.14.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 +33 -4
- package/index.js +221 -97
- package/index.ts +223 -96
- package/memoryrouter-api.ts +3 -1
- package/openclaw.plugin.json +80 -33
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -9,10 +9,14 @@ 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
|
|
12
|
+
openclaw plugins install --force --accept-capabilities npm:mr-memory
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
For OpenClaw 2026.9.x and later, use a supported Node version (24.16+ on Node 24 LTS, or 26.1+ on Node 26; Node 25 is not supported). The flags explicitly approve the npm trust and capability prompts.
|
|
16
|
+
|
|
17
|
+
The explicit `npm:` source installs the latest published `mr-memory` release from npm. A ClawHub listing has not been verified and is not required for this path.
|
|
18
|
+
|
|
19
|
+
OpenClaw prints `OpenClaw can't verify where this plugin came from ... reason=provenance-invalid` for every plugin that is not an official OpenClaw package. That line is informational: the plugin is installed and loads.
|
|
16
20
|
|
|
17
21
|
## Setup
|
|
18
22
|
|
|
@@ -24,14 +28,34 @@ openclaw mr upload # Upload workspace + session history
|
|
|
24
28
|
## Commands
|
|
25
29
|
|
|
26
30
|
```bash
|
|
31
|
+
openclaw mr doctor # Repair plugin registration + native memory-search settings
|
|
27
32
|
openclaw mr status # Vault stats
|
|
28
33
|
openclaw mr upload # Upload workspace + sessions
|
|
29
34
|
openclaw mr upload --brain ~/.notopenclaw # Upload from a different agent
|
|
30
35
|
openclaw mr off # Disable
|
|
31
36
|
openclaw mr delete # Clear vault
|
|
32
37
|
openclaw mr <key> # Enable with key
|
|
38
|
+
openclaw mr low # Up to 21 memories (12 raw + 6 tier 2 + 3 tier 3)
|
|
39
|
+
openclaw mr medium # Up to 43 memories (25 + 12 + 6), balanced default
|
|
40
|
+
openclaw mr high # Up to 87 memories (50 + 25 + 12)
|
|
33
41
|
```
|
|
34
42
|
|
|
43
|
+
Loading the plugin never writes your OpenClaw config. Setup (`openclaw mr <key>`) and `openclaw mr doctor` explicitly repair its enabled entry and allowlist membership, disable native memory search when a memory key is configured, and on OpenClaw 2026.10+ grant `plugins.entries.mr-memory.hooks.allowConversationAccess` (without that grant OpenClaw silently drops the `before_prompt_build`, `llm_input` and `agent_end` hooks of every non-bundled plugin, so no memory would be injected or captured). Existing users who see a config-repair hint should run `openclaw mr doctor`, then restart their gateway. On current OpenClaw this uses the host’s locked config mutation API and `memory.search.enabled`; older OpenClaw and John’s Harness use `agents.defaults.memorySearch.enabled`. Repairs never restart the gateway automatically. Native memory flush is left untouched.
|
|
44
|
+
|
|
45
|
+
The plugin declares no OpenClaw version floor on purpose. OpenClaw evaluates `openclaw.compat.pluginApi` and `openclaw.install.minHostVersion` against its own version string, and when that string is not semver (for example `OPENCLAW_VERSION=latest` exported in the shell, as the official installer documents) a declared floor makes OpenClaw skip the plugin and the install fails with `package owner "mr-memory" has no authoritative runtime child list`. mr-memory detects host capabilities at runtime instead.
|
|
46
|
+
|
|
47
|
+
On OpenClaw 2026.10 and later, non-bundled plugins only receive conversation hooks (`before_prompt_build`, `llm_input`, `agent_end`) when `plugins.entries.mr-memory.hooks.allowConversationAccess` is `true`. Setup and `doctor` grant it on those hosts; without it the plugin loads but never injects or captures memory. Run `openclaw plugins doctor` to confirm no hooks are blocked. The `provenance-invalid` warning from `openclaw plugins doctor` is informational: it applies to every npm plugin that is not in OpenClaw’s official catalog.
|
|
48
|
+
|
|
49
|
+
### Compatibility (3.14.0)
|
|
50
|
+
|
|
51
|
+
`mr-memory` no longer declares a host version floor (`openclaw.compat.pluginApi` / `install.minHostVersion`). OpenClaw 2026.10 reads the host version from `OPENCLAW_VERSION` when it is exported, and the official install instructions export `OPENCLAW_VERSION=latest`; a non-semver string made the host skip the plugin and fail the install with `no authoritative runtime child list`. The plugin detects host capabilities at runtime instead. The manifest now declares its six tools under `contracts.tools` (2026.10 rejects undeclared tool registrations) and asks for gateway startup activation.
|
|
52
|
+
|
|
53
|
+
## Memory Density
|
|
54
|
+
|
|
55
|
+
The current density choices are `low`, `medium`, and `high`. New installs and configs without an explicit density use `medium`, the balanced default. Existing explicit `low` or `high` settings are unchanged. Legacy `xhigh` configs remain valid so upgrades do not break the gateway, but the plugin normalizes `xhigh` to `high` and no longer advertises it as a current option.
|
|
56
|
+
|
|
57
|
+
The totals are tier-aware retrieval budgets, not raw-memory-only limits. Actual results can be lower when fewer relevant memories match.
|
|
58
|
+
|
|
35
59
|
## Upload Options
|
|
36
60
|
|
|
37
61
|
```bash
|
|
@@ -102,19 +126,24 @@ After `openclaw mr <key>`, config is stored at:
|
|
|
102
126
|
```json
|
|
103
127
|
{
|
|
104
128
|
"plugins": {
|
|
129
|
+
"allow": ["mr-memory"],
|
|
105
130
|
"entries": {
|
|
106
|
-
"
|
|
131
|
+
"mr-memory": {
|
|
107
132
|
"enabled": true,
|
|
133
|
+
"hooks": { "allowConversationAccess": true },
|
|
108
134
|
"config": {
|
|
109
135
|
"key": "mk_xxx",
|
|
110
136
|
"endpoint": "https://api.memoryrouter.ai"
|
|
111
137
|
}
|
|
112
138
|
}
|
|
113
139
|
}
|
|
114
|
-
}
|
|
140
|
+
},
|
|
141
|
+
"memory": { "search": { "enabled": false } }
|
|
115
142
|
}
|
|
116
143
|
```
|
|
117
144
|
|
|
145
|
+
`plugins.allow` is only extended when it already exists, and the `hooks` grant is only written on OpenClaw builds whose schema accepts it.
|
|
146
|
+
|
|
118
147
|
## Cross-AI and MCP Memory
|
|
119
148
|
|
|
120
149
|
The OpenClaw plugin uses relay hooks. Separately, the same MemoryRouter vault can connect to supported ChatGPT, Claude, Claude Code, Codex, and compatible MCP clients. Each host follows its own integration contract. Hosted ChatGPT and Claude connectors are model-directed, do not passively capture every conversation, and do not import old conversations just by connecting.
|
package/index.js
CHANGED
|
@@ -290,10 +290,55 @@ const WORKSPACE_FILES = [
|
|
|
290
290
|
"IDENTITY.md", "USER.md", "MEMORY.md", "HEARTBEAT.md",
|
|
291
291
|
"TOOLS.md", "AGENTS.md", "SOUL.md", "BOOTSTRAP.md",
|
|
292
292
|
];
|
|
293
|
+
// ──────────────────────────────────────────────────────
|
|
294
|
+
// Helpers
|
|
295
|
+
// ──────────────────────────────────────────────────────
|
|
296
|
+
/** Current densities are low, medium, and high. Legacy xhigh remains valid in
|
|
297
|
+
* existing configs but is deliberately hidden from current choices and sent
|
|
298
|
+
* to MemoryRouter as high. Missing or invalid values use the balanced medium
|
|
299
|
+
* default. */
|
|
300
|
+
function normalizeMemoryDensity(value) {
|
|
301
|
+
switch (value) {
|
|
302
|
+
case "low":
|
|
303
|
+
case "medium":
|
|
304
|
+
case "high":
|
|
305
|
+
return value;
|
|
306
|
+
case "xhigh":
|
|
307
|
+
return "high";
|
|
308
|
+
default:
|
|
309
|
+
return "medium";
|
|
310
|
+
}
|
|
311
|
+
}
|
|
293
312
|
function openClawConfigPath() {
|
|
294
313
|
return process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
295
314
|
}
|
|
296
|
-
|
|
315
|
+
function hostConfigApi(api) {
|
|
316
|
+
return api.runtime?.config;
|
|
317
|
+
}
|
|
318
|
+
// OpenClaw's current()/mutateConfigFile API accompanies the canonical memory
|
|
319
|
+
// owner schema (memory.search). Older OpenClaw and John's Harness retain
|
|
320
|
+
// agents.defaults.memorySearch. Do not use the host version: forks version
|
|
321
|
+
// independently of their upstream config schema.
|
|
322
|
+
function usesCanonicalMemorySearch(api) {
|
|
323
|
+
return typeof hostConfigApi(api)?.current === "function";
|
|
324
|
+
}
|
|
325
|
+
function nativeMemorySearchDisabled(api) {
|
|
326
|
+
const config = api.config;
|
|
327
|
+
// Metadata-only registration may deliberately throw on api.runtime access.
|
|
328
|
+
// Read the provided config only; runtime capability detection belongs in CLI actions.
|
|
329
|
+
return config?.memory?.search?.enabled === false
|
|
330
|
+
|| config?.agents?.defaults?.memorySearch?.enabled === false;
|
|
331
|
+
}
|
|
332
|
+
// Only explicit CLI actions call these helpers. Loading/registering the plugin
|
|
333
|
+
// must never mutate the host config (including fire-and-forget writes).
|
|
334
|
+
async function readOpenClawConfigFile(api) {
|
|
335
|
+
const runtimeConfig = hostConfigApi(api);
|
|
336
|
+
if (typeof runtimeConfig?.current === "function") {
|
|
337
|
+
return structuredClone(runtimeConfig.current());
|
|
338
|
+
}
|
|
339
|
+
if (typeof runtimeConfig?.loadConfig === "function") {
|
|
340
|
+
return structuredClone(runtimeConfig.loadConfig());
|
|
341
|
+
}
|
|
297
342
|
try {
|
|
298
343
|
return JSON.parse(await readFile(openClawConfigPath(), "utf8"));
|
|
299
344
|
}
|
|
@@ -303,7 +348,13 @@ async function readOpenClawConfigFile() {
|
|
|
303
348
|
throw err;
|
|
304
349
|
}
|
|
305
350
|
}
|
|
306
|
-
async function writeOpenClawConfigFile(config) {
|
|
351
|
+
async function writeOpenClawConfigFile(api, config) {
|
|
352
|
+
const runtimeConfig = hostConfigApi(api);
|
|
353
|
+
if (typeof runtimeConfig?.writeConfigFile === "function") {
|
|
354
|
+
await runtimeConfig.writeConfigFile(config);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// Legacy hosts only: no native config writer is exposed.
|
|
307
358
|
const configPath = openClawConfigPath();
|
|
308
359
|
await mkdir(dirname(configPath), { recursive: true });
|
|
309
360
|
try {
|
|
@@ -316,51 +367,74 @@ async function writeOpenClawConfigFile(config) {
|
|
|
316
367
|
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
317
368
|
`, "utf8");
|
|
318
369
|
}
|
|
370
|
+
async function mutateOpenClawConfigFile(api, mutate) {
|
|
371
|
+
const runtimeConfig = hostConfigApi(api);
|
|
372
|
+
if (typeof runtimeConfig?.mutateConfigFile === "function") {
|
|
373
|
+
// The host rewrites the file on every mutation call, even a no-op one.
|
|
374
|
+
// Probe a snapshot first so repeated `mr doctor` runs stay write-free.
|
|
375
|
+
if (typeof runtimeConfig.current === "function") {
|
|
376
|
+
const probe = structuredClone(runtimeConfig.current());
|
|
377
|
+
if (!mutate(probe))
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
// Let the host acquire its lock and supply the latest authored config.
|
|
381
|
+
// Never auto-restart a gateway as a side effect of a plugin CLI command.
|
|
382
|
+
const result = await runtimeConfig.mutateConfigFile({
|
|
383
|
+
base: "source",
|
|
384
|
+
afterWrite: { mode: "none", reason: "MemoryRouter explicit CLI configuration; restart manually to apply" },
|
|
385
|
+
mutate,
|
|
386
|
+
});
|
|
387
|
+
return result.result === true;
|
|
388
|
+
}
|
|
389
|
+
const config = await readOpenClawConfigFile(api);
|
|
390
|
+
if (!mutate(config))
|
|
391
|
+
return false;
|
|
392
|
+
await writeOpenClawConfigFile(api, config);
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
319
395
|
// ──────────────────────────────────────────────────────
|
|
320
396
|
// Full-replace: disable OpenClaw's native memory search so MemoryRouter
|
|
321
397
|
// owns the memory_search tool (no name conflict, no native embeddings 429).
|
|
322
398
|
// memoryFlush (durable MEMORY.md writes) is intentionally left untouched.
|
|
323
|
-
// Global
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
399
|
+
// Global defaults cover subagents unless the user explicitly overrides them.
|
|
400
|
+
async function setNativeMemorySearchEnabled(api, enabled) {
|
|
401
|
+
return mutateOpenClawConfigFile(api, (config) => {
|
|
402
|
+
let search;
|
|
403
|
+
if (usesCanonicalMemorySearch(api)) {
|
|
404
|
+
config.memory ??= {};
|
|
405
|
+
config.memory.search ??= {};
|
|
406
|
+
search = config.memory.search;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
config.agents ??= {};
|
|
410
|
+
config.agents.defaults ??= {};
|
|
411
|
+
config.agents.defaults.memorySearch ??= {};
|
|
412
|
+
search = config.agents.defaults.memorySearch;
|
|
413
|
+
}
|
|
414
|
+
if (search.enabled === enabled)
|
|
415
|
+
return false;
|
|
416
|
+
search.enabled = enabled;
|
|
417
|
+
return true;
|
|
418
|
+
});
|
|
335
419
|
}
|
|
336
|
-
//
|
|
337
|
-
// flip it off so already-installed users are fixed on restart after updating
|
|
338
|
-
// (no need to re-run `openclaw mr <key>`).
|
|
420
|
+
// Explicit setup/doctor repair only. With no key, leave native search available.
|
|
339
421
|
async function selfHealNativeMemorySearch(api) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (ms?.enabled === false)
|
|
344
|
-
return; // already disabled — nothing to do
|
|
345
|
-
const changed = await setNativeMemorySearchEnabled(false);
|
|
346
|
-
if (changed) {
|
|
347
|
-
api.logger.info?.("memoryrouter: disabled native memory search (full-replace). Restart gateway to apply.");
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
catch (err) {
|
|
351
|
-
api.logger.info?.(`memoryrouter: self-heal skipped — ${err instanceof Error ? err.message : String(err)}`);
|
|
352
|
-
}
|
|
422
|
+
const changed = await setNativeMemorySearchEnabled(api, false);
|
|
423
|
+
if (changed)
|
|
424
|
+
console.log(" • Native memory search disabled — MemoryRouter now owns memory_search");
|
|
353
425
|
}
|
|
354
|
-
async function updateOpenClawPluginEntry(api, patch) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
426
|
+
async function updateOpenClawPluginEntry(api, patch, mergeConfig = false) {
|
|
427
|
+
await mutateOpenClawConfigFile(api, (config) => {
|
|
428
|
+
config.plugins ??= {};
|
|
429
|
+
config.plugins.entries ??= {};
|
|
430
|
+
config.plugins.entries[api.id] ??= { enabled: true };
|
|
431
|
+
const entry = config.plugins.entries[api.id];
|
|
432
|
+
if (patch.config)
|
|
433
|
+
entry.config = mergeConfig ? { ...(entry.config || {}), ...patch.config } : patch.config;
|
|
434
|
+
if (typeof patch.enabled === "boolean")
|
|
435
|
+
entry.enabled = patch.enabled;
|
|
436
|
+
return true;
|
|
437
|
+
});
|
|
364
438
|
}
|
|
365
439
|
async function setPluginConfig(api, config) {
|
|
366
440
|
const compat = api;
|
|
@@ -379,45 +453,65 @@ async function setPluginEnabled(api, enabled) {
|
|
|
379
453
|
await updateOpenClawPluginEntry(api, { enabled });
|
|
380
454
|
}
|
|
381
455
|
async function mergePluginConfig(api, patch) {
|
|
382
|
-
|
|
456
|
+
if (typeof api.updatePluginConfig !== "function") {
|
|
457
|
+
// Merge the focused patch inside the host's transaction, not into a stale
|
|
458
|
+
// runtime snapshot which could overwrite another command's latest changes.
|
|
459
|
+
await updateOpenClawPluginEntry(api, { config: patch }, true);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const current = await readOpenClawConfigFile(api);
|
|
383
463
|
const existing = current.plugins?.entries?.[api.id]?.config;
|
|
384
464
|
await setPluginConfig(api, {
|
|
385
465
|
...(existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {}),
|
|
386
466
|
...patch,
|
|
387
467
|
});
|
|
388
468
|
}
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
//
|
|
469
|
+
// OpenClaw 2026.10+ drops every conversation hook (before_prompt_build,
|
|
470
|
+
// llm_input, agent_end) from a non-bundled plugin unless the user grants
|
|
471
|
+
// plugins.entries.<id>.hooks.allowConversationAccess. Without it mr-memory
|
|
472
|
+
// loads but never injects or captures a single memory. The grant is only
|
|
473
|
+
// written by the explicit CLI actions, on hosts whose schema accepts it
|
|
474
|
+
// (the same hosts that expose current()/mutateConfigFile). Older hosts and
|
|
475
|
+
// John's Harness reject unknown keys under hooks, so they are left alone.
|
|
476
|
+
function hostGatesConversationHooks(api) {
|
|
477
|
+
return usesCanonicalMemorySearch(api);
|
|
478
|
+
}
|
|
479
|
+
function conversationAccessGranted(config, pluginId) {
|
|
480
|
+
return config?.plugins?.entries?.[pluginId]?.hooks?.allowConversationAccess === true;
|
|
481
|
+
}
|
|
482
|
+
async function ensureConversationHookAccess(api) {
|
|
483
|
+
if (!hostGatesConversationHooks(api))
|
|
484
|
+
return false;
|
|
485
|
+
return mutateOpenClawConfigFile(api, (config) => {
|
|
486
|
+
config.plugins ??= {};
|
|
487
|
+
config.plugins.entries ??= {};
|
|
488
|
+
config.plugins.entries[api.id] ??= { enabled: true };
|
|
489
|
+
const entry = config.plugins.entries[api.id];
|
|
490
|
+
if (entry.hooks?.allowConversationAccess === true)
|
|
491
|
+
return false;
|
|
492
|
+
entry.hooks = { ...(entry.hooks && typeof entry.hooks === "object" ? entry.hooks : {}), allowConversationAccess: true };
|
|
493
|
+
return true;
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
// Explicit setup/doctor repair; preserve all config, use native plugin APIs first.
|
|
497
|
+
// Never call this from register(), a hook, or service.start().
|
|
392
498
|
async function ensureDoctorSafePluginEntry(api) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const entries = plugins.entries ?? {};
|
|
397
|
-
const existingEntry = entries[api.id] && typeof entries[api.id] === "object" && !Array.isArray(entries[api.id])
|
|
398
|
-
? entries[api.id]
|
|
399
|
-
: {};
|
|
400
|
-
const nextPlugins = {
|
|
401
|
-
...plugins,
|
|
402
|
-
entries: {
|
|
403
|
-
...entries,
|
|
404
|
-
[api.id]: {
|
|
405
|
-
...existingEntry,
|
|
406
|
-
enabled: true,
|
|
407
|
-
},
|
|
408
|
-
},
|
|
409
|
-
};
|
|
410
|
-
if (Array.isArray(plugins.allow) && !plugins.allow.includes(api.id)) {
|
|
411
|
-
nextPlugins.allow = [...plugins.allow, api.id];
|
|
412
|
-
}
|
|
413
|
-
const next = { ...config, plugins: nextPlugins };
|
|
414
|
-
if (JSON.stringify(next.plugins) === JSON.stringify(config.plugins))
|
|
415
|
-
return;
|
|
416
|
-
await writeOpenClawConfigFile(next);
|
|
417
|
-
}
|
|
418
|
-
catch (err) {
|
|
419
|
-
api.logger.info?.(`memoryrouter: doctor-safe registration skipped — ${err instanceof Error ? err.message : String(err)}`);
|
|
499
|
+
const config = await readOpenClawConfigFile(api);
|
|
500
|
+
if (config.plugins?.entries?.[api.id]?.enabled !== true) {
|
|
501
|
+
await setPluginEnabled(api, true);
|
|
420
502
|
}
|
|
503
|
+
// Plugin-enable APIs do not necessarily maintain plugins.allow. Re-read after
|
|
504
|
+
// enabling so a host API's config update cannot be overwritten by a stale copy.
|
|
505
|
+
await mutateOpenClawConfigFile(api, (current) => {
|
|
506
|
+
const allow = current.plugins?.allow;
|
|
507
|
+
if (!Array.isArray(allow) || allow.includes(api.id))
|
|
508
|
+
return false;
|
|
509
|
+
current.plugins.allow = [...allow, api.id];
|
|
510
|
+
return true;
|
|
511
|
+
});
|
|
512
|
+
const granted = await ensureConversationHookAccess(api);
|
|
513
|
+
if (granted)
|
|
514
|
+
console.log(" • Conversation hook access granted — MemoryRouter can inject and capture memory");
|
|
421
515
|
}
|
|
422
516
|
/**
|
|
423
517
|
* Read all workspace files and return as a single text blob for token counting.
|
|
@@ -474,7 +568,7 @@ const memoryRouterPlugin = {
|
|
|
474
568
|
const cfg = api.pluginConfig;
|
|
475
569
|
const endpoint = cfg?.endpoint?.replace(/\/v1\/?$/, "") || DEFAULT_ENDPOINT;
|
|
476
570
|
const memoryKey = cfg?.key;
|
|
477
|
-
const density = cfg?.density
|
|
571
|
+
const density = normalizeMemoryDensity(cfg?.density);
|
|
478
572
|
const mode = cfg?.mode || "relay";
|
|
479
573
|
const logging = cfg?.logging ?? false;
|
|
480
574
|
const embeddings = cfg?.embeddings; // undefined = bge (default), "qwen" = qwen3-8b
|
|
@@ -589,11 +683,24 @@ const memoryRouterPlugin = {
|
|
|
589
683
|
}, KEEP_WARM_PING_INTERVAL_MS);
|
|
590
684
|
warmupTimer.unref?.();
|
|
591
685
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
686
|
+
// Host install/update/doctor may load plugins inside a guarded config
|
|
687
|
+
// transaction. Even an unawaited write here races its base-hash check.
|
|
688
|
+
// Configuration repair is owned exclusively by the explicit CLI actions.
|
|
689
|
+
// Hook gating is a runtime capability check; metadata-only registration may
|
|
690
|
+
// throw on api.runtime access, so read the plugin entry from api.config only.
|
|
691
|
+
const hookGrantMissing = hasAnyKey && !conversationAccessGranted(api.config, api.id)
|
|
692
|
+
&& (() => { try {
|
|
693
|
+
return hostGatesConversationHooks(api);
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
return false;
|
|
697
|
+
} })();
|
|
698
|
+
if (hasAnyKey && (!nativeMemorySearchDisabled(api)
|
|
699
|
+
|| api.config?.plugins?.entries?.[api.id]?.enabled !== true
|
|
700
|
+
|| (Array.isArray(api.config?.plugins?.allow) && !api.config.plugins.allow.includes(api.id))
|
|
701
|
+
|| hookGrantMissing)) {
|
|
702
|
+
api.logger.info?.("memoryrouter: config repair available — run: openclaw mr doctor");
|
|
703
|
+
}
|
|
597
704
|
if (hasAnyKey) {
|
|
598
705
|
if (memoryKey)
|
|
599
706
|
api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
@@ -688,6 +795,8 @@ If setting up MemoryRouter for another agent:
|
|
|
688
795
|
// On stock OpenClaw, the return value is ignored (fire-and-forget).
|
|
689
796
|
// When PR #24122 merges, OpenClaw will use the returned prependContext.
|
|
690
797
|
// This gives forward compatibility — no plugin update needed.
|
|
798
|
+
// Current stock SDK typings still mark llm_input as observation-only;
|
|
799
|
+
// runtime builds that support returned context consume this value.
|
|
691
800
|
api.on("llm_input", async (event, ctx) => {
|
|
692
801
|
// Auto-consolidation runs are single-purpose tool sessions: no memory
|
|
693
802
|
// injection (their prompt must stay exactly the consolidation
|
|
@@ -1087,7 +1196,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1087
1196
|
}
|
|
1088
1197
|
},
|
|
1089
1198
|
};
|
|
1090
|
-
});
|
|
1199
|
+
}, { name: "memory_search" });
|
|
1091
1200
|
// memory_date_search: calls MR /v1/memory/date-search (time-window retrieval)
|
|
1092
1201
|
api.registerTool((ctx) => {
|
|
1093
1202
|
const toolKey = resolveKey(ctx);
|
|
@@ -1185,7 +1294,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1185
1294
|
}
|
|
1186
1295
|
},
|
|
1187
1296
|
};
|
|
1188
|
-
});
|
|
1297
|
+
}, { name: "memory_date_search" });
|
|
1189
1298
|
// ==================================================================
|
|
1190
1299
|
// Tools: memory_reflect + memory_commit (consolidation, two-tool shape)
|
|
1191
1300
|
// Mirrors the MCP consolidate_memories / commit_reflections pair.
|
|
@@ -1258,7 +1367,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1258
1367
|
}
|
|
1259
1368
|
},
|
|
1260
1369
|
};
|
|
1261
|
-
});
|
|
1370
|
+
}, { name: "memory_reflect" });
|
|
1262
1371
|
// memory_commit: commit wrapper (idempotent; 409 means the lease expired)
|
|
1263
1372
|
api.registerTool((ctx) => {
|
|
1264
1373
|
const toolKey = resolveKey(ctx);
|
|
@@ -1345,7 +1454,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1345
1454
|
}
|
|
1346
1455
|
},
|
|
1347
1456
|
};
|
|
1348
|
-
});
|
|
1457
|
+
}, { name: "memory_commit" });
|
|
1349
1458
|
// memory_inspect: read-only lineage drill-down (curation surface, ids allowed)
|
|
1350
1459
|
api.registerTool((ctx) => {
|
|
1351
1460
|
const toolKey = resolveKey(ctx);
|
|
@@ -1382,7 +1491,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1382
1491
|
}
|
|
1383
1492
|
},
|
|
1384
1493
|
};
|
|
1385
|
-
});
|
|
1494
|
+
}, { name: "memory_inspect" });
|
|
1386
1495
|
// memory_get — exact copy of OpenClaw's implementation (pure local file read)
|
|
1387
1496
|
api.registerTool((ctx) => {
|
|
1388
1497
|
const workspaceDir = ctx.workspaceDir || "";
|
|
@@ -1421,7 +1530,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1421
1530
|
}
|
|
1422
1531
|
},
|
|
1423
1532
|
};
|
|
1424
|
-
});
|
|
1533
|
+
}, { name: "memory_get" });
|
|
1425
1534
|
} // end if (hasAnyKey)
|
|
1426
1535
|
// ==================================================================
|
|
1427
1536
|
// CLI Commands
|
|
@@ -1434,22 +1543,17 @@ If setting up MemoryRouter for another agent:
|
|
|
1434
1543
|
}
|
|
1435
1544
|
try {
|
|
1436
1545
|
await mergePluginConfig(api, { key });
|
|
1437
|
-
await
|
|
1438
|
-
|
|
1439
|
-
const changed = await setNativeMemorySearchEnabled(false);
|
|
1440
|
-
if (changed)
|
|
1441
|
-
console.log(` • Native memory search disabled — MemoryRouter now owns memory_search`);
|
|
1442
|
-
}
|
|
1443
|
-
catch (err) {
|
|
1444
|
-
console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
1445
|
-
}
|
|
1546
|
+
await ensureDoctorSafePluginEntry(api);
|
|
1547
|
+
await selfHealNativeMemorySearch(api);
|
|
1446
1548
|
console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
|
|
1447
1549
|
console.log(` • memory_search + memory_date_search + memory_get tools registered via plugin`);
|
|
1550
|
+
console.log(` • Restart the gateway to apply: openclaw gateway restart`);
|
|
1448
1551
|
console.log(`\nRun: openclaw mr upload to upload your memories`);
|
|
1449
1552
|
}
|
|
1450
1553
|
catch (err) {
|
|
1451
1554
|
const message = err instanceof Error ? err.message : String(err);
|
|
1452
1555
|
console.error(`Failed to enable MemoryRouter: ${message}`);
|
|
1556
|
+
process.exitCode = 1;
|
|
1453
1557
|
}
|
|
1454
1558
|
};
|
|
1455
1559
|
const mr = program
|
|
@@ -1467,6 +1571,25 @@ If setting up MemoryRouter for another agent:
|
|
|
1467
1571
|
.description("Enable MemoryRouter with a memory key (alias)")
|
|
1468
1572
|
.argument("<key>", "Your MemoryRouter memory key (mk_xxx)")
|
|
1469
1573
|
.action(async (key) => { await applyKey(key); });
|
|
1574
|
+
mr.command("doctor")
|
|
1575
|
+
.description("Repair MemoryRouter plugin registration and native memory-search settings")
|
|
1576
|
+
.action(async () => {
|
|
1577
|
+
try {
|
|
1578
|
+
await ensureDoctorSafePluginEntry(api);
|
|
1579
|
+
const current = await readOpenClawConfigFile(api);
|
|
1580
|
+
const saved = current.plugins?.entries?.[api.id]?.config;
|
|
1581
|
+
const hasSavedKey = !!(saved?.key || Object.values(saved?.agentKeys ?? {}).some(Boolean));
|
|
1582
|
+
if (hasSavedKey)
|
|
1583
|
+
await selfHealNativeMemorySearch(api);
|
|
1584
|
+
console.log("✓ MemoryRouter config repaired. Restart gateway to apply changed settings.");
|
|
1585
|
+
if (!hasSavedKey)
|
|
1586
|
+
console.log(" • No key configured — run: openclaw mr <key>");
|
|
1587
|
+
}
|
|
1588
|
+
catch (err) {
|
|
1589
|
+
console.error(`MemoryRouter doctor failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1590
|
+
process.exitCode = 1;
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1470
1593
|
mr.command("off")
|
|
1471
1594
|
.description("Disable MemoryRouter (removes key, keeps CLI available)")
|
|
1472
1595
|
.action(async () => {
|
|
@@ -1477,7 +1600,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1477
1600
|
// Note: we intentionally do NOT call setPluginEnabled(api, false)
|
|
1478
1601
|
// Restore native memory search so the user isn't left with NO memory search.
|
|
1479
1602
|
try {
|
|
1480
|
-
const changed = await setNativeMemorySearchEnabled(true);
|
|
1603
|
+
const changed = await setNativeMemorySearchEnabled(api, true);
|
|
1481
1604
|
if (changed)
|
|
1482
1605
|
console.log(" • Native memory search restored");
|
|
1483
1606
|
}
|
|
@@ -1499,11 +1622,12 @@ If setting up MemoryRouter for another agent:
|
|
|
1499
1622
|
console.log("Then restart gateway:");
|
|
1500
1623
|
console.log(" openclaw gateway restart");
|
|
1501
1624
|
});
|
|
1502
|
-
//
|
|
1625
|
+
// Current density commands. Legacy xhigh configs still load as high,
|
|
1626
|
+
// but xhigh is intentionally not advertised as a command.
|
|
1503
1627
|
for (const [name, desc] of [
|
|
1504
|
-
["
|
|
1505
|
-
["
|
|
1506
|
-
["
|
|
1628
|
+
["low", "Set density to low: up to 21 memories (12 raw + 6 tier 2 + 3 tier 3)"],
|
|
1629
|
+
["medium", "Set density to medium: up to 43 memories (25 raw + 12 tier 2 + 6 tier 3) [default, balanced]"],
|
|
1630
|
+
["high", "Set density to high: up to 87 memories (50 raw + 25 tier 2 + 12 tier 3)"],
|
|
1507
1631
|
]) {
|
|
1508
1632
|
mr.command(name)
|
|
1509
1633
|
.description(desc)
|
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 { commitReflections, consolidateCheckout, dateSearch, deleteMemories, deleteMemoryIds, getMemoryStats, ingestMemory, inspectMemory, prepareMemory, searchMemory, warmupMemory } from "./memoryrouter-api.js";
|
|
12
|
+
import { commitReflections, consolidateCheckout, dateSearch, deleteMemories, deleteMemoryIds, getMemoryStats, ingestMemory, inspectMemory, prepareMemory, searchMemory, warmupMemory, type MemoryDensity } from "./memoryrouter-api.js";
|
|
13
13
|
import { isAutoConsolidationSession, maybeTriggerAutoConsolidate } from "./auto-consolidate.js";
|
|
14
14
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
15
15
|
import path from "node:path";
|
|
@@ -316,7 +316,8 @@ const WORKSPACE_FILES = [
|
|
|
316
316
|
type MemoryRouterConfig = {
|
|
317
317
|
key: string;
|
|
318
318
|
endpoint?: string;
|
|
319
|
-
|
|
319
|
+
/** xhigh is accepted only for backward compatibility and normalizes to high. */
|
|
320
|
+
density?: MemoryDensity | "xhigh";
|
|
320
321
|
mode?: "relay" | "proxy";
|
|
321
322
|
logging?: boolean;
|
|
322
323
|
embeddings?: string;
|
|
@@ -334,6 +335,23 @@ type MemoryRouterConfig = {
|
|
|
334
335
|
// Helpers
|
|
335
336
|
// ──────────────────────────────────────────────────────
|
|
336
337
|
|
|
338
|
+
/** Current densities are low, medium, and high. Legacy xhigh remains valid in
|
|
339
|
+
* existing configs but is deliberately hidden from current choices and sent
|
|
340
|
+
* to MemoryRouter as high. Missing or invalid values use the balanced medium
|
|
341
|
+
* default. */
|
|
342
|
+
function normalizeMemoryDensity(value?: MemoryRouterConfig["density"]): MemoryDensity {
|
|
343
|
+
switch (value) {
|
|
344
|
+
case "low":
|
|
345
|
+
case "medium":
|
|
346
|
+
case "high":
|
|
347
|
+
return value;
|
|
348
|
+
case "xhigh":
|
|
349
|
+
return "high";
|
|
350
|
+
default:
|
|
351
|
+
return "medium";
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
337
355
|
type CompatApi = OpenClawPluginApi & {
|
|
338
356
|
updatePluginConfig?: (config: Record<string, unknown>) => Promise<void>;
|
|
339
357
|
updatePluginEnabled?: (enabled: boolean) => Promise<void>;
|
|
@@ -345,7 +363,47 @@ function openClawConfigPath(): string {
|
|
|
345
363
|
return process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
346
364
|
}
|
|
347
365
|
|
|
348
|
-
|
|
366
|
+
type HostConfigApi = {
|
|
367
|
+
loadConfig?: () => JsonObject;
|
|
368
|
+
writeConfigFile?: (config: JsonObject) => Promise<unknown>;
|
|
369
|
+
current?: () => JsonObject;
|
|
370
|
+
mutateConfigFile?: (params: {
|
|
371
|
+
base: "source";
|
|
372
|
+
afterWrite: { mode: "none"; reason: string };
|
|
373
|
+
mutate: (draft: JsonObject) => boolean;
|
|
374
|
+
}) => Promise<{ result?: boolean }>;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
function hostConfigApi(api: OpenClawPluginApi): HostConfigApi | undefined {
|
|
378
|
+
return api.runtime?.config as unknown as HostConfigApi | undefined;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// OpenClaw's current()/mutateConfigFile API accompanies the canonical memory
|
|
382
|
+
// owner schema (memory.search). Older OpenClaw and John's Harness retain
|
|
383
|
+
// agents.defaults.memorySearch. Do not use the host version: forks version
|
|
384
|
+
// independently of their upstream config schema.
|
|
385
|
+
function usesCanonicalMemorySearch(api: OpenClawPluginApi): boolean {
|
|
386
|
+
return typeof hostConfigApi(api)?.current === "function";
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function nativeMemorySearchDisabled(api: OpenClawPluginApi): boolean {
|
|
390
|
+
const config = api.config as JsonObject;
|
|
391
|
+
// Metadata-only registration may deliberately throw on api.runtime access.
|
|
392
|
+
// Read the provided config only; runtime capability detection belongs in CLI actions.
|
|
393
|
+
return config?.memory?.search?.enabled === false
|
|
394
|
+
|| config?.agents?.defaults?.memorySearch?.enabled === false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Only explicit CLI actions call these helpers. Loading/registering the plugin
|
|
398
|
+
// must never mutate the host config (including fire-and-forget writes).
|
|
399
|
+
async function readOpenClawConfigFile(api: OpenClawPluginApi): Promise<JsonObject> {
|
|
400
|
+
const runtimeConfig = hostConfigApi(api);
|
|
401
|
+
if (typeof runtimeConfig?.current === "function") {
|
|
402
|
+
return structuredClone(runtimeConfig.current());
|
|
403
|
+
}
|
|
404
|
+
if (typeof runtimeConfig?.loadConfig === "function") {
|
|
405
|
+
return structuredClone(runtimeConfig.loadConfig());
|
|
406
|
+
}
|
|
349
407
|
try {
|
|
350
408
|
return JSON.parse(await readFile(openClawConfigPath(), "utf8"));
|
|
351
409
|
} catch (err: any) {
|
|
@@ -354,7 +412,13 @@ async function readOpenClawConfigFile(): Promise<JsonObject> {
|
|
|
354
412
|
}
|
|
355
413
|
}
|
|
356
414
|
|
|
357
|
-
async function writeOpenClawConfigFile(config: JsonObject): Promise<void> {
|
|
415
|
+
async function writeOpenClawConfigFile(api: OpenClawPluginApi, config: JsonObject): Promise<void> {
|
|
416
|
+
const runtimeConfig = hostConfigApi(api);
|
|
417
|
+
if (typeof runtimeConfig?.writeConfigFile === "function") {
|
|
418
|
+
await runtimeConfig.writeConfigFile(config);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// Legacy hosts only: no native config writer is exposed.
|
|
358
422
|
const configPath = openClawConfigPath();
|
|
359
423
|
await mkdir(dirname(configPath), { recursive: true });
|
|
360
424
|
try {
|
|
@@ -366,54 +430,70 @@ async function writeOpenClawConfigFile(config: JsonObject): Promise<void> {
|
|
|
366
430
|
`, "utf8");
|
|
367
431
|
}
|
|
368
432
|
|
|
433
|
+
async function mutateOpenClawConfigFile(api: OpenClawPluginApi, mutate: (draft: JsonObject) => boolean): Promise<boolean> {
|
|
434
|
+
const runtimeConfig = hostConfigApi(api);
|
|
435
|
+
if (typeof runtimeConfig?.mutateConfigFile === "function") {
|
|
436
|
+
// The host rewrites the file on every mutation call, even a no-op one.
|
|
437
|
+
// Probe a snapshot first so repeated `mr doctor` runs stay write-free.
|
|
438
|
+
if (typeof runtimeConfig.current === "function") {
|
|
439
|
+
const probe = structuredClone(runtimeConfig.current());
|
|
440
|
+
if (!mutate(probe)) return false;
|
|
441
|
+
}
|
|
442
|
+
// Let the host acquire its lock and supply the latest authored config.
|
|
443
|
+
// Never auto-restart a gateway as a side effect of a plugin CLI command.
|
|
444
|
+
const result = await runtimeConfig.mutateConfigFile({
|
|
445
|
+
base: "source",
|
|
446
|
+
afterWrite: { mode: "none", reason: "MemoryRouter explicit CLI configuration; restart manually to apply" },
|
|
447
|
+
mutate,
|
|
448
|
+
});
|
|
449
|
+
return result.result === true;
|
|
450
|
+
}
|
|
451
|
+
const config = await readOpenClawConfigFile(api);
|
|
452
|
+
if (!mutate(config)) return false;
|
|
453
|
+
await writeOpenClawConfigFile(api, config);
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
|
|
369
457
|
// ──────────────────────────────────────────────────────
|
|
370
458
|
// Full-replace: disable OpenClaw's native memory search so MemoryRouter
|
|
371
459
|
// owns the memory_search tool (no name conflict, no native embeddings 429).
|
|
372
460
|
// memoryFlush (durable MEMORY.md writes) is intentionally left untouched.
|
|
373
|
-
// Global
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
461
|
+
// Global defaults cover subagents unless the user explicitly overrides them.
|
|
462
|
+
async function setNativeMemorySearchEnabled(api: OpenClawPluginApi, enabled: boolean): Promise<boolean> {
|
|
463
|
+
return mutateOpenClawConfigFile(api, (config) => {
|
|
464
|
+
let search: JsonObject;
|
|
465
|
+
if (usesCanonicalMemorySearch(api)) {
|
|
466
|
+
config.memory ??= {};
|
|
467
|
+
config.memory.search ??= {};
|
|
468
|
+
search = config.memory.search;
|
|
469
|
+
} else {
|
|
470
|
+
config.agents ??= {};
|
|
471
|
+
config.agents.defaults ??= {};
|
|
472
|
+
config.agents.defaults.memorySearch ??= {};
|
|
473
|
+
search = config.agents.defaults.memorySearch;
|
|
474
|
+
}
|
|
475
|
+
if (search.enabled === enabled) return false;
|
|
476
|
+
search.enabled = enabled;
|
|
477
|
+
return true;
|
|
478
|
+
});
|
|
384
479
|
}
|
|
385
480
|
|
|
386
|
-
//
|
|
387
|
-
// flip it off so already-installed users are fixed on restart after updating
|
|
388
|
-
// (no need to re-run `openclaw mr <key>`).
|
|
481
|
+
// Explicit setup/doctor repair only. With no key, leave native search available.
|
|
389
482
|
async function selfHealNativeMemorySearch(api: OpenClawPluginApi): Promise<void> {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const ms = config?.agents?.defaults?.memorySearch;
|
|
393
|
-
if (ms?.enabled === false) return; // already disabled — nothing to do
|
|
394
|
-
const changed = await setNativeMemorySearchEnabled(false);
|
|
395
|
-
if (changed) {
|
|
396
|
-
api.logger.info?.(
|
|
397
|
-
"memoryrouter: disabled native memory search (full-replace). Restart gateway to apply.",
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
} catch (err) {
|
|
401
|
-
api.logger.info?.(
|
|
402
|
-
`memoryrouter: self-heal skipped — ${err instanceof Error ? err.message : String(err)}`,
|
|
403
|
-
);
|
|
404
|
-
}
|
|
483
|
+
const changed = await setNativeMemorySearchEnabled(api, false);
|
|
484
|
+
if (changed) console.log(" • Native memory search disabled — MemoryRouter now owns memory_search");
|
|
405
485
|
}
|
|
406
486
|
|
|
407
|
-
async function updateOpenClawPluginEntry(api: OpenClawPluginApi, patch: JsonObject): Promise<void> {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
...config.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
487
|
+
async function updateOpenClawPluginEntry(api: OpenClawPluginApi, patch: JsonObject, mergeConfig = false): Promise<void> {
|
|
488
|
+
await mutateOpenClawConfigFile(api, (config) => {
|
|
489
|
+
config.plugins ??= {};
|
|
490
|
+
config.plugins.entries ??= {};
|
|
491
|
+
config.plugins.entries[api.id] ??= { enabled: true };
|
|
492
|
+
const entry = config.plugins.entries[api.id];
|
|
493
|
+
if (patch.config) entry.config = mergeConfig ? { ...(entry.config || {}), ...patch.config } : patch.config;
|
|
494
|
+
if (typeof patch.enabled === "boolean") entry.enabled = patch.enabled;
|
|
495
|
+
return true;
|
|
496
|
+
});
|
|
417
497
|
}
|
|
418
498
|
|
|
419
499
|
async function setPluginConfig(api: OpenClawPluginApi, config: Record<string, unknown>): Promise<void> {
|
|
@@ -435,7 +515,13 @@ async function setPluginEnabled(api: OpenClawPluginApi, enabled: boolean): Promi
|
|
|
435
515
|
}
|
|
436
516
|
|
|
437
517
|
async function mergePluginConfig(api: OpenClawPluginApi, patch: Record<string, unknown>): Promise<void> {
|
|
438
|
-
|
|
518
|
+
if (typeof (api as CompatApi).updatePluginConfig !== "function") {
|
|
519
|
+
// Merge the focused patch inside the host's transaction, not into a stale
|
|
520
|
+
// runtime snapshot which could overwrite another command's latest changes.
|
|
521
|
+
await updateOpenClawPluginEntry(api, { config: patch }, true);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const current = await readOpenClawConfigFile(api);
|
|
439
525
|
const existing = current.plugins?.entries?.[api.id]?.config;
|
|
440
526
|
await setPluginConfig(api, {
|
|
441
527
|
...(existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {}),
|
|
@@ -443,36 +529,51 @@ async function mergePluginConfig(api: OpenClawPluginApi, patch: Record<string, u
|
|
|
443
529
|
});
|
|
444
530
|
}
|
|
445
531
|
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
532
|
+
// OpenClaw 2026.10+ drops every conversation hook (before_prompt_build,
|
|
533
|
+
// llm_input, agent_end) from a non-bundled plugin unless the user grants
|
|
534
|
+
// plugins.entries.<id>.hooks.allowConversationAccess. Without it mr-memory
|
|
535
|
+
// loads but never injects or captures a single memory. The grant is only
|
|
536
|
+
// written by the explicit CLI actions, on hosts whose schema accepts it
|
|
537
|
+
// (the same hosts that expose current()/mutateConfigFile). Older hosts and
|
|
538
|
+
// John's Harness reject unknown keys under hooks, so they are left alone.
|
|
539
|
+
function hostGatesConversationHooks(api: OpenClawPluginApi): boolean {
|
|
540
|
+
return usesCanonicalMemorySearch(api);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function conversationAccessGranted(config: JsonObject, pluginId: string): boolean {
|
|
544
|
+
return config?.plugins?.entries?.[pluginId]?.hooks?.allowConversationAccess === true;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function ensureConversationHookAccess(api: OpenClawPluginApi): Promise<boolean> {
|
|
548
|
+
if (!hostGatesConversationHooks(api)) return false;
|
|
549
|
+
return mutateOpenClawConfigFile(api, (config) => {
|
|
550
|
+
config.plugins ??= {};
|
|
551
|
+
config.plugins.entries ??= {};
|
|
552
|
+
config.plugins.entries[api.id] ??= { enabled: true };
|
|
553
|
+
const entry = config.plugins.entries[api.id];
|
|
554
|
+
if (entry.hooks?.allowConversationAccess === true) return false;
|
|
555
|
+
entry.hooks = { ...(entry.hooks && typeof entry.hooks === "object" ? entry.hooks : {}), allowConversationAccess: true };
|
|
556
|
+
return true;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Explicit setup/doctor repair; preserve all config, use native plugin APIs first.
|
|
561
|
+
// Never call this from register(), a hook, or service.start().
|
|
449
562
|
async function ensureDoctorSafePluginEntry(api: OpenClawPluginApi): Promise<void> {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const entries = plugins.entries ?? {};
|
|
454
|
-
const existingEntry = entries[api.id] && typeof entries[api.id] === "object" && !Array.isArray(entries[api.id])
|
|
455
|
-
? entries[api.id]
|
|
456
|
-
: {};
|
|
457
|
-
const nextPlugins: JsonObject = {
|
|
458
|
-
...plugins,
|
|
459
|
-
entries: {
|
|
460
|
-
...entries,
|
|
461
|
-
[api.id]: {
|
|
462
|
-
...existingEntry,
|
|
463
|
-
enabled: true,
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
|
-
};
|
|
467
|
-
if (Array.isArray(plugins.allow) && !plugins.allow.includes(api.id)) {
|
|
468
|
-
nextPlugins.allow = [...plugins.allow, api.id];
|
|
469
|
-
}
|
|
470
|
-
const next = { ...config, plugins: nextPlugins };
|
|
471
|
-
if (JSON.stringify(next.plugins) === JSON.stringify(config.plugins)) return;
|
|
472
|
-
await writeOpenClawConfigFile(next);
|
|
473
|
-
} catch (err) {
|
|
474
|
-
api.logger.info?.(`memoryrouter: doctor-safe registration skipped — ${err instanceof Error ? err.message : String(err)}`);
|
|
563
|
+
const config = await readOpenClawConfigFile(api);
|
|
564
|
+
if (config.plugins?.entries?.[api.id]?.enabled !== true) {
|
|
565
|
+
await setPluginEnabled(api, true);
|
|
475
566
|
}
|
|
567
|
+
// Plugin-enable APIs do not necessarily maintain plugins.allow. Re-read after
|
|
568
|
+
// enabling so a host API's config update cannot be overwritten by a stale copy.
|
|
569
|
+
await mutateOpenClawConfigFile(api, (current) => {
|
|
570
|
+
const allow = current.plugins?.allow;
|
|
571
|
+
if (!Array.isArray(allow) || allow.includes(api.id)) return false;
|
|
572
|
+
current.plugins.allow = [...allow, api.id];
|
|
573
|
+
return true;
|
|
574
|
+
});
|
|
575
|
+
const granted = await ensureConversationHookAccess(api);
|
|
576
|
+
if (granted) console.log(" • Conversation hook access granted — MemoryRouter can inject and capture memory");
|
|
476
577
|
}
|
|
477
578
|
|
|
478
579
|
/**
|
|
@@ -525,7 +626,7 @@ const memoryRouterPlugin = {
|
|
|
525
626
|
const cfg = api.pluginConfig as MemoryRouterConfig | undefined;
|
|
526
627
|
const endpoint = cfg?.endpoint?.replace(/\/v1\/?$/, "") || DEFAULT_ENDPOINT;
|
|
527
628
|
const memoryKey = cfg?.key;
|
|
528
|
-
const density = cfg?.density
|
|
629
|
+
const density = normalizeMemoryDensity(cfg?.density);
|
|
529
630
|
const mode = cfg?.mode || "relay";
|
|
530
631
|
const logging = cfg?.logging ?? false;
|
|
531
632
|
const embeddings = cfg?.embeddings; // undefined = bge (default), "qwen" = qwen3-8b
|
|
@@ -642,10 +743,19 @@ const memoryRouterPlugin = {
|
|
|
642
743
|
warmupTimer.unref?.();
|
|
643
744
|
}
|
|
644
745
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
746
|
+
// Host install/update/doctor may load plugins inside a guarded config
|
|
747
|
+
// transaction. Even an unawaited write here races its base-hash check.
|
|
748
|
+
// Configuration repair is owned exclusively by the explicit CLI actions.
|
|
749
|
+
// Hook gating is a runtime capability check; metadata-only registration may
|
|
750
|
+
// throw on api.runtime access, so read the plugin entry from api.config only.
|
|
751
|
+
const hookGrantMissing = hasAnyKey && !conversationAccessGranted(api.config as JsonObject, api.id)
|
|
752
|
+
&& (() => { try { return hostGatesConversationHooks(api); } catch { return false; } })();
|
|
753
|
+
if (hasAnyKey && (!nativeMemorySearchDisabled(api)
|
|
754
|
+
|| api.config?.plugins?.entries?.[api.id]?.enabled !== true
|
|
755
|
+
|| (Array.isArray(api.config?.plugins?.allow) && !api.config.plugins.allow.includes(api.id))
|
|
756
|
+
|| hookGrantMissing)) {
|
|
757
|
+
api.logger.info?.("memoryrouter: config repair available — run: openclaw mr doctor");
|
|
758
|
+
}
|
|
649
759
|
|
|
650
760
|
if (hasAnyKey) {
|
|
651
761
|
if (memoryKey) api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
@@ -744,7 +854,9 @@ If setting up MemoryRouter for another agent:
|
|
|
744
854
|
// On stock OpenClaw, the return value is ignored (fire-and-forget).
|
|
745
855
|
// When PR #24122 merges, OpenClaw will use the returned prependContext.
|
|
746
856
|
// This gives forward compatibility — no plugin update needed.
|
|
747
|
-
|
|
857
|
+
// Current stock SDK typings still mark llm_input as observation-only;
|
|
858
|
+
// runtime builds that support returned context consume this value.
|
|
859
|
+
(api as any).on("llm_input", async (event: any, ctx: any) => {
|
|
748
860
|
// Auto-consolidation runs are single-purpose tool sessions: no memory
|
|
749
861
|
// injection (their prompt must stay exactly the consolidation
|
|
750
862
|
// mission) and no re-trigger loops.
|
|
@@ -1167,7 +1279,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1167
1279
|
}
|
|
1168
1280
|
},
|
|
1169
1281
|
};
|
|
1170
|
-
});
|
|
1282
|
+
}, { name: "memory_search" });
|
|
1171
1283
|
|
|
1172
1284
|
// memory_date_search: calls MR /v1/memory/date-search (time-window retrieval)
|
|
1173
1285
|
api.registerTool((ctx) => {
|
|
@@ -1270,7 +1382,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1270
1382
|
}
|
|
1271
1383
|
},
|
|
1272
1384
|
};
|
|
1273
|
-
});
|
|
1385
|
+
}, { name: "memory_date_search" });
|
|
1274
1386
|
|
|
1275
1387
|
// ==================================================================
|
|
1276
1388
|
// Tools: memory_reflect + memory_commit (consolidation, two-tool shape)
|
|
@@ -1352,7 +1464,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1352
1464
|
}
|
|
1353
1465
|
},
|
|
1354
1466
|
};
|
|
1355
|
-
});
|
|
1467
|
+
}, { name: "memory_reflect" });
|
|
1356
1468
|
|
|
1357
1469
|
// memory_commit: commit wrapper (idempotent; 409 means the lease expired)
|
|
1358
1470
|
api.registerTool((ctx) => {
|
|
@@ -1440,7 +1552,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1440
1552
|
}
|
|
1441
1553
|
},
|
|
1442
1554
|
};
|
|
1443
|
-
});
|
|
1555
|
+
}, { name: "memory_commit" });
|
|
1444
1556
|
|
|
1445
1557
|
// memory_inspect: read-only lineage drill-down (curation surface, ids allowed)
|
|
1446
1558
|
api.registerTool((ctx) => {
|
|
@@ -1475,7 +1587,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1475
1587
|
}
|
|
1476
1588
|
},
|
|
1477
1589
|
};
|
|
1478
|
-
});
|
|
1590
|
+
}, { name: "memory_inspect" });
|
|
1479
1591
|
|
|
1480
1592
|
// memory_get — exact copy of OpenClaw's implementation (pure local file read)
|
|
1481
1593
|
api.registerTool((ctx) => {
|
|
@@ -1513,7 +1625,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1513
1625
|
}
|
|
1514
1626
|
},
|
|
1515
1627
|
};
|
|
1516
|
-
});
|
|
1628
|
+
}, { name: "memory_get" });
|
|
1517
1629
|
} // end if (hasAnyKey)
|
|
1518
1630
|
|
|
1519
1631
|
// ==================================================================
|
|
@@ -1529,19 +1641,16 @@ If setting up MemoryRouter for another agent:
|
|
|
1529
1641
|
}
|
|
1530
1642
|
try {
|
|
1531
1643
|
await mergePluginConfig(api, { key });
|
|
1532
|
-
await
|
|
1533
|
-
|
|
1534
|
-
const changed = await setNativeMemorySearchEnabled(false);
|
|
1535
|
-
if (changed) console.log(` • Native memory search disabled — MemoryRouter now owns memory_search`);
|
|
1536
|
-
} catch (err) {
|
|
1537
|
-
console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
1538
|
-
}
|
|
1644
|
+
await ensureDoctorSafePluginEntry(api);
|
|
1645
|
+
await selfHealNativeMemorySearch(api);
|
|
1539
1646
|
console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
|
|
1540
1647
|
console.log(` • memory_search + memory_date_search + memory_get tools registered via plugin`);
|
|
1648
|
+
console.log(` • Restart the gateway to apply: openclaw gateway restart`);
|
|
1541
1649
|
console.log(`\nRun: openclaw mr upload to upload your memories`);
|
|
1542
1650
|
} catch (err) {
|
|
1543
1651
|
const message = err instanceof Error ? err.message : String(err);
|
|
1544
1652
|
console.error(`Failed to enable MemoryRouter: ${message}`);
|
|
1653
|
+
process.exitCode = 1;
|
|
1545
1654
|
}
|
|
1546
1655
|
};
|
|
1547
1656
|
|
|
@@ -1559,6 +1668,23 @@ If setting up MemoryRouter for another agent:
|
|
|
1559
1668
|
.argument("<key>", "Your MemoryRouter memory key (mk_xxx)")
|
|
1560
1669
|
.action(async (key: string) => { await applyKey(key); });
|
|
1561
1670
|
|
|
1671
|
+
mr.command("doctor")
|
|
1672
|
+
.description("Repair MemoryRouter plugin registration and native memory-search settings")
|
|
1673
|
+
.action(async () => {
|
|
1674
|
+
try {
|
|
1675
|
+
await ensureDoctorSafePluginEntry(api);
|
|
1676
|
+
const current = await readOpenClawConfigFile(api);
|
|
1677
|
+
const saved = current.plugins?.entries?.[api.id]?.config;
|
|
1678
|
+
const hasSavedKey = !!(saved?.key || Object.values(saved?.agentKeys ?? {}).some(Boolean));
|
|
1679
|
+
if (hasSavedKey) await selfHealNativeMemorySearch(api);
|
|
1680
|
+
console.log("✓ MemoryRouter config repaired. Restart gateway to apply changed settings.");
|
|
1681
|
+
if (!hasSavedKey) console.log(" • No key configured — run: openclaw mr <key>");
|
|
1682
|
+
} catch (err) {
|
|
1683
|
+
console.error(`MemoryRouter doctor failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1684
|
+
process.exitCode = 1;
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1562
1688
|
mr.command("off")
|
|
1563
1689
|
.description("Disable MemoryRouter (removes key, keeps CLI available)")
|
|
1564
1690
|
.action(async () => {
|
|
@@ -1569,7 +1695,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1569
1695
|
// Note: we intentionally do NOT call setPluginEnabled(api, false)
|
|
1570
1696
|
// Restore native memory search so the user isn't left with NO memory search.
|
|
1571
1697
|
try {
|
|
1572
|
-
const changed = await setNativeMemorySearchEnabled(true);
|
|
1698
|
+
const changed = await setNativeMemorySearchEnabled(api, true);
|
|
1573
1699
|
if (changed) console.log(" • Native memory search restored");
|
|
1574
1700
|
} catch (err) {
|
|
1575
1701
|
console.error(` ⚠ Could not restore native memory search: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1590,11 +1716,12 @@ If setting up MemoryRouter for another agent:
|
|
|
1590
1716
|
console.log(" openclaw gateway restart");
|
|
1591
1717
|
});
|
|
1592
1718
|
|
|
1593
|
-
//
|
|
1719
|
+
// Current density commands. Legacy xhigh configs still load as high,
|
|
1720
|
+
// but xhigh is intentionally not advertised as a command.
|
|
1594
1721
|
for (const [name, desc] of [
|
|
1595
|
-
["
|
|
1596
|
-
["
|
|
1597
|
-
["
|
|
1722
|
+
["low", "Set density to low: up to 21 memories (12 raw + 6 tier 2 + 3 tier 3)"],
|
|
1723
|
+
["medium", "Set density to medium: up to 43 memories (25 raw + 12 tier 2 + 6 tier 3) [default, balanced]"],
|
|
1724
|
+
["high", "Set density to high: up to 87 memories (50 raw + 25 tier 2 + 12 tier 3)"],
|
|
1598
1725
|
] as const) {
|
|
1599
1726
|
mr.command(name)
|
|
1600
1727
|
.description(desc)
|
package/memoryrouter-api.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
2
2
|
|
|
3
|
+
export type MemoryDensity = "low" | "medium" | "high";
|
|
4
|
+
|
|
3
5
|
type PreparePayload = {
|
|
4
6
|
messages: Array<{ role: string; content: string }>;
|
|
5
|
-
density:
|
|
7
|
+
density: MemoryDensity;
|
|
6
8
|
embeddings?: string;
|
|
7
9
|
};
|
|
8
10
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,37 +2,43 @@
|
|
|
2
2
|
"id": "mr-memory",
|
|
3
3
|
"name": "MemoryRouter Memory",
|
|
4
4
|
"description": "Persistent memory for OpenClaw, powered by MemoryRouter. Memory that survives sessions, compaction, and model changes.",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
5
|
+
"version": "3.14.0",
|
|
6
|
+
"activation": {
|
|
7
|
+
"onStartup": true,
|
|
8
|
+
"onCommands": [
|
|
9
|
+
"mr"
|
|
10
|
+
],
|
|
11
|
+
"onCapabilities": [
|
|
12
|
+
"hook",
|
|
13
|
+
"tool"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"cliCommands": [
|
|
17
|
+
{
|
|
18
|
+
"name": "mr",
|
|
19
|
+
"description": "MemoryRouter memory commands",
|
|
20
|
+
"hasSubcommands": true
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"contracts": {
|
|
24
|
+
"tools": [
|
|
25
|
+
"memory_search",
|
|
26
|
+
"memory_date_search",
|
|
27
|
+
"memory_get",
|
|
28
|
+
"memory_reflect",
|
|
29
|
+
"memory_commit",
|
|
30
|
+
"memory_inspect"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"toolMetadata": {
|
|
34
|
+
"memory_get": {
|
|
35
|
+
"replaySafe": true
|
|
24
36
|
},
|
|
25
|
-
"
|
|
26
|
-
"
|
|
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."
|
|
37
|
+
"memory_inspect": {
|
|
38
|
+
"replaySafe": true
|
|
30
39
|
},
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"placeholder": "true",
|
|
34
|
-
"advanced": true,
|
|
35
|
-
"help": "Automatically consolidate memories in the background when debt crosses the server threshold. Default true."
|
|
40
|
+
"memory_commit": {
|
|
41
|
+
"sideEffecting": true
|
|
36
42
|
}
|
|
37
43
|
},
|
|
38
44
|
"configSchema": {
|
|
@@ -48,18 +54,26 @@
|
|
|
48
54
|
},
|
|
49
55
|
"density": {
|
|
50
56
|
"type": "string",
|
|
51
|
-
"
|
|
57
|
+
"pattern": "^(low|medium|high|xhigh)$",
|
|
58
|
+
"default": "medium",
|
|
59
|
+
"description": "Current densities are low, medium, and high. Legacy xhigh configs remain valid and are treated as high."
|
|
52
60
|
},
|
|
53
61
|
"logging": {
|
|
54
62
|
"type": "boolean"
|
|
55
63
|
},
|
|
56
64
|
"mode": {
|
|
57
65
|
"type": "string",
|
|
58
|
-
"enum": [
|
|
66
|
+
"enum": [
|
|
67
|
+
"relay",
|
|
68
|
+
"proxy"
|
|
69
|
+
]
|
|
59
70
|
},
|
|
60
71
|
"embeddings": {
|
|
61
72
|
"type": "string",
|
|
62
|
-
"enum": [
|
|
73
|
+
"enum": [
|
|
74
|
+
"bge",
|
|
75
|
+
"qwen"
|
|
76
|
+
],
|
|
63
77
|
"description": "Embedding model: bge (1024d, default) or qwen (4096d)"
|
|
64
78
|
},
|
|
65
79
|
"keepWarmMs": {
|
|
@@ -81,5 +95,38 @@
|
|
|
81
95
|
}
|
|
82
96
|
},
|
|
83
97
|
"required": []
|
|
84
|
-
}
|
|
98
|
+
},
|
|
99
|
+
"uiHints": {
|
|
100
|
+
"key": {
|
|
101
|
+
"label": "Memory Key",
|
|
102
|
+
"sensitive": true,
|
|
103
|
+
"placeholder": "mk_xxx",
|
|
104
|
+
"help": "Your MemoryRouter API key (get one at memoryrouter.ai)"
|
|
105
|
+
},
|
|
106
|
+
"endpoint": {
|
|
107
|
+
"label": "API Endpoint",
|
|
108
|
+
"placeholder": "https://api.memoryrouter.ai/v1",
|
|
109
|
+
"advanced": true,
|
|
110
|
+
"help": "Override for self-hosted MemoryRouter"
|
|
111
|
+
},
|
|
112
|
+
"density": {
|
|
113
|
+
"label": "Memory Density",
|
|
114
|
+
"placeholder": "medium",
|
|
115
|
+
"advanced": true,
|
|
116
|
+
"help": "Current choices: low, up to 21 memories (12 raw + 6 tier 2 + 3 tier 3); medium, up to 43 (25 + 12 + 6), balanced default; high, up to 87 (50 + 25 + 12)."
|
|
117
|
+
},
|
|
118
|
+
"keepWarmMs": {
|
|
119
|
+
"label": "Keep Warm Window (ms)",
|
|
120
|
+
"placeholder": "180000",
|
|
121
|
+
"advanced": true,
|
|
122
|
+
"help": "How long to keep your vault warm after each message. Default 180000 (3 min). Set 0 to disable warmup pings."
|
|
123
|
+
},
|
|
124
|
+
"autoConsolidate": {
|
|
125
|
+
"label": "Automatic Consolidation",
|
|
126
|
+
"placeholder": "true",
|
|
127
|
+
"advanced": true,
|
|
128
|
+
"help": "Automatically consolidate memories in the background when debt crosses the server threshold. Default true."
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"icon": "https://memoryrouter.ai/logo.png"
|
|
85
132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mr-memory",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.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": [
|
|
@@ -49,12 +49,8 @@
|
|
|
49
49
|
"extensions": [
|
|
50
50
|
"./index.js"
|
|
51
51
|
],
|
|
52
|
-
"compat": {
|
|
53
|
-
"pluginApi": ">=2026.3.7"
|
|
54
|
-
},
|
|
55
52
|
"install": {
|
|
56
|
-
"npmSpec": "mr-memory"
|
|
57
|
-
"minHostVersion": ">=2026.3.7"
|
|
53
|
+
"npmSpec": "mr-memory"
|
|
58
54
|
}
|
|
59
55
|
},
|
|
60
56
|
"main": "./index.js",
|