@memoryrelay/plugin-memoryrelay-ai 0.5.3 → 0.6.2

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/index.ts CHANGED
@@ -1,11 +1,19 @@
1
1
  /**
2
2
  * OpenClaw Memory Plugin - MemoryRelay
3
+ * Version: 0.6.0 (Enhanced)
3
4
  *
4
5
  * Long-term memory with vector search using MemoryRelay API.
5
6
  * Provides auto-recall and auto-capture via lifecycle hooks.
6
7
  *
7
8
  * API: https://api.memoryrelay.net
8
9
  * Docs: https://memoryrelay.io
10
+ *
11
+ * ENHANCEMENTS (v0.6.0):
12
+ * - Retry logic with exponential backoff (3 attempts)
13
+ * - Request timeout (30 seconds)
14
+ * - Environment variable fallback support
15
+ * - Channel filtering (excludeChannels config)
16
+ * - Additional CLI commands (stats, delete, export)
9
17
  */
10
18
 
11
19
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
@@ -16,19 +24,23 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
16
24
 
17
25
  const DEFAULT_API_URL = "https://api.memoryrelay.net";
18
26
  const VALID_HEALTH_STATUSES = ["ok", "healthy", "up"];
27
+ const REQUEST_TIMEOUT_MS = 30000; // 30 seconds
28
+ const MAX_RETRIES = 3;
29
+ const INITIAL_RETRY_DELAY_MS = 1000; // 1 second
19
30
 
20
31
  // ============================================================================
21
32
  // Types
22
33
  // ============================================================================
23
34
 
24
35
  interface MemoryRelayConfig {
25
- apiKey: string;
26
- agentId: string;
36
+ apiKey?: string; // Optional now (can use env var)
37
+ agentId?: string; // Optional now (can use env var)
27
38
  apiUrl?: string;
28
39
  autoCapture?: boolean;
29
40
  autoRecall?: boolean;
30
41
  recallLimit?: number;
31
42
  recallThreshold?: number;
43
+ excludeChannels?: string[]; // NEW: Channels to skip auto-recall
32
44
  }
33
45
 
