@memoryrelay/plugin-memoryrelay-ai 0.16.3 → 0.17.1
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 +143 -1
- package/index.ts +151 -21
- package/openclaw.plugin.json +43 -2
- package/package.json +11 -1
- package/src/cache/local-cache.ts +374 -0
- package/src/cache/memory-manager.ts +100 -0
- package/src/cache/schema.ts +130 -0
- package/src/cache/sync-daemon.ts +199 -0
- package/src/cache/types.ts +53 -0
- package/src/cache/vector.ts +162 -0
- package/src/client/memoryrelay-client.ts +1 -1
- package/src/hooks/agent-end.ts +4 -2
- package/src/hooks/before-prompt-build.ts +4 -2
- package/src/pipelines/capture/store.ts +28 -4
- package/src/pipelines/recall/search.ts +82 -2
- package/src/pipelines/types.ts +26 -1
- package/src/status-reporter.ts +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Persistent memory, architectural decisions, reusable patterns, and project orchestration for AI agents.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@memoryrelay/plugin-memoryrelay-ai)
|
|
8
|
-
[](https://openclaw.ai)
|
|
9
9
|
|
|
10
10
|
## Why MemoryRelay?
|
|
11
11
|
|
|
@@ -34,6 +34,18 @@ MemoryRelay is designed for engineering teams managing complex, long-running pro
|
|
|
34
34
|
openclaw plugins install @memoryrelay/plugin-memoryrelay-ai
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
**1b. Install native dependencies (for local SQLite cache)**
|
|
38
|
+
|
|
39
|
+
The local cache requires `better-sqlite3`, which includes native bindings. After plugin installation, run:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd ~/.openclaw/extensions/plugin-memoryrelay-ai && npm install --omit=dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or install globally: `npm install -g better-sqlite3`
|
|
46
|
+
|
|
47
|
+
> **Note:** If you skip this step, the plugin still works — it falls back to API-only mode (no local cache).
|
|
48
|
+
|
|
37
49
|
**2. Set your API key**
|
|
38
50
|
|
|
39
51
|
```bash
|
|
@@ -226,6 +238,7 @@ openclaw config set plugins.entries.plugin-memoryrelay-ai.config '{
|
|
|
226
238
|
| `excludeChannels` | string[] | `[]` | Channel IDs to skip auto-recall |
|
|
227
239
|
| `sessionTimeoutMinutes` | number | `120` | Idle time before session auto-close (10-1440) |
|
|
228
240
|
| `sessionCleanupIntervalMinutes` | number | `30` | Stale session check interval (5-360) |
|
|
241
|
+
| `localCache` | object | see below | Local SQLite cache configuration (v0.17.0+) |
|
|
229
242
|
| `debug` | boolean | `false` | Enable debug logging of API calls |
|
|
230
243
|
| `verbose` | boolean | `false` | Include request/response bodies in logs |
|
|
231
244
|
| `maxLogEntries` | number | `100` | Circular buffer size for in-memory logs (10-10000) |
|
|
@@ -239,6 +252,33 @@ openclaw config set plugins.entries.plugin-memoryrelay-ai.config '{
|
|
|
239
252
|
| `MEMORYRELAY_API_URL` | `apiUrl` |
|
|
240
253
|
| `MEMORYRELAY_DEFAULT_PROJECT` | `defaultProject` |
|
|
241
254
|
|
|
255
|
+
### Local Cache Configuration (v0.17.0+)
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"localCache": {
|
|
260
|
+
"enabled": true,
|
|
261
|
+
"dbPath": "~/.openclaw/memoryrelay-cache.db",
|
|
262
|
+
"syncIntervalMinutes": 5,
|
|
263
|
+
"maxLocalMemories": 10000,
|
|
264
|
+
"vectorSearch": { "enabled": false, "provider": "sqlite-vec" },
|
|
265
|
+
"ttl": { "hot": 72, "warm": 168, "cold": 720 }
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
| Key | Type | Default | Description |
|
|
271
|
+
|-----|------|---------|-------------|
|
|
272
|
+
| `localCache.enabled` | boolean | `true` | Enable local SQLite cache |
|
|
273
|
+
| `localCache.dbPath` | string | `~/.openclaw/memoryrelay-cache.db` | Path to SQLite database |
|
|
274
|
+
| `localCache.syncIntervalMinutes` | number | `5` | Background sync interval (1-60) |
|
|
275
|
+
| `localCache.maxLocalMemories` | number | `10000` | Max memories stored locally |
|
|
276
|
+
| `localCache.vectorSearch.enabled` | boolean | `false` | Enable sqlite-vec vector search |
|
|
277
|
+
| `localCache.vectorSearch.provider` | string | `sqlite-vec` | Vector extension provider |
|
|
278
|
+
| `localCache.ttl.hot` | number | `72` | Hot tier TTL in hours (3 days) |
|
|
279
|
+
| `localCache.ttl.warm` | number | `168` | Warm tier TTL in hours (7 days) |
|
|
280
|
+
| `localCache.ttl.cold` | number | `720` | Cold tier TTL in hours (30 days) |
|
|
281
|
+
|
|
242
282
|
### Auto-Capture Tiers
|
|
243
283
|
|
|
244
284
|
| Tier | Behavior | Use When |
|
|
@@ -267,6 +307,22 @@ The `confirmFirst` setting (default: `5`) prompts for confirmation on the first
|
|
|
267
307
|
}
|
|
268
308
|
```
|
|
269
309
|
|
|
310
|
+
## Performance (v0.17.0+)
|
|
311
|
+
|
|
312
|
+
With local cache enabled (default in v0.17.0), most operations skip API round-trips entirely:
|
|
313
|
+
|
|
314
|
+
| Operation | API-only (v0.16) | Local cache (v0.17) | Improvement |
|
|
315
|
+
|-----------|------------------|---------------------|-------------|
|
|
316
|
+
| Recall | ~200–500ms | <5ms | 40–100× |
|
|
317
|
+
| Capture | ~150–300ms | <2ms | 75–150× |
|
|
318
|
+
| Status probe | ~100ms | <1ms | 100× |
|
|
319
|
+
|
|
320
|
+
The local cache uses SQLite (better-sqlite3) with FTS5 for full-text search. An optional sqlite-vec extension enables local vector similarity search without API round-trips.
|
|
321
|
+
|
|
322
|
+
SyncDaemon runs in the background, pushing buffered writes and pulling remote changes on a configurable interval (default: 5 minutes).
|
|
323
|
+
|
|
324
|
+
> **Note:** v0.17.0 also fixes the `· unavailable` status display in `openclaw status` — the plugin now returns real memory counts from the local cache.
|
|
325
|
+
|
|
270
326
|
## Architecture & Privacy
|
|
271
327
|
|
|
272
328
|
### Data Flow
|
|
@@ -383,6 +439,92 @@ Then inspect with `/memory-logs` or `/memory-metrics` to identify slow or failin
|
|
|
383
439
|
|
|
384
440
|
- `memory_batch_store`: May return 500 errors on large batches (use individual `memory_store` as workaround)
|
|
385
441
|
|
|
442
|
+
## VPS Setup
|
|
443
|
+
|
|
444
|
+
Complete guide for running Claude Code with MemoryRelay on a VPS (Ubuntu).
|
|
445
|
+
|
|
446
|
+
### Prerequisites
|
|
447
|
+
|
|
448
|
+
- Node.js 20+
|
|
449
|
+
- [OpenClaw](https://openclaw.ai) installed and configured
|
|
450
|
+
- [Claude Code](https://claude.ai/code) CLI installed
|
|
451
|
+
|
|
452
|
+
### 1. Install the MCP server
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
npm install -g @memoryrelay/mcp-server
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### 2. Configure Claude Code settings
|
|
459
|
+
|
|
460
|
+
Add the MemoryRelay MCP server to `~/.claude/settings.json`:
|
|
461
|
+
|
|
462
|
+
```json
|
|
463
|
+
{
|
|
464
|
+
"mcpServers": {
|
|
465
|
+
"MemoryRelay": {
|
|
466
|
+
"command": "memoryrelay-mcp",
|
|
467
|
+
"args": ["--agent-id", "YOUR_AGENT_UUID"],
|
|
468
|
+
"env": {
|
|
469
|
+
"MEMORYRELAY_API_KEY": "mem_prod_your_key_here"
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
> **Important:** `agentId` must be a UUID obtained from `GET /v1/agents` — not a name string. Using a name string will cause authentication failures.
|
|
477
|
+
|
|
478
|
+
### 3. Install the OpenClaw plugin
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
openclaw plugins install @memoryrelay/plugin-memoryrelay-ai
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### 4. Add `.mcp.json` to each project
|
|
485
|
+
|
|
486
|
+
Create `.mcp.json` in each project worktree root. This is **required** for MCP tools to be available in Claude sessions (including `claude --print`):
|
|
487
|
+
|
|
488
|
+
```json
|
|
489
|
+
{
|
|
490
|
+
"mcpServers": {
|
|
491
|
+
"MemoryRelay": {
|
|
492
|
+
"command": "memoryrelay-mcp",
|
|
493
|
+
"args": ["--agent-id", "YOUR_AGENT_UUID"],
|
|
494
|
+
"env": {
|
|
495
|
+
"MEMORYRELAY_API_KEY": "mem_prod_your_key_here"
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 5. Install Alteriom Claude Skills (optional)
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
git clone git@github.com:Alteriom/alteriom-claude-skills.git ~/.alteriom-claude-skills
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
These provide curated skill files for common workflows across projects.
|
|
509
|
+
|
|
510
|
+
## Known Issues
|
|
511
|
+
|
|
512
|
+
| Issue | Status | Workaround |
|
|
513
|
+
|-------|--------|------------|
|
|
514
|
+
| `openclaw status` shows `· unavailable` on OpenClaw 2026.3.28 | Cosmetic — plugin is functional | Fix planned in v0.17.0 (local cache with MemorySearchManager-compatible schema) |
|
|
515
|
+
| `plugins update --all` doesn't reliably update extensions | OpenClaw CLI bug | `rm -rf ~/.openclaw/extensions/plugin-memoryrelay-ai && openclaw plugins install @memoryrelay/plugin-memoryrelay-ai` |
|
|
516
|
+
| `agentId` must be a UUID from `GET /v1/agents` | By design | Do not use agent name strings — retrieve the UUID from the API |
|
|
517
|
+
|
|
518
|
+
## Roadmap
|
|
519
|
+
|
|
520
|
+
**v0.17.0** — Local SQLite cache layer ([Epic #62](https://github.com/memoryrelay/openclaw-plugin/issues/62))
|
|
521
|
+
|
|
522
|
+
- Local SQLite cache for offline-first memory access
|
|
523
|
+
- SyncDaemon for background API synchronization
|
|
524
|
+
- Local vector search via `sqlite-vec`
|
|
525
|
+
- `MemorySearchManager`-compatible schema (fixes `openclaw status` display)
|
|
526
|
+
- Issues [#63](https://github.com/memoryrelay/openclaw-plugin/issues/63)–[#72](https://github.com/memoryrelay/openclaw-plugin/issues/72)
|
|
527
|
+
|
|
386
528
|
## Development
|
|
387
529
|
|
|
388
530
|
```bash
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenClaw Memory Plugin - MemoryRelay
|
|
3
|
-
* Version: 0.
|
|
3
|
+
* Version: 0.17.1
|
|
4
4
|
*
|
|
5
5
|
* Long-term memory with vector search using MemoryRelay API.
|
|
6
6
|
* Provides auto-recall and auto-capture via lifecycle hooks.
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* Docs: https://memoryrelay.ai
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { mkdirSync } from "node:fs";
|
|
18
|
+
import { homedir } from "node:os";
|
|
18
19
|
import { join } from "node:path";
|
|
19
20
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
20
21
|
|
|
@@ -31,6 +32,10 @@ import {
|
|
|
31
32
|
VALID_HEALTH_STATUSES,
|
|
32
33
|
} from "./src/client/memoryrelay-client.js";
|
|
33
34
|
import { SessionResolver } from "./src/context/session-resolver.js";
|
|
35
|
+
import { LocalCache } from "./src/cache/local-cache.js";
|
|
36
|
+
import { SyncDaemon } from "./src/cache/sync-daemon.js";
|
|
37
|
+
import { PluginMemoryManager } from "./src/cache/memory-manager.js";
|
|
38
|
+
import type { LocalCacheConfig } from "./src/cache/types.js";
|
|
34
39
|
|
|
35
40
|
// --- Hooks ---
|
|
36
41
|
import { registerBeforeAgentStart } from "./src/hooks/before-agent-start.js";
|
|
@@ -98,6 +103,7 @@ interface MemoryRelayConfig {
|
|
|
98
103
|
maxLogEntries?: number;
|
|
99
104
|
sessionTimeoutMinutes?: number;
|
|
100
105
|
sessionCleanupIntervalMinutes?: number;
|
|
106
|
+
localCache?: Partial<LocalCacheConfig>;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
// ============================================================================
|
|
@@ -352,23 +358,64 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
352
358
|
api.logger.error(`memory-memoryrelay: health check failed: ${String(err)}`);
|
|
353
359
|
}
|
|
354
360
|
|
|
355
|
-
// ---
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
361
|
+
// --- Local Cache + SyncDaemon (v0.17.0+) ---
|
|
362
|
+
// Replaces the stub file hack from v0.16.x. LocalCache creates a real SQLite
|
|
363
|
+
// database at the expected path, satisfying OpenClaw's existsSync scanner and
|
|
364
|
+
// enabling local-first recall/capture pipelines.
|
|
365
|
+
const DEFAULT_CACHE_CONFIG: LocalCacheConfig = {
|
|
366
|
+
enabled: true,
|
|
367
|
+
dbPath: "",
|
|
368
|
+
syncIntervalMinutes: 5,
|
|
369
|
+
maxLocalMemories: 1000,
|
|
370
|
+
vectorSearch: { enabled: false, provider: "none" },
|
|
371
|
+
ttl: { hot: 72, warm: 168, cold: 720 },
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const localCacheConfig: LocalCacheConfig = {
|
|
375
|
+
...DEFAULT_CACHE_CONFIG,
|
|
376
|
+
...cfg?.localCache,
|
|
377
|
+
vectorSearch: { ...DEFAULT_CACHE_CONFIG.vectorSearch, ...cfg?.localCache?.vectorSearch },
|
|
378
|
+
ttl: { ...DEFAULT_CACHE_CONFIG.ttl, ...cfg?.localCache?.ttl },
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
let localCache: LocalCache | null = null;
|
|
382
|
+
let syncDaemon: SyncDaemon | null = null;
|
|
383
|
+
let memoryManager: PluginMemoryManager | null = null;
|
|
384
|
+
|
|
385
|
+
if (localCacheConfig.enabled) {
|
|
386
|
+
try {
|
|
387
|
+
const openclawHome = process.env.OPENCLAW_HOME || join(homedir(), ".openclaw");
|
|
388
|
+
const resolvedAgentId = agentId || "main";
|
|
389
|
+
const storeDir = join(openclawHome, "memory");
|
|
366
390
|
mkdirSync(storeDir, { recursive: true });
|
|
367
|
-
|
|
368
|
-
|
|
391
|
+
const dbPath = join(storeDir, `${resolvedAgentId}.sqlite`);
|
|
392
|
+
localCacheConfig.dbPath = dbPath;
|
|
393
|
+
|
|
394
|
+
localCache = new LocalCache(dbPath, localCacheConfig);
|
|
395
|
+
syncDaemon = new SyncDaemon(localCache, client, localCacheConfig);
|
|
396
|
+
syncDaemon.start();
|
|
397
|
+
|
|
398
|
+
const vectorAvailable = localCacheConfig.vectorSearch.enabled;
|
|
399
|
+
memoryManager = new PluginMemoryManager(
|
|
400
|
+
localCache,
|
|
401
|
+
syncDaemon,
|
|
402
|
+
localCacheConfig,
|
|
403
|
+
vectorAvailable,
|
|
404
|
+
agentId || "main",
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
// Initial pull on startup (non-blocking)
|
|
408
|
+
syncDaemon.pull().catch((err) =>
|
|
409
|
+
api.logger.warn?.(`memory-memoryrelay: initial sync failed: ${String(err)}`),
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
api.logger.info?.(`memory-memoryrelay: local cache initialized at ${dbPath}`);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
api.logger.warn?.(`memory-memoryrelay: local cache init failed, falling back to API-only: ${String(err)}`);
|
|
415
|
+
localCache = null;
|
|
416
|
+
syncDaemon = null;
|
|
417
|
+
memoryManager = null;
|
|
369
418
|
}
|
|
370
|
-
} catch (err) {
|
|
371
|
-
api.logger.warn?.(`memory-memoryrelay: failed to create stub store file: ${String(err)}`);
|
|
372
419
|
}
|
|
373
420
|
|
|
374
421
|
// --- Tool enablement filter ---
|
|
@@ -398,8 +445,8 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
398
445
|
// ========================================================================
|
|
399
446
|
|
|
400
447
|
registerBeforeAgentStart(api, pluginConfig, isToolEnabled, defaultProject);
|
|
401
|
-
registerBeforePromptBuild(api, pluginConfig, client, sessionResolver);
|
|
402
|
-
registerAgentEnd(api, pluginConfig, client, sessionResolver);
|
|
448
|
+
registerBeforePromptBuild(api, pluginConfig, client, sessionResolver, localCache, syncDaemon);
|
|
449
|
+
registerAgentEnd(api, pluginConfig, client, sessionResolver, localCache, syncDaemon);
|
|
403
450
|
registerSessionLifecycle(api, pluginConfig, client, agentId, defaultProject, sessionResolver);
|
|
404
451
|
registerSubagentHooks(api, pluginConfig, client, agentId, autoCaptureConfig, isBlocklisted);
|
|
405
452
|
registerCompactionHooks(api, client, agentId, blocklist, extractRescueContent);
|
|
@@ -425,7 +472,7 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
425
472
|
// ========================================================================
|
|
426
473
|
|
|
427
474
|
api.logger.info?.(
|
|
428
|
-
`memory-memoryrelay: plugin v0.
|
|
475
|
+
`memory-memoryrelay: plugin v0.17.1 loaded (${Object.values(TOOL_GROUPS).flat().length} tools, autoRecall: ${pluginConfig.autoRecall}, autoCapture: ${autoCaptureConfig.enabled ? autoCaptureConfig.tier : "off"}, debug: ${debugEnabled})`,
|
|
429
476
|
);
|
|
430
477
|
|
|
431
478
|
// ========================================================================
|
|
@@ -467,6 +514,29 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
467
514
|
// `openclaw status` shows memory count, vector info, and provider details
|
|
468
515
|
// instead of "unavailable". OpenClaw 2026.3.28+ calls this for all memory plugins.
|
|
469
516
|
api.registerGatewayMethod?.("memory.probe", async ({ respond }) => {
|
|
517
|
+
// Use local PluginMemoryManager when available (v0.17.0+)
|
|
518
|
+
if (memoryManager) {
|
|
519
|
+
try {
|
|
520
|
+
const stats = memoryManager.cacheStats();
|
|
521
|
+
const daemonInfo = memoryManager.getSyncDaemonInfo();
|
|
522
|
+
respond(true, {
|
|
523
|
+
available: true,
|
|
524
|
+
provider: "memoryrelay",
|
|
525
|
+
memoryCount: stats.totalMemories,
|
|
526
|
+
tierBreakdown: stats.tierBreakdown,
|
|
527
|
+
bufferDepth: stats.bufferDepth,
|
|
528
|
+
syncActive: daemonInfo.running,
|
|
529
|
+
lastSync: stats.lastSync,
|
|
530
|
+
vector: { enabled: localCacheConfig.vectorSearch.enabled, dims: 768 },
|
|
531
|
+
fts: { enabled: true },
|
|
532
|
+
consecutiveErrors: daemonInfo.errors,
|
|
533
|
+
});
|
|
534
|
+
return;
|
|
535
|
+
} catch (err) {
|
|
536
|
+
api.logger.warn?.(`memory-memoryrelay: memory.probe local cache failed, falling back to API: ${String(err)}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
470
540
|
try {
|
|
471
541
|
const health = await client.health() as { status: string; embedding_info?: { dimension?: number } };
|
|
472
542
|
const healthStatus = String(health.status).toLowerCase();
|
|
@@ -509,6 +579,16 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
509
579
|
}
|
|
510
580
|
});
|
|
511
581
|
|
|
582
|
+
// getMemorySearchManager — exposes the PluginMemoryManager so OpenClaw's
|
|
583
|
+
// status scanner can call status(), probeVectorAvailability(), and close().
|
|
584
|
+
api.registerGatewayMethod?.("getMemorySearchManager", async ({ respond }) => {
|
|
585
|
+
if (memoryManager) {
|
|
586
|
+
respond(true, { manager: memoryManager });
|
|
587
|
+
} else {
|
|
588
|
+
respond(false, { error: "Local cache not initialized" });
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
512
592
|
api.registerGatewayMethod?.("memory.status", async ({ respond }) => {
|
|
513
593
|
try {
|
|
514
594
|
const startTime = Date.now();
|
|
@@ -833,7 +913,43 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
833
913
|
|
|
834
914
|
if (statusReporter) {
|
|
835
915
|
const report = statusReporter.buildReport(connectionStatus, reportConfig, { total_memories: memoryCount }, TOOL_GROUPS);
|
|
836
|
-
|
|
916
|
+
let text = StatusReporter.formatReport(report);
|
|
917
|
+
|
|
918
|
+
// Append local cache section when available
|
|
919
|
+
if (memoryManager) {
|
|
920
|
+
try {
|
|
921
|
+
const cacheStats = memoryManager.cacheStats();
|
|
922
|
+
const daemonInfo = memoryManager.getSyncDaemonInfo();
|
|
923
|
+
const cacheLines: string[] = [];
|
|
924
|
+
cacheLines.push("LOCAL CACHE");
|
|
925
|
+
const { hot, warm, cold } = cacheStats.tierBreakdown;
|
|
926
|
+
cacheLines.push(` Total: ${cacheStats.totalMemories} memories (hot: ${hot}, warm: ${warm}, cold: ${cold})`);
|
|
927
|
+
cacheLines.push(` Buffer: ${cacheStats.bufferDepth} entries pending sync`);
|
|
928
|
+
if (cacheStats.lastSync) {
|
|
929
|
+
const ago = StatusReporter.formatTimeAgo(new Date(cacheStats.lastSync));
|
|
930
|
+
cacheLines.push(` Last sync: ${ago}`);
|
|
931
|
+
} else {
|
|
932
|
+
cacheLines.push(" Last sync: never");
|
|
933
|
+
}
|
|
934
|
+
const vecLabel = localCacheConfig.vectorSearch.enabled ? "ready (sqlite-vec, 768 dims)" : "disabled";
|
|
935
|
+
cacheLines.push(` Vector: ${vecLabel}`);
|
|
936
|
+
cacheLines.push(" FTS: ready");
|
|
937
|
+
cacheLines.push("");
|
|
938
|
+
const daemonStatus = daemonInfo.running ? "running" : "stopped";
|
|
939
|
+
cacheLines.push(`SYNC DAEMON: ${daemonStatus}`);
|
|
940
|
+
cacheLines.push(` Interval: ${daemonInfo.intervalMinutes} minutes`);
|
|
941
|
+
cacheLines.push(` Errors: ${daemonInfo.errors} consecutive`);
|
|
942
|
+
if (daemonInfo.lastError) {
|
|
943
|
+
cacheLines.push(` Last error: ${daemonInfo.lastError}`);
|
|
944
|
+
}
|
|
945
|
+
cacheLines.push("");
|
|
946
|
+
text += cacheLines.join("\n");
|
|
947
|
+
} catch {
|
|
948
|
+
// cache stats unavailable — skip section
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return { text };
|
|
837
953
|
}
|
|
838
954
|
return { text: `MemoryRelay: ${isConnected ? "connected" : "disconnected"} | Endpoint: ${apiUrl} | Memories: ${memoryCount} | Agent: ${agentId}` };
|
|
839
955
|
} catch (err) {
|
|
@@ -1336,6 +1452,20 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
|
1336
1452
|
},
|
|
1337
1453
|
});
|
|
1338
1454
|
|
|
1455
|
+
// ========================================================================
|
|
1456
|
+
// Local Cache Cleanup Service
|
|
1457
|
+
// ========================================================================
|
|
1458
|
+
|
|
1459
|
+
if (localCache || syncDaemon) {
|
|
1460
|
+
api.registerService({
|
|
1461
|
+
id: "memoryrelay-cache-cleanup",
|
|
1462
|
+
stop: async () => {
|
|
1463
|
+
syncDaemon?.stop();
|
|
1464
|
+
localCache?.close();
|
|
1465
|
+
},
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1339
1469
|
// ========================================================================
|
|
1340
1470
|
// Stale Session Cleanup Service
|
|
1341
1471
|
// ========================================================================
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"id": "plugin-memoryrelay-ai",
|
|
3
3
|
"kind": "memory",
|
|
4
4
|
"name": "MemoryRelay AI",
|
|
5
|
-
"description": "MemoryRelay v0.
|
|
6
|
-
"version": "0.
|
|
5
|
+
"description": "MemoryRelay v0.17.1 - Long-term memory with pipeline architecture, 42 tools, 17 commands, V2 async, sessions, decisions, patterns & projects (api.memoryrelay.net)",
|
|
6
|
+
"version": "0.17.1",
|
|
7
7
|
"uiHints": {
|
|
8
8
|
"apiKey": {
|
|
9
9
|
"label": "MemoryRelay API Key",
|
|
@@ -225,6 +225,47 @@
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
},
|
|
228
|
+
"localCache": {
|
|
229
|
+
"type": "object",
|
|
230
|
+
"description": "Local SQLite cache for fast, offline-capable memory access",
|
|
231
|
+
"properties": {
|
|
232
|
+
"enabled": {
|
|
233
|
+
"type": "boolean",
|
|
234
|
+
"default": true,
|
|
235
|
+
"description": "Enable local SQLite cache layer"
|
|
236
|
+
},
|
|
237
|
+
"syncIntervalMinutes": {
|
|
238
|
+
"type": "number",
|
|
239
|
+
"default": 5,
|
|
240
|
+
"minimum": 1,
|
|
241
|
+
"maximum": 60,
|
|
242
|
+
"description": "How often to sync with the API (minutes)"
|
|
243
|
+
},
|
|
244
|
+
"maxLocalMemories": {
|
|
245
|
+
"type": "number",
|
|
246
|
+
"default": 1000,
|
|
247
|
+
"minimum": 100,
|
|
248
|
+
"maximum": 10000,
|
|
249
|
+
"description": "Maximum number of memories to keep locally"
|
|
250
|
+
},
|
|
251
|
+
"vectorSearch": {
|
|
252
|
+
"type": "object",
|
|
253
|
+
"properties": {
|
|
254
|
+
"enabled": { "type": "boolean", "default": true },
|
|
255
|
+
"provider": { "type": "string", "default": "sqlite-vec" }
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
"ttl": {
|
|
259
|
+
"type": "object",
|
|
260
|
+
"description": "Time-to-live per tier (hours)",
|
|
261
|
+
"properties": {
|
|
262
|
+
"hot": { "type": "number", "default": 72 },
|
|
263
|
+
"warm": { "type": "number", "default": 168 },
|
|
264
|
+
"cold": { "type": "number", "default": 720 }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
},
|
|
228
269
|
"ranking": {
|
|
229
270
|
"type": "object",
|
|
230
271
|
"properties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memoryrelay/plugin-memoryrelay-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "OpenClaw memory plugin for MemoryRelay API - 42 tools, 17 commands, V2 async, sessions, decisions, patterns, projects & semantic search",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"openclaw": ">=2026.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
36
37
|
"@types/node": "^25.3.5",
|
|
37
38
|
"@vitest/coverage-v8": "^1.2.0",
|
|
38
39
|
"typescript": "^5.9.3",
|
|
@@ -51,7 +52,16 @@
|
|
|
51
52
|
"src/",
|
|
52
53
|
"skills/"
|
|
53
54
|
],
|
|
55
|
+
"bundledDependencies": [
|
|
56
|
+
"better-sqlite3"
|
|
57
|
+
],
|
|
54
58
|
"engines": {
|
|
55
59
|
"node": ">=20.0.0"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"better-sqlite3": "^12.8.0"
|
|
63
|
+
},
|
|
64
|
+
"optionalDependencies": {
|
|
65
|
+
"sqlite-vec": "^0.1.0"
|
|
56
66
|
}
|
|
57
67
|
}
|