openclaw-memory-decay 0.1.8 → 0.1.10
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 +98 -6
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -1
- package/scripts/detect-python-lib.mjs +5 -1
- package/src/index.ts +10 -3
- package/src/python-env.ts +21 -4
- package/src/service.ts +59 -7
package/README.md
CHANGED
|
@@ -86,17 +86,22 @@ openclaw gateway restart
|
|
|
86
86
|
If you previously installed this plugin from a local checkout via `openclaw plugins install -l .`, migrate to the npm package. Local/path-based installs are now deprecated in favor of the published npm package.
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
|
-
# 1.
|
|
89
|
+
# 1. Remove the old pre-0.1.8 plugin install if it exists
|
|
90
|
+
openclaw plugins uninstall memory-decay
|
|
91
|
+
|
|
92
|
+
# 2. Install from npm instead
|
|
90
93
|
openclaw plugins install openclaw-memory-decay
|
|
91
94
|
|
|
92
|
-
#
|
|
95
|
+
# 3. Restart gateway
|
|
93
96
|
openclaw gateway restart
|
|
94
97
|
|
|
95
|
-
#
|
|
98
|
+
# 4. Verify
|
|
96
99
|
openclaw plugins list | grep openclaw-memory-decay # should show: openclaw-memory-decay | loaded
|
|
97
100
|
curl -s http://127.0.0.1:8100/health # should show: {"status":"ok","current_tick":0}
|
|
98
101
|
```
|
|
99
102
|
|
|
103
|
+
If the old install is already gone, `openclaw plugins uninstall memory-decay` will simply report that nothing was removed.
|
|
104
|
+
|
|
100
105
|
If auto-detection does not recover your backend path after migration, set the interpreter explicitly:
|
|
101
106
|
|
|
102
107
|
```bash
|
|
@@ -104,6 +109,12 @@ openclaw config set plugins.entries.openclaw-memory-decay.config.pythonPath "~/.
|
|
|
104
109
|
openclaw gateway restart
|
|
105
110
|
```
|
|
106
111
|
|
|
112
|
+
If you are upgrading from an older release, note the plugin id changed from `memory-decay` to `openclaw-memory-decay` in `0.1.8`.
|
|
113
|
+
|
|
114
|
+
- New installs should use `plugins.entries.openclaw-memory-decay`.
|
|
115
|
+
- Older configs under `plugins.entries.memory-decay.config` are still read as a compatibility fallback.
|
|
116
|
+
- For a clean config, migrate to the new key when convenient.
|
|
117
|
+
|
|
107
118
|
**Your memories are safe.** The SQLite database (`memories.db`) is not affected by plugin reinstallation or migration to npm install.
|
|
108
119
|
|
|
109
120
|
To update in the future:
|
|
@@ -136,8 +147,8 @@ Add to `~/.openclaw/openclaw.json` under `plugins.entries.openclaw-memory-decay.
|
|
|
136
147
|
| Option | Default | Description |
|
|
137
148
|
|--------|---------|-------------|
|
|
138
149
|
| `serverPort` | `8100` | Port for the memory-decay HTTP server |
|
|
139
|
-
| `memoryDecayPath` | (auto) | Path to memory-decay-core. Auto-detected from the
|
|
140
|
-
| `pythonPath` | `python3` | Path to Python interpreter. Set this explicitly if you
|
|
150
|
+
| `memoryDecayPath` | (auto) | Path to memory-decay-core. Auto-detected at runtime from the documented default venv or from explicit config |
|
|
151
|
+
| `pythonPath` | `python3` | Path to Python interpreter. Set this explicitly if you do not use `~/.openclaw/venvs/memory-decay` |
|
|
141
152
|
| `dbPath` | `~/.openclaw/memory-decay-data/memories.db` | SQLite database location |
|
|
142
153
|
| `autoSave` | `true` | Auto-save every conversation turn at low importance. Set `false` to let the agent decide what to save |
|
|
143
154
|
| `embeddingProvider` | `local` | Embedding provider: `local`, `openai`, or `gemini` |
|
|
@@ -263,12 +274,32 @@ openclaw plugins install openclaw-memory-decay
|
|
|
263
274
|
curl http://127.0.0.1:8100/health
|
|
264
275
|
```
|
|
265
276
|
|
|
266
|
-
If the import check fails, install the backend into
|
|
277
|
+
If the import check fails, install the backend into the documented default venv path or set `pythonPath` explicitly:
|
|
267
278
|
```bash
|
|
268
279
|
python3 -m venv ~/.openclaw/venvs/memory-decay
|
|
269
280
|
~/.openclaw/venvs/memory-decay/bin/pip install memory-decay
|
|
270
281
|
```
|
|
271
282
|
|
|
283
|
+
If `memory-decay` is only installed in your system Python or in some other venv, the plugin may still fail to auto-detect it. In that case, point the plugin at the exact Python you want it to use:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
openclaw config set plugins.entries.openclaw-memory-decay.config.pythonPath "/absolute/path/to/python"
|
|
287
|
+
openclaw gateway restart
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Sanity check the exact interpreter before restarting:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
/absolute/path/to/python -c "import memory_decay.server; print('ok')"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
If you use a different venv path, configure it directly:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
openclaw config set plugins.entries.openclaw-memory-decay.config.pythonPath "/absolute/path/to/venv/bin/python"
|
|
300
|
+
openclaw gateway restart
|
|
301
|
+
```
|
|
302
|
+
|
|
272
303
|
### `error: externally-managed-environment`
|
|
273
304
|
|
|
274
305
|
Your system Python is PEP 668 managed. Install `memory-decay` into a virtualenv instead of the system interpreter:
|
|
@@ -359,6 +390,67 @@ openclaw gateway restart
|
|
|
359
390
|
| `~/.openclaw/memory-decay-data/memories.db` | Memory database |
|
|
360
391
|
| `~/.openclaw/workspace/memory/` | Legacy memory files (can migrate) |
|
|
361
392
|
|
|
393
|
+
## Known Upgrade Issues
|
|
394
|
+
|
|
395
|
+
These are real migration issues we hit while moving from older releases. If an AI agent is helping with setup, it should check these before assuming the install itself is broken.
|
|
396
|
+
|
|
397
|
+
### 1. Old `memory-decay` install still exists
|
|
398
|
+
|
|
399
|
+
Symptom:
|
|
400
|
+
- `duplicate plugin id detected`
|
|
401
|
+
- `plugin id mismatch`
|
|
402
|
+
- both `memory-decay` and `openclaw-memory-decay` appear in `openclaw plugins list`
|
|
403
|
+
|
|
404
|
+
Fix:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
openclaw plugins uninstall memory-decay
|
|
408
|
+
openclaw gateway restart
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
If duplicate warnings still mention `.openclaw-install-stage-*`, remove stale failed-install directories:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
find ~/.openclaw/extensions -maxdepth 1 -type d -name '.openclaw-install-stage-*' -print
|
|
415
|
+
find ~/.openclaw/extensions -maxdepth 1 -type d -name '.openclaw-install-stage-*' -exec rm -rf {} +
|
|
416
|
+
openclaw gateway restart
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 2. Plugin updated, but port `8100` never comes up
|
|
420
|
+
|
|
421
|
+
Symptom:
|
|
422
|
+
- `curl http://127.0.0.1:8100/health` times out
|
|
423
|
+
- gateway log says `Could not auto-detect memory-decay installation`
|
|
424
|
+
|
|
425
|
+
Fix:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
openclaw config set plugins.entries.openclaw-memory-decay.config.pythonPath "~/.openclaw/venvs/memory-decay/bin/python"
|
|
429
|
+
openclaw config set plugins.entries.openclaw-memory-decay.config.serverPort 8100
|
|
430
|
+
openclaw gateway restart
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
If you also need to restore the backend root explicitly:
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
openclaw config set plugins.entries.openclaw-memory-decay.config.memoryDecayPath "/absolute/path/to/memory-decay-core"
|
|
437
|
+
openclaw gateway restart
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### 3. `memory-decay` is installed, but only in system Python or another venv
|
|
441
|
+
|
|
442
|
+
Symptom:
|
|
443
|
+
- `pip show memory-decay` looks fine
|
|
444
|
+
- plugin still cannot find `memory_decay.server`
|
|
445
|
+
|
|
446
|
+
Fix:
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
/absolute/path/to/python -c "import memory_decay.server; print('ok')"
|
|
450
|
+
openclaw config set plugins.entries.openclaw-memory-decay.config.pythonPath "/absolute/path/to/python"
|
|
451
|
+
openclaw gateway restart
|
|
452
|
+
```
|
|
453
|
+
|
|
362
454
|
## License
|
|
363
455
|
|
|
364
456
|
MIT
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-memory-decay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw memory plugin backed by memory-decay engine",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc",
|
|
20
|
+
"test": "node --test test/*.test.mjs",
|
|
21
|
+
"test:integration": "docker build -t memory-decay-test -f Dockerfile .. && docker run --rm memory-decay-test",
|
|
20
22
|
"prepublishOnly": "npm run build",
|
|
21
23
|
"postinstall": "node scripts/detect-python.mjs",
|
|
22
24
|
"setup": "node scripts/link-sdk.mjs"
|
|
@@ -3,6 +3,10 @@ import { existsSync as nodeExistsSync, mkdtempSync, readFileSync, rmSync } from
|
|
|
3
3
|
import { homedir as nodeHomedir, tmpdir } from "node:os";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
|
|
6
|
+
const PYTHON_COMMAND_CANDIDATES = process.platform === "win32"
|
|
7
|
+
? ["python"]
|
|
8
|
+
: ["python3", "python", "python3.13", "python3.12", "python3.11", "python3.10"];
|
|
9
|
+
|
|
6
10
|
function resolvePythonPath(command, { execFileSync = nodeExecFileSync, isWin = process.platform === "win32" } = {}) {
|
|
7
11
|
if (isAbsolute(command)) {
|
|
8
12
|
return command;
|
|
@@ -43,7 +47,7 @@ export function buildPythonCandidates({
|
|
|
43
47
|
pathCandidates.push(join(root, isWin ? ".venv/Scripts/python.exe" : ".venv/bin/python"));
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
const commandCandidates = isWin ? ["python"] :
|
|
50
|
+
const commandCandidates = isWin ? ["python"] : PYTHON_COMMAND_CANDIDATES;
|
|
47
51
|
return [...new Set([
|
|
48
52
|
...pathCandidates.filter((candidate) => existsSync(candidate)),
|
|
49
53
|
...commandCandidates,
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { MemoryDecayClient } from "./client.js";
|
|
|
4
4
|
import { loadLegacyPluginConfig } from "./legacy-config.js";
|
|
5
5
|
import { MemoryDecayService, type ServiceConfig } from "./service.js";
|
|
6
6
|
import { shouldMigrate, migrateMarkdownMemories } from "./migrator.js";
|
|
7
|
-
import { detectPythonEnv, mergePythonEnv } from "./python-env.js";
|
|
7
|
+
import { detectPythonEnv, mergePythonEnv, REQUIRED_BACKEND_VERSION } from "./python-env.js";
|
|
8
8
|
import { toFreshness } from "./types.js";
|
|
9
9
|
|
|
10
10
|
const BOOTSTRAP_PROMPT = `## Memory System (memory-decay)
|
|
@@ -90,7 +90,7 @@ const memoryDecayPlugin = {
|
|
|
90
90
|
// Resolve pythonPath + memoryDecayPath: config > .python-env.json (set at install time) > error
|
|
91
91
|
let memoryDecayPath = (cfg.memoryDecayPath as string) ?? "";
|
|
92
92
|
let pythonPath = (cfg.pythonPath as string) ?? "";
|
|
93
|
-
let detectedEnv: { memoryDecayPath?: string; pythonPath?: string } = {};
|
|
93
|
+
let detectedEnv: { memoryDecayPath?: string; pythonPath?: string; backendVersion?: string } = {};
|
|
94
94
|
const { dirname, resolve } = await import("node:path");
|
|
95
95
|
const { fileURLToPath } = await import("node:url");
|
|
96
96
|
const pluginRoot = dirname(fileURLToPath(import.meta.url));
|
|
@@ -110,6 +110,7 @@ const memoryDecayPlugin = {
|
|
|
110
110
|
{ memoryDecayPath, pythonPath },
|
|
111
111
|
detectedEnv,
|
|
112
112
|
));
|
|
113
|
+
const backendVersion = detectedEnv.backendVersion ?? "unknown";
|
|
113
114
|
if (!memoryDecayPath) {
|
|
114
115
|
ctx.logger.error(
|
|
115
116
|
"Could not auto-detect memory-decay installation. " +
|
|
@@ -131,9 +132,15 @@ const memoryDecayPlugin = {
|
|
|
131
132
|
experimentDir: cfg.experimentDir as string | undefined,
|
|
132
133
|
};
|
|
133
134
|
|
|
134
|
-
service = new MemoryDecayService(config);
|
|
135
|
+
service = new MemoryDecayService(config, ctx.logger);
|
|
135
136
|
await service.start();
|
|
136
137
|
ctx.logger.info("Server started");
|
|
138
|
+
if (backendVersion !== "unknown" && backendVersion !== REQUIRED_BACKEND_VERSION) {
|
|
139
|
+
ctx.logger.info(
|
|
140
|
+
`Backend version ${backendVersion} does not match recommended ${REQUIRED_BACKEND_VERSION}. ` +
|
|
141
|
+
`To update: pip install memory-decay==${REQUIRED_BACKEND_VERSION}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
137
144
|
},
|
|
138
145
|
async stop(ctx) {
|
|
139
146
|
if (service) {
|
package/src/python-env.ts
CHANGED
|
@@ -20,13 +20,18 @@ interface DetectPythonEnvOptions {
|
|
|
20
20
|
removeTempDir?: (path: string) => void;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
const PYTHON_COMMAND_CANDIDATES = process.platform === "win32"
|
|
24
|
+
? ["python"]
|
|
25
|
+
: ["python3", "python", "python3.13", "python3.12", "python3.11", "python3.10"];
|
|
26
|
+
|
|
23
27
|
export function mergePythonEnv(
|
|
24
28
|
configured: PythonEnvLike,
|
|
25
|
-
detected:
|
|
26
|
-
): { memoryDecayPath: string; pythonPath: string } {
|
|
29
|
+
detected: { memoryDecayPath?: string; pythonPath?: string; backendVersion?: string } = {},
|
|
30
|
+
): { memoryDecayPath: string; pythonPath: string; backendVersion: string } {
|
|
27
31
|
return {
|
|
28
32
|
memoryDecayPath: configured.memoryDecayPath || detected.memoryDecayPath || "",
|
|
29
33
|
pythonPath: configured.pythonPath || detected.pythonPath || "",
|
|
34
|
+
backendVersion: detected.backendVersion || "unknown",
|
|
30
35
|
};
|
|
31
36
|
}
|
|
32
37
|
|
|
@@ -73,7 +78,7 @@ export function buildPythonCandidates({
|
|
|
73
78
|
join(root, isWin ? ".venv/Scripts/python.exe" : ".venv/bin/python")),
|
|
74
79
|
].filter((candidate): candidate is string => typeof candidate === "string" && candidate.length > 0 && existsSync(candidate));
|
|
75
80
|
|
|
76
|
-
const commandCandidates = isWin ? ["python"] :
|
|
81
|
+
const commandCandidates = isWin ? ["python"] : PYTHON_COMMAND_CANDIDATES;
|
|
77
82
|
return [...new Set([...pathCandidates, ...commandCandidates])];
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -92,6 +97,8 @@ function resolvePythonPath(
|
|
|
92
97
|
return execFileSync(resolver, [command], { encoding: "utf8" }).trim().split(/\r?\n/)[0];
|
|
93
98
|
}
|
|
94
99
|
|
|
100
|
+
export const REQUIRED_BACKEND_VERSION = "0.1.3";
|
|
101
|
+
|
|
95
102
|
export function detectPythonEnv({
|
|
96
103
|
pluginRoot,
|
|
97
104
|
env = process.env,
|
|
@@ -102,7 +109,7 @@ export function detectPythonEnv({
|
|
|
102
109
|
makeTempDir = () => mkdtempSync(join(tmpdir(), "memory-decay-python-env-")),
|
|
103
110
|
readPathFile = (path) => readFileSync(path, "utf8"),
|
|
104
111
|
removeTempDir = (path) => rmSync(path, { recursive: true, force: true }),
|
|
105
|
-
}: DetectPythonEnvOptions): { memoryDecayPath: string; pythonPath: string } | null {
|
|
112
|
+
}: DetectPythonEnvOptions): { memoryDecayPath: string; pythonPath: string; backendVersion: string } | null {
|
|
106
113
|
for (const candidate of buildPythonCandidates({ pluginRoot, env, isWin, homedir, existsSync })) {
|
|
107
114
|
try {
|
|
108
115
|
execFileSync(candidate, ["-c", "import memory_decay.server"], { stdio: "ignore" });
|
|
@@ -122,9 +129,19 @@ export function detectPythonEnv({
|
|
|
122
129
|
const moduleFile = readPathFile(outputPath).trim();
|
|
123
130
|
removeTempDir(tempDir);
|
|
124
131
|
|
|
132
|
+
let backendVersion = "unknown";
|
|
133
|
+
try {
|
|
134
|
+
const versionOutput = execFileSync(candidate, [
|
|
135
|
+
"-c",
|
|
136
|
+
`import memory_decay; print(getattr(memory_decay, "__version__", "unknown"))`,
|
|
137
|
+
], { encoding: "utf8" }).trim();
|
|
138
|
+
backendVersion = versionOutput || "unknown";
|
|
139
|
+
} catch {}
|
|
140
|
+
|
|
125
141
|
return {
|
|
126
142
|
pythonPath: resolvePythonPath(candidate, { execFileSync, isWin }),
|
|
127
143
|
memoryDecayPath: resolveMemoryDecayPath(moduleFile),
|
|
144
|
+
backendVersion,
|
|
128
145
|
};
|
|
129
146
|
} catch {}
|
|
130
147
|
}
|
package/src/service.ts
CHANGED
|
@@ -13,19 +13,34 @@ export interface ServiceConfig {
|
|
|
13
13
|
experimentDir?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface Logger {
|
|
17
|
+
info: (msg: string) => void;
|
|
18
|
+
warn: (msg: string) => void;
|
|
19
|
+
error: (msg: string) => void;
|
|
20
|
+
debug?: (msg: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
export class MemoryDecayService {
|
|
17
24
|
private process: ChildProcess | null = null;
|
|
18
25
|
private client: MemoryDecayClient;
|
|
19
26
|
private config: ServiceConfig;
|
|
27
|
+
private logger: Logger;
|
|
20
28
|
private restartCount = 0;
|
|
21
29
|
private maxRestarts = 3;
|
|
30
|
+
private stderrTail: string[] = [];
|
|
31
|
+
private stopped = false;
|
|
32
|
+
private static readonly STDERR_MAX_LINES = 50;
|
|
22
33
|
|
|
23
|
-
constructor(config: ServiceConfig) {
|
|
34
|
+
constructor(config: ServiceConfig, logger: Logger) {
|
|
24
35
|
this.config = config;
|
|
36
|
+
this.logger = logger;
|
|
25
37
|
this.client = new MemoryDecayClient(config.port);
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
async start(): Promise<void> {
|
|
41
|
+
this.stopped = false;
|
|
42
|
+
this.stderrTail = [];
|
|
43
|
+
|
|
29
44
|
const args = [
|
|
30
45
|
"-m", "memory_decay.server",
|
|
31
46
|
"--host", "127.0.0.1",
|
|
@@ -44,11 +59,37 @@ export class MemoryDecayService {
|
|
|
44
59
|
stdio: ["ignore", "pipe", "pipe"],
|
|
45
60
|
});
|
|
46
61
|
|
|
47
|
-
this.process.on("
|
|
48
|
-
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
this.process.stderr?.on("data", (chunk: Buffer) => {
|
|
63
|
+
for (const line of chunk.toString().split("\n").filter(Boolean)) {
|
|
64
|
+
this.stderrTail.push(line);
|
|
65
|
+
if (this.stderrTail.length > MemoryDecayService.STDERR_MAX_LINES) {
|
|
66
|
+
this.stderrTail.shift();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.process.stdout?.on("data", (chunk: Buffer) => {
|
|
72
|
+
if (this.logger.debug) {
|
|
73
|
+
this.logger.debug(chunk.toString().trimEnd());
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.process.on("exit", (code, signal) => {
|
|
78
|
+
if (code !== 0) {
|
|
79
|
+
const context = this.formatStderrContext();
|
|
80
|
+
const sig = signal ? ` (signal: ${signal})` : "";
|
|
81
|
+
|
|
82
|
+
if (this.restartCount < this.maxRestarts) {
|
|
83
|
+
this.restartCount++;
|
|
84
|
+
this.logger.error(
|
|
85
|
+
`Server exited with code ${code}${sig}, restarting (${this.restartCount}/${this.maxRestarts})${context}`
|
|
86
|
+
);
|
|
87
|
+
this.start().catch(() => {});
|
|
88
|
+
} else {
|
|
89
|
+
this.logger.error(
|
|
90
|
+
`Server exited with code ${code}${sig}, max restarts (${this.maxRestarts}) exhausted${context}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
52
93
|
}
|
|
53
94
|
});
|
|
54
95
|
|
|
@@ -56,8 +97,11 @@ export class MemoryDecayService {
|
|
|
56
97
|
}
|
|
57
98
|
|
|
58
99
|
async stop(): Promise<void> {
|
|
100
|
+
this.stopped = true;
|
|
59
101
|
if (this.process) {
|
|
60
102
|
this.maxRestarts = 0;
|
|
103
|
+
this.process.stdout?.removeAllListeners("data");
|
|
104
|
+
this.process.stderr?.removeAllListeners("data");
|
|
61
105
|
this.process.kill("SIGTERM");
|
|
62
106
|
this.process = null;
|
|
63
107
|
}
|
|
@@ -67,16 +111,24 @@ export class MemoryDecayService {
|
|
|
67
111
|
return this.client;
|
|
68
112
|
}
|
|
69
113
|
|
|
114
|
+
private formatStderrContext(): string {
|
|
115
|
+
return this.stderrTail.length
|
|
116
|
+
? `\nLast stderr:\n ${this.stderrTail.slice(-10).join("\n ")}`
|
|
117
|
+
: "";
|
|
118
|
+
}
|
|
119
|
+
|
|
70
120
|
private async waitForHealth(timeoutMs = 15000): Promise<void> {
|
|
71
121
|
const start = Date.now();
|
|
72
122
|
while (Date.now() - start < timeoutMs) {
|
|
123
|
+
if (this.stopped) return;
|
|
73
124
|
try {
|
|
74
125
|
await this.client.health();
|
|
126
|
+
this.restartCount = 0;
|
|
75
127
|
return;
|
|
76
128
|
} catch {
|
|
77
129
|
await new Promise((r) => setTimeout(r, 500));
|
|
78
130
|
}
|
|
79
131
|
}
|
|
80
|
-
throw new Error(`
|
|
132
|
+
throw new Error(`Server failed to start within ${timeoutMs}ms${this.formatStderrContext()}`);
|
|
81
133
|
}
|
|
82
134
|
}
|