openclaw-memory-decay 0.1.6 → 0.1.8
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 +14 -18
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
- package/scripts/detect-python-lib.mjs +6 -1
- package/src/index.ts +14 -7
- package/src/legacy-config.ts +35 -0
- package/src/python-env.ts +119 -0
package/README.md
CHANGED
|
@@ -63,12 +63,11 @@ Activation
|
|
|
63
63
|
python3 -m venv ~/.openclaw/venvs/memory-decay
|
|
64
64
|
~/.openclaw/venvs/memory-decay/bin/pip install memory-decay
|
|
65
65
|
|
|
66
|
-
# 2. Install this plugin
|
|
67
|
-
source ~/.openclaw/venvs/memory-decay/bin/activate
|
|
66
|
+
# 2. Install this plugin from npm
|
|
68
67
|
openclaw plugins install openclaw-memory-decay
|
|
69
68
|
|
|
70
69
|
# 3. (Optional but recommended) Restrict auto-load to trusted plugins only
|
|
71
|
-
openclaw config set plugins.allow '["memory-decay"]'
|
|
70
|
+
openclaw config set plugins.allow '["openclaw-memory-decay"]'
|
|
72
71
|
|
|
73
72
|
# 4. Restart the gateway
|
|
74
73
|
openclaw gateway restart
|
|
@@ -94,14 +93,14 @@ openclaw plugins install openclaw-memory-decay
|
|
|
94
93
|
openclaw gateway restart
|
|
95
94
|
|
|
96
95
|
# 3. Verify
|
|
97
|
-
openclaw plugins list | grep memory-decay # should show: memory-decay | loaded
|
|
96
|
+
openclaw plugins list | grep openclaw-memory-decay # should show: openclaw-memory-decay | loaded
|
|
98
97
|
curl -s http://127.0.0.1:8100/health # should show: {"status":"ok","current_tick":0}
|
|
99
98
|
```
|
|
100
99
|
|
|
101
100
|
If auto-detection does not recover your backend path after migration, set the interpreter explicitly:
|
|
102
101
|
|
|
103
102
|
```bash
|
|
104
|
-
openclaw config set plugins.entries.memory-decay.config.pythonPath "~/.openclaw/venvs/memory-decay/bin/python"
|
|
103
|
+
openclaw config set plugins.entries.openclaw-memory-decay.config.pythonPath "~/.openclaw/venvs/memory-decay/bin/python"
|
|
105
104
|
openclaw gateway restart
|
|
106
105
|
```
|
|
107
106
|
|
|
@@ -109,19 +108,19 @@ openclaw gateway restart
|
|
|
109
108
|
|
|
110
109
|
To update in the future:
|
|
111
110
|
```bash
|
|
112
|
-
openclaw plugins update memory-decay
|
|
111
|
+
openclaw plugins update openclaw-memory-decay
|
|
113
112
|
openclaw gateway restart
|
|
114
113
|
```
|
|
115
114
|
|
|
116
115
|
## Configuration
|
|
117
116
|
|
|
118
|
-
Add to `~/.openclaw/openclaw.json` under `plugins.entries.memory-decay.config`:
|
|
117
|
+
Add to `~/.openclaw/openclaw.json` under `plugins.entries.openclaw-memory-decay.config`:
|
|
119
118
|
|
|
120
119
|
```json
|
|
121
120
|
{
|
|
122
121
|
"plugins": {
|
|
123
122
|
"entries": {
|
|
124
|
-
"memory-decay": {
|
|
123
|
+
"openclaw-memory-decay": {
|
|
125
124
|
"enabled": true,
|
|
126
125
|
"config": {
|
|
127
126
|
"dbPath": "~/.openclaw/memory-decay-data/memories.db",
|
|
@@ -138,7 +137,7 @@ Add to `~/.openclaw/openclaw.json` under `plugins.entries.memory-decay.config`:
|
|
|
138
137
|
|--------|---------|-------------|
|
|
139
138
|
| `serverPort` | `8100` | Port for the memory-decay HTTP server |
|
|
140
139
|
| `memoryDecayPath` | (auto) | Path to memory-decay-core. Auto-detected from the install-time Python environment if not set |
|
|
141
|
-
| `pythonPath` | `python3` | Path to Python interpreter. Set this explicitly if
|
|
140
|
+
| `pythonPath` | `python3` | Path to Python interpreter. Set this explicitly if you use a custom venv path instead of `~/.openclaw/venvs/memory-decay` |
|
|
142
141
|
| `dbPath` | `~/.openclaw/memory-decay-data/memories.db` | SQLite database location |
|
|
143
142
|
| `autoSave` | `true` | Auto-save every conversation turn at low importance. Set `false` to let the agent decide what to save |
|
|
144
143
|
| `embeddingProvider` | `local` | Embedding provider: `local`, `openai`, or `gemini` |
|
|
@@ -244,7 +243,7 @@ The plugin registers these tools:
|
|
|
244
243
|
This warning appears when `plugins.allow` is not set. While the plugin still loads (since it is explicitly configured in `plugins.entries`), it is good practice to restrict auto-load to trusted plugins only:
|
|
245
244
|
|
|
246
245
|
```bash
|
|
247
|
-
openclaw config set plugins.allow '["memory-decay"]'
|
|
246
|
+
openclaw config set plugins.allow '["openclaw-memory-decay"]'
|
|
248
247
|
openclaw gateway restart
|
|
249
248
|
```
|
|
250
249
|
|
|
@@ -297,20 +296,17 @@ openclaw plugins doctor
|
|
|
297
296
|
python3 -m venv ~/.openclaw/venvs/memory-decay
|
|
298
297
|
~/.openclaw/venvs/memory-decay/bin/pip install memory-decay
|
|
299
298
|
|
|
300
|
-
# 2.
|
|
301
|
-
source ~/.openclaw/venvs/memory-decay/bin/activate
|
|
302
|
-
|
|
303
|
-
# 3. Install plugin from npm
|
|
299
|
+
# 2. Install plugin from npm
|
|
304
300
|
openclaw plugins install openclaw-memory-decay
|
|
305
301
|
|
|
306
|
-
#
|
|
302
|
+
# 3. Restart gateway
|
|
307
303
|
openclaw gateway restart
|
|
308
304
|
|
|
309
|
-
#
|
|
305
|
+
# 4. Verify plugin is loaded
|
|
310
306
|
openclaw plugins list
|
|
311
|
-
# Look for: memory-decay | loaded
|
|
307
|
+
# Look for: openclaw-memory-decay | loaded
|
|
312
308
|
|
|
313
|
-
#
|
|
309
|
+
# 5. Check server health
|
|
314
310
|
curl -s http://127.0.0.1:8100/health
|
|
315
311
|
# Expected: {"status":"ok","current_tick":0}
|
|
316
312
|
```
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "memory-decay",
|
|
2
|
+
"id": "openclaw-memory-decay",
|
|
3
3
|
"name": "Memory Decay",
|
|
4
4
|
"description": "Human-like memory with decay and reinforcement for OpenClaw agents",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.8",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync as nodeExecFileSync } from "node:child_process";
|
|
2
2
|
import { existsSync as nodeExistsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
3
|
+
import { homedir as nodeHomedir, tmpdir } from "node:os";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
|
|
6
6
|
function resolvePythonPath(command, { execFileSync = nodeExecFileSync, isWin = process.platform === "win32" } = {}) {
|
|
@@ -23,7 +23,9 @@ export function buildPythonCandidates({
|
|
|
23
23
|
env = process.env,
|
|
24
24
|
isWin = process.platform === "win32",
|
|
25
25
|
existsSync = nodeExistsSync,
|
|
26
|
+
homedir = nodeHomedir,
|
|
26
27
|
} = {}) {
|
|
28
|
+
const openclawStateDir = resolve(env.OPENCLAW_HOME || homedir(), ".openclaw");
|
|
27
29
|
const siblingRoots = [
|
|
28
30
|
resolve(pluginRoot, "../memory-decay"),
|
|
29
31
|
resolve(pluginRoot, "../memory-decay-core"),
|
|
@@ -31,6 +33,9 @@ export function buildPythonCandidates({
|
|
|
31
33
|
const pathCandidates = [];
|
|
32
34
|
|
|
33
35
|
if (env.MD_PYTHON_PATH) pathCandidates.push(env.MD_PYTHON_PATH);
|
|
36
|
+
pathCandidates.push(
|
|
37
|
+
join(openclawStateDir, "venvs", "memory-decay", isWin ? "Scripts/python.exe" : "bin/python"),
|
|
38
|
+
);
|
|
34
39
|
if (env.VIRTUAL_ENV) {
|
|
35
40
|
pathCandidates.push(join(env.VIRTUAL_ENV, isWin ? "Scripts/python.exe" : "bin/python"));
|
|
36
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
2
|
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
3
3
|
import { MemoryDecayClient } from "./client.js";
|
|
4
|
+
import { loadLegacyPluginConfig } from "./legacy-config.js";
|
|
4
5
|
import { MemoryDecayService, type ServiceConfig } from "./service.js";
|
|
5
6
|
import { shouldMigrate, migrateMarkdownMemories } from "./migrator.js";
|
|
6
|
-
import { mergePythonEnv } from "./python-env.js";
|
|
7
|
+
import { detectPythonEnv, mergePythonEnv } from "./python-env.js";
|
|
7
8
|
import { toFreshness } from "./types.js";
|
|
8
9
|
|
|
9
10
|
const BOOTSTRAP_PROMPT = `## Memory System (memory-decay)
|
|
@@ -39,14 +40,14 @@ Your memories naturally decay over time. Frequently recalled memories grow stron
|
|
|
39
40
|
const MIN_MESSAGE_LENGTH = 20;
|
|
40
41
|
|
|
41
42
|
const memoryDecayPlugin = {
|
|
42
|
-
id: "memory-decay",
|
|
43
|
+
id: "openclaw-memory-decay",
|
|
43
44
|
name: "Memory Decay",
|
|
44
45
|
description: "Human-like memory with decay and reinforcement",
|
|
45
46
|
kind: "memory" as const,
|
|
46
47
|
configSchema: emptyPluginConfigSchema(),
|
|
47
48
|
|
|
48
49
|
register(api: OpenClawPluginApi) {
|
|
49
|
-
const cfg = api.pluginConfig ?? {};
|
|
50
|
+
const cfg = loadLegacyPluginConfig((api.pluginConfig ?? {}) as Record<string, unknown>);
|
|
50
51
|
const port = (cfg.serverPort as number) ?? 8100;
|
|
51
52
|
const autoSave = cfg.autoSave !== false; // default true
|
|
52
53
|
|
|
@@ -90,15 +91,21 @@ const memoryDecayPlugin = {
|
|
|
90
91
|
let memoryDecayPath = (cfg.memoryDecayPath as string) ?? "";
|
|
91
92
|
let pythonPath = (cfg.pythonPath as string) ?? "";
|
|
92
93
|
let detectedEnv: { memoryDecayPath?: string; pythonPath?: string } = {};
|
|
94
|
+
const { dirname, resolve } = await import("node:path");
|
|
95
|
+
const { fileURLToPath } = await import("node:url");
|
|
96
|
+
const pluginRoot = dirname(fileURLToPath(import.meta.url));
|
|
93
97
|
if (!memoryDecayPath || !pythonPath) {
|
|
94
98
|
try {
|
|
95
99
|
const { readFileSync } = await import("node:fs");
|
|
96
|
-
const { resolve, dirname } = await import("node:path");
|
|
97
|
-
const { fileURLToPath } = await import("node:url");
|
|
98
|
-
const pluginRoot = dirname(fileURLToPath(import.meta.url));
|
|
99
100
|
detectedEnv = JSON.parse(readFileSync(resolve(pluginRoot, "../.python-env.json"), "utf8"));
|
|
100
101
|
} catch {}
|
|
101
102
|
}
|
|
103
|
+
if (!detectedEnv.memoryDecayPath || !detectedEnv.pythonPath) {
|
|
104
|
+
detectedEnv = mergePythonEnv(
|
|
105
|
+
detectedEnv,
|
|
106
|
+
detectPythonEnv({ pluginRoot }) ?? {},
|
|
107
|
+
);
|
|
108
|
+
}
|
|
102
109
|
({ memoryDecayPath, pythonPath } = mergePythonEnv(
|
|
103
110
|
{ memoryDecayPath, pythonPath },
|
|
104
111
|
detectedEnv,
|
|
@@ -106,7 +113,7 @@ const memoryDecayPlugin = {
|
|
|
106
113
|
if (!memoryDecayPath) {
|
|
107
114
|
ctx.logger.error(
|
|
108
115
|
"Could not auto-detect memory-decay installation. " +
|
|
109
|
-
"
|
|
116
|
+
"Install the backend into ~/.openclaw/venvs/memory-decay or set pythonPath/memoryDecayPath in plugin config."
|
|
110
117
|
);
|
|
111
118
|
return;
|
|
112
119
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
export type PluginConfigLike = Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
function resolveOpenClawConfigPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
8
|
+
return resolve(env.OPENCLAW_HOME || homedir(), ".openclaw", "openclaw.json");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function loadLegacyPluginConfig(
|
|
12
|
+
currentConfig: PluginConfigLike,
|
|
13
|
+
{
|
|
14
|
+
env = process.env,
|
|
15
|
+
configPath = resolveOpenClawConfigPath(env),
|
|
16
|
+
}: { env?: NodeJS.ProcessEnv; configPath?: string } = {},
|
|
17
|
+
): PluginConfigLike {
|
|
18
|
+
if (Object.keys(currentConfig).length > 0) {
|
|
19
|
+
return currentConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!existsSync(configPath)) {
|
|
23
|
+
return currentConfig;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const root = JSON.parse(readFileSync(configPath, "utf8"));
|
|
28
|
+
const legacyConfig = root?.plugins?.entries?.["memory-decay"]?.config;
|
|
29
|
+
return legacyConfig && typeof legacyConfig === "object"
|
|
30
|
+
? { ...legacyConfig }
|
|
31
|
+
: currentConfig;
|
|
32
|
+
} catch {
|
|
33
|
+
return currentConfig;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/python-env.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
import { execFileSync as nodeExecFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync as nodeExistsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { homedir as nodeHomedir, tmpdir } from "node:os";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
|
|
1
6
|
export interface PythonEnvLike {
|
|
2
7
|
memoryDecayPath?: string;
|
|
3
8
|
pythonPath?: string;
|
|
4
9
|
}
|
|
5
10
|
|
|
11
|
+
interface DetectPythonEnvOptions {
|
|
12
|
+
pluginRoot: string;
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
isWin?: boolean;
|
|
15
|
+
homedir?: () => string;
|
|
16
|
+
existsSync?: (path: string) => boolean;
|
|
17
|
+
execFileSync?: typeof nodeExecFileSync;
|
|
18
|
+
makeTempDir?: () => string;
|
|
19
|
+
readPathFile?: (path: string) => string;
|
|
20
|
+
removeTempDir?: (path: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
6
23
|
export function mergePythonEnv(
|
|
7
24
|
configured: PythonEnvLike,
|
|
8
25
|
detected: PythonEnvLike = {},
|
|
@@ -12,3 +29,105 @@ export function mergePythonEnv(
|
|
|
12
29
|
pythonPath: configured.pythonPath || detected.pythonPath || "",
|
|
13
30
|
};
|
|
14
31
|
}
|
|
32
|
+
|
|
33
|
+
function resolveOpenClawStateDir(
|
|
34
|
+
env: NodeJS.ProcessEnv,
|
|
35
|
+
homedir: () => string,
|
|
36
|
+
): string {
|
|
37
|
+
return resolve(env.OPENCLAW_HOME || homedir(), ".openclaw");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolveMemoryDecayPath(moduleFile: string): string {
|
|
41
|
+
const packageDir = dirname(resolve(moduleFile));
|
|
42
|
+
const packageParent = dirname(packageDir);
|
|
43
|
+
return basename(packageParent) === "src" ? dirname(packageParent) : packageParent;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildPythonCandidates({
|
|
47
|
+
pluginRoot,
|
|
48
|
+
env = process.env,
|
|
49
|
+
isWin = process.platform === "win32",
|
|
50
|
+
homedir = nodeHomedir,
|
|
51
|
+
existsSync = nodeExistsSync,
|
|
52
|
+
}: Omit<DetectPythonEnvOptions, "execFileSync" | "makeTempDir" | "readPathFile" | "removeTempDir">): string[] {
|
|
53
|
+
const siblingRoots = [
|
|
54
|
+
resolve(pluginRoot, "../../memory-decay"),
|
|
55
|
+
resolve(pluginRoot, "../../memory-decay-core"),
|
|
56
|
+
resolve(pluginRoot, "../memory-decay"),
|
|
57
|
+
resolve(pluginRoot, "../memory-decay-core"),
|
|
58
|
+
];
|
|
59
|
+
const openclawStateDir = resolveOpenClawStateDir(env, homedir);
|
|
60
|
+
const recommendedVenv = join(
|
|
61
|
+
openclawStateDir,
|
|
62
|
+
"venvs",
|
|
63
|
+
"memory-decay",
|
|
64
|
+
isWin ? "Scripts/python.exe" : "bin/python",
|
|
65
|
+
);
|
|
66
|
+
const pathCandidates = [
|
|
67
|
+
env.MD_PYTHON_PATH,
|
|
68
|
+
recommendedVenv,
|
|
69
|
+
env.VIRTUAL_ENV
|
|
70
|
+
? join(env.VIRTUAL_ENV, isWin ? "Scripts/python.exe" : "bin/python")
|
|
71
|
+
: undefined,
|
|
72
|
+
...siblingRoots.map((root) =>
|
|
73
|
+
join(root, isWin ? ".venv/Scripts/python.exe" : ".venv/bin/python")),
|
|
74
|
+
].filter((candidate): candidate is string => typeof candidate === "string" && candidate.length > 0 && existsSync(candidate));
|
|
75
|
+
|
|
76
|
+
const commandCandidates = isWin ? ["python"] : ["python3", "python"];
|
|
77
|
+
return [...new Set([...pathCandidates, ...commandCandidates])];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolvePythonPath(
|
|
81
|
+
command: string,
|
|
82
|
+
{
|
|
83
|
+
execFileSync = nodeExecFileSync,
|
|
84
|
+
isWin = process.platform === "win32",
|
|
85
|
+
}: Pick<DetectPythonEnvOptions, "execFileSync" | "isWin"> = {},
|
|
86
|
+
): string {
|
|
87
|
+
if (isAbsolute(command)) {
|
|
88
|
+
return command;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const resolver = isWin ? "where" : "which";
|
|
92
|
+
return execFileSync(resolver, [command], { encoding: "utf8" }).trim().split(/\r?\n/)[0];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function detectPythonEnv({
|
|
96
|
+
pluginRoot,
|
|
97
|
+
env = process.env,
|
|
98
|
+
isWin = process.platform === "win32",
|
|
99
|
+
homedir = nodeHomedir,
|
|
100
|
+
existsSync = nodeExistsSync,
|
|
101
|
+
execFileSync = nodeExecFileSync,
|
|
102
|
+
makeTempDir = () => mkdtempSync(join(tmpdir(), "memory-decay-python-env-")),
|
|
103
|
+
readPathFile = (path) => readFileSync(path, "utf8"),
|
|
104
|
+
removeTempDir = (path) => rmSync(path, { recursive: true, force: true }),
|
|
105
|
+
}: DetectPythonEnvOptions): { memoryDecayPath: string; pythonPath: string } | null {
|
|
106
|
+
for (const candidate of buildPythonCandidates({ pluginRoot, env, isWin, homedir, existsSync })) {
|
|
107
|
+
try {
|
|
108
|
+
execFileSync(candidate, ["-c", "import memory_decay.server"], { stdio: "ignore" });
|
|
109
|
+
const tempDir = makeTempDir();
|
|
110
|
+
const outputPath = join(tempDir, "memory-decay-module-path.txt");
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
execFileSync(candidate, [
|
|
114
|
+
"-c",
|
|
115
|
+
`import memory_decay, pathlib; pathlib.Path(${JSON.stringify(outputPath)}).write_text(str(pathlib.Path(memory_decay.__file__).resolve()))`,
|
|
116
|
+
], { stdio: "ignore" });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
removeTempDir(tempDir);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const moduleFile = readPathFile(outputPath).trim();
|
|
123
|
+
removeTempDir(tempDir);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
pythonPath: resolvePythonPath(candidate, { execFileSync, isWin }),
|
|
127
|
+
memoryDecayPath: resolveMemoryDecayPath(moduleFile),
|
|
128
|
+
};
|
|
129
|
+
} catch {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null;
|
|
133
|
+
}
|