34
46
  interface Memory {
@@ -47,8 +59,68 @@ interface SearchResult {
47
59
  score: number;
48
60
  }
49
61
 
62
+ interface Stats {
63
+ total_memories: number;
64
+ last_updated?: string;
65
+ }
66
+
50
67
  // ============================================================================
51
- // MemoryRelay API Client
68
+ // Utility Functions
69
+ // ============================================================================
70
+
71
+ /**
72
+ * Sleep for specified milliseconds
73
+ */
74
+ function sleep(ms: number): Promise<void> {
75
+ return new Promise((resolve) => setTimeout(resolve, ms));
76
+ }
77
+
78
+ /**
79
+ * Check if error is retryable (network/timeout errors)
80
+ */
81
+ function isRetryableError(error: unknown): boolean {
82
+ const errStr = String(error).toLowerCase();
83
+ return (
84
+ errStr.includes("timeout") ||
85
+ errStr.includes("econnrefused") ||
86
+ errStr.includes("enotfound") ||
87
+ errStr.includes("network") ||
88
+ errStr.includes("fetch failed") ||
89
+ errStr.includes("502") ||
90
+ errStr.includes("503") ||
91
+ errStr.includes("504")
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Fetch with timeout
97
+ */
98
+ async function fetchWithTimeout(
99
+ url: string,
100
+ options: RequestInit,
101
+ timeoutMs: number,
102
+ ): Promise<Response> {
103
+ const controller = new AbortController();
104
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
105
+
106
+ try {
107
+ const response = await fetch(url, {
108
+ ...options,
109
+ signal: controller.signal,
110
+ });
111
+ clearTimeout(timeout);
112
+ return response;
113
+ } catch (err) {
114
+ clearTimeout(timeout);
115
+ if ((err as Error).name === "AbortError") {
116
+ throw new Error("Request timeout");
117
+ }
118
+ throw err;
119
+ }
120
+ }
121
+
122
+ // ============================================================================
123
+ // MemoryRelay API Client (Enhanced)
52
124
  // ============================================================================
53
125
 
54
126
  class MemoryRelayClient {
@@ -58,32 +130,60 @@ class MemoryRelayClient {
58
130
  private readonly apiUrl: string = DEFAULT_API_URL,
59
131
  ) {}
60
132
 
133
+ /**
134
+ * Make HTTP request with retry logic and timeout
135
+ */
61
136
  private async request<T>(
62
137
  method: string,
63
138
  path: string,
64
139
  body?: unknown,
140
+ retryCount = 0,
65
141
  ): Promise<T> {
66
142
  const url = `${this.apiUrl}${path}`;
67
143
 
68
- const response = await fetch(url, {
69
- method,
70
- headers: {
71
- "Content-Type": "application/json",
72
- Authorization: `Bearer ${this.apiKey}`,
73
- "User-Agent": "openclaw-memory-memoryrelay/0.1.0",
74
- },
75
- body: body ? JSON.stringify(body) : undefined,
76
- });
77
-
78
- if (!response.ok) {
79
- const errorData = await response.json().catch(() => ({}));
80
- throw new Error(
81
- `MemoryRelay API error: ${response.status} ${response.statusText}` +
82
- (errorData.message ? ` - ${errorData.message}` : ""),
144
+ try {
145
+ const response = await fetchWithTimeout(
146
+ url,
147
+ {
148
+ method,
149
+ headers: {
150
+ "Content-Type": "application/json",
151
+ Authorization: `Bearer ${this.apiKey}`,
152
+ "User-Agent": "openclaw-memory-memoryrelay/0.6.0",
153
+ },
154
+ body: body ? JSON.stringify(body) : undefined,
155
+ },
156
+ REQUEST_TIMEOUT_MS,
83
157
  );
84
- }
85
158
 
86
- return response.json();
159
+ if (!response.ok) {
160
+ const errorData = await response.json().catch(() => ({}));
161
+ const error = new Error(
162
+ `MemoryRelay API error: ${response.status} ${response.statusText}` +
163
+ (errorData.message ? ` - ${errorData.message}` : ""),
164
+ );
165
+
166
+ // Retry on 5xx errors
167
+ if (response.status >= 500 && retryCount < MAX_RETRIES) {
168
+ const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
169
+ await sleep(delay);
170
+ return this.request<T>(method, path, body, retryCount + 1);
171
+ }
172
+
173
+ throw error;
174
+ }
175
+
176
+ return response.json();
177
+ } catch (err) {
178
+ // Retry on network errors
179
+ if (isRetryableError(err) && retryCount < MAX_RETRIES) {
180
+ const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
181
+ await sleep(delay);
182
+ return this.request<T>(method, path, body, retryCount + 1);
183
+ }
184
+
185
+ throw err;
186
+ }
87
187
  }
88
188
 
89
189
  async store(content: string, metadata?: Record<string, string>): Promise<Memory> {
@@ -132,8 +232,8 @@ class MemoryRelayClient {
132
232
  return this.request<{ status: string }>("GET", "/v1/health");
133
233
  }
134
234
 
135
- async stats(): Promise<{ total_memories: number; last_updated?: string }> {
136
- const response = await this.request<{ data: { total_memories: number; last_updated?: string } }>(
235
+ async stats(): Promise<Stats> {
236
+ const response = await this.request<{ data: Stats }>(
137
237
  "GET",
138
238
  `/v1/stats?agent_id=${encodeURIComponent(this.agentId)}`,
139
239
  );
@@ -142,6 +242,25 @@ class MemoryRelayClient {
142
242
  last_updated: response.data?.last_updated,
143
243
  };
144
244
  }
245
+
246
+ /**
247
+ * Export all memories as JSON
248
+ */
249
+ async export(): Promise<Memory[]> {
250
+ const allMemories: Memory[] = [];
251
+ let offset = 0;
252
+ const limit = 100;
253
+
254
+ while (true) {
255
+ const batch = await this.list(limit, offset);
256
+ if (batch.length === 0) break;
257
+ allMemories.push(...batch);
258
+ offset += limit;
259
+ if (batch.length < limit) break; // Last page
260
+ }
261
+
262
+ return allMemories;
263
+ }
145
264
  }
146
265
 
147
266
  // ============================================================================
@@ -171,59 +290,60 @@ function shouldCapture(text: string): boolean {
171
290
 
172
291
  export default async function plugin(api: OpenClawPluginApi): Promise<void> {
173
292
  const cfg = api.pluginConfig as MemoryRelayConfig | undefined;
174
-
175
- if (!cfg?.apiKey) {
293
+
294
+ // NEW: Fall back to environment variables
295
+ const apiKey = cfg?.apiKey || process.env.MEMORYRELAY_API_KEY;
296
+ const agentId = cfg?.agentId || process.env.MEMORYRELAY_AGENT_ID || api.agentName;
297
+
298
+ if (!apiKey) {
176
299
  api.logger.error(
177
- "memory-memoryrelay: Missing API key in config.\n\n" +
178
- "REQUIRED: Add config after installation:\n\n" +
179
- "cat ~/.openclaw/openclaw.json | jq '.plugins.entries.\"plugin-memoryrelay-ai\".config = {\n" +
180
- " \"apiKey\": \"YOUR_API_KEY\",\n" +
181
- " \"agentId\": \"YOUR_AGENT_ID\"\n" +
182
- "}' > /tmp/config.json && mv /tmp/config.json ~/.openclaw/openclaw.json\n\n" +
183
- "Then restart: openclaw gateway restart\n\n" +
184
- "Get your API key from: https://memoryrelay.ai"
300
+ "memory-memoryrelay: Missing API key in config or MEMORYRELAY_API_KEY env var.\n\n" +
301
+ "REQUIRED: Add config after installation:\n\n" +
302
+ 'cat ~/.openclaw/openclaw.json | jq \'.plugins.entries."plugin-memoryrelay-ai".config = {\n' +
303
+ ' "apiKey": "YOUR_API_KEY",\n' +
304
+ ' "agentId": "YOUR_AGENT_ID"\n' +
305
+ "}' > /tmp/config.json && mv /tmp/config.json ~/.openclaw/openclaw.json\n\n" +
306
+ "Or set environment variable:\n" +
307
+ 'export MEMORYRELAY_API_KEY="mem_prod_..."\n\n' +
308
+ "Then restart: openclaw gateway restart\n\n" +
309
+ "Get your API key from: https://memoryrelay.ai",
185
310
  );
186
311
  return;
187
312
  }
188
-
189
- if (!cfg.agentId) {
190
- api.logger.error("memory-memoryrelay: Missing agentId in config");
313
+
314
+ if (!agentId) {
315
+ api.logger.error("memory-memoryrelay: Missing agentId in config or MEMORYRELAY_AGENT_ID env var");
191
316
  return;
192
317
  }
193
-
194
- const apiUrl = cfg.apiUrl || DEFAULT_API_URL;
195
- const client = new MemoryRelayClient(cfg.apiKey, cfg.agentId, apiUrl);
196
318
 
197
- // Verify connection on startup
319
+ const apiUrl = cfg?.apiUrl || process.env.MEMORYRELAY_API_URL || DEFAULT_API_URL;
320
+ const client = new MemoryRelayClient(apiKey, agentId, apiUrl);
321
+
322
+ // Verify connection on startup (with timeout)
198
323
  try {
199
324
  await client.health();
200
325
  api.logger.info(`memory-memoryrelay: connected to ${apiUrl}`);
201
326
  } catch (err) {
202
327
  api.logger.error(`memory-memoryrelay: health check failed: ${String(err)}`);
203
- return;
328
+ // Continue loading plugin even if health check fails (will retry on first use)
204
329
  }
205
330
 
206
331
  // ========================================================================
207
332
  // Status Reporting (for openclaw status command)
208
333
  // ========================================================================
209
334
 
210
- // Register gateway RPC method for status probing
211
- // This allows OpenClaw's status command to query plugin availability
212
335
  api.registerGatewayMethod?.("memory.status", async ({ respond }) => {
213
336
  try {
214
337
  const health = await client.health();
215
338
  let memoryCount = 0;
216
339
 
217
- // Try to get stats if the endpoint exists
218
340
  try {
219
341
  const stats = await client.stats();
220
342
  memoryCount = stats.total_memories;
221
343
  } catch (statsErr) {
222
- // Stats endpoint may not exist yet - that's okay, just report 0
223
344
  api.logger.debug?.(`memory-memoryrelay: stats endpoint unavailable: ${String(statsErr)}`);
224
345
  }
225
346
 
226
- // Consider API connected if health check succeeds with any recognized status
227
347
  const healthStatus = String(health.status).toLowerCase();
228
348
  const isConnected = VALID_HEALTH_STATUSES.includes(healthStatus);
229
349
 
@@ -233,7 +353,6 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
233
353
  endpoint: apiUrl,
234
354
  memoryCount: memoryCount,
235
355
  agentId: agentId,
236
- // OpenClaw checks status.vector.available for memory plugins
237
356
  vector: {
238
357
  available: true,
239
358
  enabled: true,
@@ -246,7 +365,6 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
246
365
  error: String(err),
247
366
  endpoint: apiUrl,
248
367
  agentId: agentId,
249
- // Report vector as unavailable when API fails
250
368
  vector: {
251
369
  available: false,
252
370
  enabled: true,
@@ -328,7 +446,7 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
328
446
  },
329
447
  execute: async (_id, { query, limit = 5 }: { query: string; limit?: number }) => {
330
448
  try {
331
- const results = await client.search(query, limit, cfg.recallThreshold || 0.3);
449
+ const results = await client.search(query, limit, cfg?.recallThreshold || 0.3);
332
450
 
333
451
  if (results.length === 0) {
334
452
  return {
@@ -453,7 +571,7 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
453
571
  );
454
572
 
455
573
  // ========================================================================
456
- // CLI Commands
574
+ // CLI Commands (Enhanced)
457
575
  // ========================================================================
458
576
 
459
577
  api.registerCli(
@@ -466,24 +584,48 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
466
584
  .action(async () => {
467
585
  try {
468
586
  const health = await client.health();
587
+ const stats = await client.stats();
469
588
  console.log(`Status: ${health.status}`);
470
589
  console.log(`Agent ID: ${agentId}`);
471
590
  console.log(`API: ${apiUrl}`);
591
+ console.log(`Total Memories: ${stats.total_memories}`);
592
+ if (stats.last_updated) {
593
+ console.log(`Last Updated: ${new Date(stats.last_updated).toLocaleString()}`);
594
+ }
472
595
  } catch (err) {
473
596
  console.error(`Connection failed: ${String(err)}`);
474
597
  }
475
598
  });
476
599
 
600
+ mem
601
+ .command("stats")
602
+ .description("Show agent statistics")
603
+ .action(async () => {
604
+ try {
605
+ const stats = await client.stats();
606
+ console.log(`Total Memories: ${stats.total_memories}`);
607
+ if (stats.last_updated) {
608
+ console.log(`Last Updated: ${new Date(stats.last_updated).toLocaleString()}`);
609
+ }
610
+ } catch (err) {
611
+ console.error(`Failed to fetch stats: ${String(err)}`);
612
+ }
613
+ });
614
+
477
615
  mem
478
616
  .command("list")
479
617
  .description("List recent memories")
480
618
  .option("--limit <n>", "Max results", "10")
481
619
  .action(async (opts) => {
482
- const memories = await client.list(parseInt(opts.limit));
483
- for (const m of memories) {
484
- console.log(`[${m.id.slice(0, 8)}] ${m.content.slice(0, 80)}...`);
620
+ try {
621
+ const memories = await client.list(parseInt(opts.limit));
622
+ for (const m of memories) {
623
+ console.log(`[${m.id.slice(0, 8)}] ${m.content.slice(0, 80)}...`);
624
+ }
625
+ console.log(`\nTotal: ${memories.length} memories`);
626
+ } catch (err) {
627
+ console.error(`Failed to list memories: ${String(err)}`);
485
628
  }
486
- console.log(`\nTotal: ${memories.length} memories`);
487
629
  });
488
630
 
489
631
  mem
@@ -492,9 +634,42 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
492
634
  .argument("<query>", "Search query")
493
635
  .option("--limit <n>", "Max results", "5")
494
636
  .action(async (query, opts) => {
495
- const results = await client.search(query, parseInt(opts.limit));
496
- for (const r of results) {
497
- console.log(`[${r.score.toFixed(2)}] ${r.memory.content.slice(0, 80)}...`);
637
+ try {
638
+ const results = await client.search(query, parseInt(opts.limit));
639
+ for (const r of results) {
640
+ console.log(`[${r.score.toFixed(2)}] ${r.memory.content.slice(0, 80)}...`);
641
+ }
642
+ } catch (err) {
643
+ console.error(`Search failed: ${String(err)}`);
644
+ }
645
+ });
646
+
647
+ mem
648
+ .command("delete")
649
+ .description("Delete a memory by ID")
650
+ .argument("<id>", "Memory ID")
651
+ .action(async (id) => {
652
+ try {
653
+ await client.delete(id);
654
+ console.log(`Memory ${id.slice(0, 8)}... deleted.`);
655
+ } catch (err) {
656
+ console.error(`Delete failed: ${String(err)}`);
657
+ }
658
+ });
659
+
660
+ mem
661
+ .command("export")
662
+ .description("Export all memories to JSON file")
663
+ .option("--output <path>", "Output file path", "memories-export.json")
664
+ .action(async (opts) => {
665
+ try {
666
+ console.log("Exporting memories...");
667
+ const memories = await client.export();
668
+ const fs = await import("fs/promises");
669
+ await fs.writeFile(opts.output, JSON.stringify(memories, null, 2));
670
+ console.log(`Exported ${memories.length} memories to ${opts.output}`);
671
+ } catch (err) {
672
+ console.error(`Export failed: ${String(err)}`);
498
673
  }
499
674
  });
500
675
  },
@@ -506,12 +681,23 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
506
681
  // ========================================================================
507
682
 
508
683
  // Auto-recall: inject relevant memories before agent starts
509
- if (cfg.autoRecall) {
684
+ if (cfg?.autoRecall) {
510
685
  api.on("before_agent_start", async (event) => {
511
686
  if (!event.prompt || event.prompt.length < 10) {
512
687
  return;
513
688
  }
514
689
 
690
+ // NEW: Check if current channel is excluded
691
+ if (cfg.excludeChannels && event.channel) {
692
+ const channelId = String(event.channel);
693
+ if (cfg.excludeChannels.some((excluded) => channelId.includes(excluded))) {
694
+ api.logger.debug?.(
695
+ `memory-memoryrelay: skipping auto-recall for excluded channel: ${channelId}`,
696
+ );
697
+ return;
698
+ }
699
+ }
700
+
515
701
  try {
516
702
  const results = await client.search(
517
703
  event.prompt,
@@ -523,9 +709,7 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
523
709
  return;
524
710
  }
525
711
 
526
- const memoryContext = results
527
- .map((r) => `- ${r.memory.content}`)
528
- .join("\n");
712
+ const memoryContext = results.map((r) => `- ${r.memory.content}`).join("\n");
529
713
 
530
714
  api.logger.info?.(
531
715
  `memory-memoryrelay: injecting ${results.length} memories into context`,
@@ -541,7 +725,7 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
541
725
  }
542
726
 
543
727
  // Auto-capture: analyze and store important information after agent ends
544
- if (cfg.autoCapture) {
728
+ if (cfg?.autoCapture) {
545
729
  api.on("agent_end", async (event) => {
546
730
  if (!event.success || !event.messages || event.messages.length === 0) {
547
731
  return;
@@ -596,6 +780,6 @@ export default async function plugin(api: OpenClawPluginApi): Promise<void> {
596
780
  }
597
781
 
598
782
  api.logger.info?.(
599
- `memory-memoryrelay: plugin loaded (autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture})`,
783
+ `memory-memoryrelay: plugin loaded (autoRecall: ${cfg?.autoRecall}, autoCapture: ${cfg?.autoCapture})`,
600
784
  );
601
785
  }
@@ -3,24 +3,24 @@
3
3
  "kind": "memory",
4
4
  "name": "MemoryRelay AI",
5
5
  "description": "AI memory service using MemoryRelay API (api.memoryrelay.net)",
6
- "version": "0.5.3",
6
+ "version": "0.6.2",
7
7
  "uiHints": {
8
8
  "apiKey": {
9
9
  "label": "MemoryRelay API Key",
10
10
  "sensitive": true,
11
11
  "placeholder": "mem_prod_...",
12
- "help": "API key from memoryrelay.io (or use ${MEMORYRELAY_API_KEY})"
12
+ "help": "API key from memoryrelay.io (or use ${MEMORYRELAY_API_KEY} env var)"
13
13
  },
14
14
  "agentId": {
15
15
  "label": "Agent ID",
16
16
  "placeholder": "iris",
17
- "help": "Unique identifier for this agent in MemoryRelay"
17
+ "help": "Unique identifier for this agent in MemoryRelay (or use ${MEMORYRELAY_AGENT_ID} env var)"
18
18
  },
19
19
  "apiUrl": {
20
20
  "label": "API URL",
21
21
  "placeholder": "https://api.memoryrelay.net",
22
22
  "advanced": true,
23
- "help": "MemoryRelay API endpoint"
23
+ "help": "MemoryRelay API endpoint (or use ${MEMORYRELAY_API_URL} env var)"
24
24
  },
25
25
  "autoCapture": {
26
26
  "label": "Auto-Capture",
@@ -29,6 +29,11 @@
29
29
  "autoRecall": {
30
30
  "label": "Auto-Recall",
31
31
  "help": "Automatically inject relevant memories into context"
32
+ },
33
+ "excludeChannels": {
34
+ "label": "Exclude Channels",
35
+ "help": "List of channel IDs to skip auto-recall (e.g., ['whatsapp:group_123', 'telegram:456'])",
36
+ "advanced": true
32
37
  }
33
38
  },
34
39
  "configSchema": {
@@ -38,12 +43,12 @@
38
43
  "apiKey": {
39
44
  "type": "string",
40
45
  "minLength": 1,
41
- "description": "MemoryRelay API key (required, get from https://memoryrelay.ai)"
46
+ "description": "MemoryRelay API key (optional, can use MEMORYRELAY_API_KEY env var)"
42
47
  },
43
48
  "agentId": {
44
49
  "type": "string",
45
50
  "minLength": 1,
46
- "description": "Unique agent identifier (required)"
51
+ "description": "Unique agent identifier (optional, can use MEMORYRELAY_AGENT_ID env var or agent name)"
47
52
  },
48
53
  "apiUrl": {
49
54
  "type": "string",
@@ -59,11 +64,23 @@
59
64
  },
60
65
  "recallLimit": {
61
66
  "type": "number",
62
- "default": 5
67
+ "default": 5,
68
+ "minimum": 1,
69
+ "maximum": 20
63
70
  },
64
71
  "recallThreshold": {
65
72
  "type": "number",
66
- "default": 0.3
73
+ "default": 0.3,
74
+ "minimum": 0,
75
+ "maximum": 1
76
+ },
77
+ "excludeChannels": {
78
+ "type": "array",
79
+ "items": {
80
+ "type": "string"
81
+ },
82
+ "default": [],
83
+ "description": "List of channel IDs to exclude from auto-recall"
67
84
  }
68
85
  },
69
86
  "required": []
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@memoryrelay/plugin-memoryrelay-ai",
3
- "version": "0.5.3",
4
- "description": "OpenClaw memory plugin for MemoryRelay API - long-term memory with semantic search",
3
+ "version": "0.6.2",
4
+ "description": "OpenClaw memory plugin for MemoryRelay API - long-term memory with semantic search (ENHANCED)",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "scripts": {
8
- "postinstall": "node -e \"console.log('\\n⚠️ CONFIGURATION REQUIRED\\n\\nAdd config after installation:\\n\\ncat ~/.openclaw/openclaw.json | jq \\'.plugins.entries.\\\"plugin-memoryrelay-ai\\\".config = {\\\"apiKey\\\": \\\"YOUR_KEY\\\", \\\"agentId\\\": \\\"YOUR_AGENT\\\"}\\' > /tmp/config.json && mv /tmp/config.json ~/.openclaw/openclaw.json\\n\\nThen restart: openclaw gateway restart\\n\\nGet your API key from: https://memoryrelay.ai\\n')\""
8
+ "test": "vitest run",
9
+ "test:watch": "vitest",
10
+ "test:coverage": "vitest run --coverage",
11
+ "postinstall": "node -e \"console.log('\\n⚠️ CONFIGURATION REQUIRED\\n\\nAdd config after installation:\\n\\ncat ~/.openclaw/openclaw.json | jq \\'.plugins.entries.\\\"plugin-memoryrelay-ai\\\".config = {\\\"apiKey\\\": \\\"YOUR_KEY\\\", \\\"agentId\\\": \\\"YOUR_AGENT\\\"}\\' > /tmp/config.json && mv /tmp/config.json ~/.openclaw/openclaw.json\\n\\nOr use environment variables:\\nexport MEMORYRELAY_API_KEY=\\\"mem_prod_...\\\"\\nexport MEMORYRELAY_AGENT_ID=\\\"your-agent\\\"\\n\\nThen restart: openclaw gateway restart\\n\\nGet your API key from: https://memoryrelay.ai\\n')\""
9
12
  },
10
13
  "keywords": [
11
14
  "openclaw",
@@ -30,10 +33,14 @@
30
33
  "peerDependencies": {
31
34
  "openclaw": ">=2026.2.0"
32
35
  },
36
+ "devDependencies": {
37
+ "vitest": "^1.2.0",
38
+ "@vitest/coverage-v8": "^1.2.0"
39
+ },
33
40
  "openclaw": {
34
41
  "id": "plugin-memoryrelay-ai",
35
42
  "extensions": [
36
- "./"
43
+ "./index.ts"
37
44
  ]
38
45
  },
39
46
  "files": [