openclaw-memory-decay 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -10
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/detect-python-lib.mjs +6 -1
- package/src/index.ts +11 -5
- package/src/python-env.ts +119 -0
package/README.md
CHANGED
|
@@ -63,8 +63,7 @@ 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
|
|
@@ -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` |
|
|
@@ -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
307
|
# Look for: 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
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
|
@@ -3,7 +3,7 @@ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
|
3
3
|
import { MemoryDecayClient } from "./client.js";
|
|
4
4
|
import { MemoryDecayService, type ServiceConfig } from "./service.js";
|
|
5
5
|
import { shouldMigrate, migrateMarkdownMemories } from "./migrator.js";
|
|
6
|
-
import { mergePythonEnv } from "./python-env.js";
|
|
6
|
+
import { detectPythonEnv, mergePythonEnv } from "./python-env.js";
|
|
7
7
|
import { toFreshness } from "./types.js";
|
|
8
8
|
|
|
9
9
|
const BOOTSTRAP_PROMPT = `## Memory System (memory-decay)
|
|
@@ -90,15 +90,21 @@ const memoryDecayPlugin = {
|
|
|
90
90
|
let memoryDecayPath = (cfg.memoryDecayPath as string) ?? "";
|
|
91
91
|
let pythonPath = (cfg.pythonPath as string) ?? "";
|
|
92
92
|
let detectedEnv: { memoryDecayPath?: string; pythonPath?: string } = {};
|
|
93
|
+
const { dirname, resolve } = await import("node:path");
|
|
94
|
+
const { fileURLToPath } = await import("node:url");
|
|
95
|
+
const pluginRoot = dirname(fileURLToPath(import.meta.url));
|
|
93
96
|
if (!memoryDecayPath || !pythonPath) {
|
|
94
97
|
try {
|
|
95
98
|
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
99
|
detectedEnv = JSON.parse(readFileSync(resolve(pluginRoot, "../.python-env.json"), "utf8"));
|
|
100
100
|
} catch {}
|
|
101
101
|
}
|
|
102
|
+
if (!detectedEnv.memoryDecayPath || !detectedEnv.pythonPath) {
|
|
103
|
+
detectedEnv = mergePythonEnv(
|
|
104
|
+
detectedEnv,
|
|
105
|
+
detectPythonEnv({ pluginRoot }) ?? {},
|
|
106
|
+
);
|
|
107
|
+
}
|
|
102
108
|
({ memoryDecayPath, pythonPath } = mergePythonEnv(
|
|
103
109
|
{ memoryDecayPath, pythonPath },
|
|
104
110
|
detectedEnv,
|
|
@@ -106,7 +112,7 @@ const memoryDecayPlugin = {
|
|
|
106
112
|
if (!memoryDecayPath) {
|
|
107
113
|
ctx.logger.error(
|
|
108
114
|
"Could not auto-detect memory-decay installation. " +
|
|
109
|
-
"
|
|
115
|
+
"Install the backend into ~/.openclaw/venvs/memory-decay or set pythonPath/memoryDecayPath in plugin config."
|
|
110
116
|
);
|
|
111
117
|
return;
|
|
112
118
|
}
|
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
|
+
}
|