@memoryrelay/plugin-memoryrelay-ai 0.2.0
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/LICENSE +21 -0
- package/README.md +465 -0
- package/index.ts +519 -0
- package/openclaw.plugin.json +67 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alteriom
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# OpenClaw Plugin for MemoryRelay AI
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@memoryrelay/plugin-memoryrelay-ai)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
Long-term memory plugin for OpenClaw agents using [MemoryRelay API](https://api.memoryrelay.net).
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🧠 **Semantic Search** — Natural language memory retrieval with vector embeddings
|
|
11
|
+
- 🔄 **Auto-Recall** — Automatically inject relevant memories into agent context
|
|
12
|
+
- 📝 **Auto-Capture** — Intelligently detect and store important information
|
|
13
|
+
- 🤖 **Multi-Agent** — Isolated memory namespaces per agent
|
|
14
|
+
- 🛠️ **CLI Tools** — Manage memories via `openclaw memoryrelay` commands
|
|
15
|
+
- 🔌 **Tool Integration** — Three memory tools for AI agents
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
openclaw plugins install @memoryrelay/plugin-memoryrelay-ai
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or via npm:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @memoryrelay/plugin-memoryrelay-ai
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Get API Key
|
|
32
|
+
|
|
33
|
+
Sign up at [memoryrelay.io](https://memoryrelay.io) or use the public demo API.
|
|
34
|
+
|
|
35
|
+
### 2. Configure
|
|
36
|
+
|
|
37
|
+
Add to your `~/.openclaw/openclaw.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"plugins": {
|
|
42
|
+
"slots": {
|
|
43
|
+
"memory": "plugin-memoryrelay-ai"
|
|
44
|
+
},
|
|
45
|
+
"entries": {
|
|
46
|
+
"plugin-memoryrelay-ai": {
|
|
47
|
+
"enabled": true,
|
|
48
|
+
"config": {
|
|
49
|
+
"apiKey": "mem_prod_...",
|
|
50
|
+
"agentId": "my-agent",
|
|
51
|
+
"apiUrl": "https://api.memoryrelay.net",
|
|
52
|
+
"autoRecall": true,
|
|
53
|
+
"autoCapture": false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. Restart Gateway
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
openclaw gateway restart
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 4. Test Connection
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
openclaw memoryrelay status
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
### AI Agent Tools
|
|
76
|
+
|
|
77
|
+
The plugin provides three tools your AI agent can use:
|
|
78
|
+
|
|
79
|
+
#### `memory_store`
|
|
80
|
+
|
|
81
|
+
Store a new memory with optional metadata.
|
|
82
|
+
|
|
83
|
+
> **Note**: The `agent_id` parameter is automatically injected from your config. You don't need to include it.
|
|
84
|
+
|
|
85
|
+
**Parameters:**
|
|
86
|
+
- `content` (string, required) - Memory content (1-50,000 characters)
|
|
87
|
+
- `metadata` (object, optional) - Key-value metadata (max 10KB when serialized)
|
|
88
|
+
|
|
89
|
+
**Example:**
|
|
90
|
+
```typescript
|
|
91
|
+
memory_store({
|
|
92
|
+
content: "User prefers concise bullet-point responses",
|
|
93
|
+
metadata: { category: "preferences", importance: "high" }
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Returns:** Memory object with `id`, `content`, `agent_id`, `metadata`, `created_at`, `updated_at`
|
|
98
|
+
|
|
99
|
+
**Rate Limit**: 30 requests per minute
|
|
100
|
+
|
|
101
|
+
#### `memory_recall`
|
|
102
|
+
|
|
103
|
+
Search memories using semantic similarity.
|
|
104
|
+
|
|
105
|
+
**Parameters:**
|
|
106
|
+
- `query` (string, required) - Natural language search query
|
|
107
|
+
- `limit` (number, optional, default: 10) - Maximum results (1-50)
|
|
108
|
+
- `threshold` (number, optional, default: 0.5) - Minimum similarity score (0-1)
|
|
109
|
+
|
|
110
|
+
**Example:**
|
|
111
|
+
```typescript
|
|
112
|
+
memory_recall({
|
|
113
|
+
query: "user communication preferences",
|
|
114
|
+
limit: 5,
|
|
115
|
+
threshold: 0.7
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Returns:** Array of search results with `memory` object and `score` (0-1):
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"results": [
|
|
123
|
+
{
|
|
124
|
+
"memory": {
|
|
125
|
+
"id": "550e8400-...",
|
|
126
|
+
"content": "User prefers concise bullet-point responses",
|
|
127
|
+
"metadata": { "category": "preferences" },
|
|
128
|
+
"created_at": 1707649200
|
|
129
|
+
},
|
|
130
|
+
"score": 0.89
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `memory_forget`
|
|
137
|
+
|
|
138
|
+
Delete a memory by ID or search query.
|
|
139
|
+
|
|
140
|
+
**Parameters:**
|
|
141
|
+
- `memoryId` (string, optional) - Memory UUID to delete
|
|
142
|
+
- `query` (string, optional) - Search query (shows candidates if multiple matches)
|
|
143
|
+
|
|
144
|
+
**Examples:**
|
|
145
|
+
```typescript
|
|
146
|
+
// By ID
|
|
147
|
+
memory_forget({ memoryId: "550e8400-..." })
|
|
148
|
+
|
|
149
|
+
// By query (interactive if multiple matches)
|
|
150
|
+
memory_forget({ query: "outdated preference" })
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Returns:** Success confirmation
|
|
154
|
+
|
|
155
|
+
### CLI Commands
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Check status
|
|
159
|
+
openclaw memoryrelay status
|
|
160
|
+
|
|
161
|
+
# List recent memories
|
|
162
|
+
openclaw memoryrelay list --limit 10
|
|
163
|
+
|
|
164
|
+
# Search memories
|
|
165
|
+
openclaw memoryrelay search "API configuration"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Auto-Recall
|
|
169
|
+
|
|
170
|
+
When `autoRecall: true`, relevant memories are automatically injected before each agent turn:
|
|
171
|
+
|
|
172
|
+
```xml
|
|
173
|
+
<relevant-memories>
|
|
174
|
+
The following memories from MemoryRelay may be relevant:
|
|
175
|
+
- User prefers concise responses
|
|
176
|
+
- Project uses TypeScript with strict mode
|
|
177
|
+
- ...
|
|
178
|
+
</relevant-memories>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Config:**
|
|
182
|
+
- `recallLimit`: Max memories (default: 5)
|
|
183
|
+
- `recallThreshold`: Min similarity score (default: 0.3)
|
|
184
|
+
|
|
185
|
+
### Auto-Capture
|
|
186
|
+
|
|
187
|
+
When `autoCapture: true`, the plugin detects and stores important information automatically.
|
|
188
|
+
|
|
189
|
+
**Patterns detected:**
|
|
190
|
+
- "remember that..."
|
|
191
|
+
- "my name/email/phone is..."
|
|
192
|
+
- "important: ..."
|
|
193
|
+
- API keys, SSH configs, preferences
|
|
194
|
+
|
|
195
|
+
**Note:** Disabled by default for privacy.
|
|
196
|
+
|
|
197
|
+
## Configuration
|
|
198
|
+
|
|
199
|
+
| Field | Type | Required | Default | Description |
|
|
200
|
+
|-------|------|----------|---------|-------------|
|
|
201
|
+
| `apiKey` | string | ✅ | - | MemoryRelay API key |
|
|
202
|
+
| `agentId` | string | ✅ | - | Unique agent identifier |
|
|
203
|
+
| `apiUrl` | string | No | `api.memoryrelay.net` | API endpoint |
|
|
204
|
+
| `autoRecall` | boolean | No | `true` | Auto-inject memories |
|
|
205
|
+
| `autoCapture` | boolean | No | `false` | Auto-store information |
|
|
206
|
+
| `recallLimit` | number | No | `5` | Max memories to inject |
|
|
207
|
+
| `recallThreshold` | number | No | `0.3` | Similarity threshold (0-1) |
|
|
208
|
+
|
|
209
|
+
### Environment Variables
|
|
210
|
+
|
|
211
|
+
Alternatively, use environment variables:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
export MEMORYRELAY_API_KEY="mem_prod_..."
|
|
215
|
+
export MEMORYRELAY_AGENT_ID="my-agent"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Then reference in config:
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"apiKey": "${MEMORYRELAY_API_KEY}",
|
|
222
|
+
"agentId": "${MEMORYRELAY_AGENT_ID}"
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Architecture
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
┌─────────────────────┐
|
|
230
|
+
│ OpenClaw Agent │
|
|
231
|
+
│ (Your AI) │
|
|
232
|
+
└──────────┬──────────┘
|
|
233
|
+
│
|
|
234
|
+
│ Plugin API
|
|
235
|
+
↓
|
|
236
|
+
┌─────────────────────┐
|
|
237
|
+
│ @memoryrelay/ │
|
|
238
|
+
│ openclaw-plugin │
|
|
239
|
+
│ - Tools │
|
|
240
|
+
│ - CLI │
|
|
241
|
+
│ - Lifecycle Hooks │
|
|
242
|
+
└──────────┬──────────┘
|
|
243
|
+
│
|
|
244
|
+
│ HTTPS REST
|
|
245
|
+
↓
|
|
246
|
+
┌─────────────────────┐
|
|
247
|
+
│ MemoryRelay API │
|
|
248
|
+
│ api.memoryrelay.net │
|
|
249
|
+
└─────────────────────┘
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## API
|
|
253
|
+
|
|
254
|
+
The plugin includes a TypeScript client for MemoryRelay API:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
class MemoryRelayClient {
|
|
258
|
+
async store(content: string, metadata?: Record<string, string>): Promise<Memory>
|
|
259
|
+
async search(query: string, limit?: number, threshold?: number): Promise<SearchResult[]>
|
|
260
|
+
async list(limit?: number, offset?: number): Promise<Memory[]>
|
|
261
|
+
async get(id: string): Promise<Memory>
|
|
262
|
+
async delete(id: string): Promise<void>
|
|
263
|
+
async health(): Promise<{ status: string }>
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Examples
|
|
268
|
+
|
|
269
|
+
### Basic Usage
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
// Agent conversation:
|
|
273
|
+
// User: "Remember that I prefer TypeScript over JavaScript"
|
|
274
|
+
// Agent uses: memory_store({ content: "User prefers TypeScript over JavaScript" })
|
|
275
|
+
|
|
276
|
+
// Later:
|
|
277
|
+
// User: "What language should we use?"
|
|
278
|
+
// Agent uses: memory_recall({ query: "programming language preference" })
|
|
279
|
+
// → Finds previous preference and suggests TypeScript
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### CLI Workflow
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# Store memory
|
|
286
|
+
openclaw memoryrelay store "Project uses Kubernetes on AWS EKS"
|
|
287
|
+
|
|
288
|
+
# Search later
|
|
289
|
+
openclaw memoryrelay search "kubernetes setup"
|
|
290
|
+
# → Returns relevant infrastructure memories
|
|
291
|
+
|
|
292
|
+
# List all
|
|
293
|
+
openclaw memoryrelay list --limit 20
|
|
294
|
+
|
|
295
|
+
# Delete old memory
|
|
296
|
+
openclaw memoryrelay forget --id abc123
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Troubleshooting
|
|
300
|
+
|
|
301
|
+
### Plugin Not Loading
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
# Check plugin status
|
|
305
|
+
openclaw plugins list | grep memoryrelay
|
|
306
|
+
|
|
307
|
+
# View config validation
|
|
308
|
+
openclaw doctor
|
|
309
|
+
|
|
310
|
+
# Check logs
|
|
311
|
+
journalctl -u openclaw-gateway -f
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Connection Failed
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# Test API directly
|
|
318
|
+
curl https://api.memoryrelay.net/v1/health
|
|
319
|
+
|
|
320
|
+
# Check API key
|
|
321
|
+
openclaw memoryrelay status
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### No Memories Returned
|
|
325
|
+
|
|
326
|
+
- Check `recallThreshold` (lower = more results)
|
|
327
|
+
- Verify `agentId` matches your API agent
|
|
328
|
+
- Try broader search queries
|
|
329
|
+
|
|
330
|
+
## Security
|
|
331
|
+
|
|
332
|
+
- API keys stored in `openclaw.json` (not committed to git)
|
|
333
|
+
- Supports environment variable substitution
|
|
334
|
+
- Auto-capture disabled by default (privacy)
|
|
335
|
+
- No hardcoded credentials
|
|
336
|
+
|
|
337
|
+
**Best Practices:**
|
|
338
|
+
- Use environment variables in production
|
|
339
|
+
- Never commit `openclaw.json` with real keys
|
|
340
|
+
- Rotate API keys regularly
|
|
341
|
+
- Review auto-captured memories periodically
|
|
342
|
+
|
|
343
|
+
## Development
|
|
344
|
+
|
|
345
|
+
### File Structure
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
openclaw-plugin/
|
|
349
|
+
├── index.ts # Plugin implementation
|
|
350
|
+
├── openclaw.plugin.json # Plugin manifest
|
|
351
|
+
├── package.json # NPM metadata
|
|
352
|
+
├── LICENSE # MIT license
|
|
353
|
+
└── README.md # This file
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Testing
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
# Install locally
|
|
360
|
+
openclaw plugins install --link .
|
|
361
|
+
|
|
362
|
+
# Test tools
|
|
363
|
+
# (via agent conversation or CLI)
|
|
364
|
+
|
|
365
|
+
# View logs
|
|
366
|
+
tail -f ~/.openclaw/logs/gateway.log
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Related Projects
|
|
370
|
+
|
|
371
|
+
- **MemoryRelay API** — REST API backend (FastAPI + PostgreSQL)
|
|
372
|
+
- **MCP Server** — [`memoryrelay-mcp-server`](https://www.npmjs.com/package/memoryrelay-mcp-server) for Claude Desktop
|
|
373
|
+
- **Python SDK** — `memoryrelay` on PyPI (coming soon)
|
|
374
|
+
|
|
375
|
+
## Contributing
|
|
376
|
+
|
|
377
|
+
Contributions welcome! Please:
|
|
378
|
+
|
|
379
|
+
1. Fork the repository
|
|
380
|
+
2. Create a feature branch
|
|
381
|
+
3. Add tests (if applicable)
|
|
382
|
+
4. Update documentation
|
|
383
|
+
5. Submit a pull request
|
|
384
|
+
|
|
385
|
+
## Support
|
|
386
|
+
|
|
387
|
+
- **Issues**: [GitHub Issues](https://github.com/memoryrelay/openclaw-plugin/issues)
|
|
388
|
+
- **Docs**: [memoryrelay.io](https://memoryrelay.io)
|
|
389
|
+
- **Discord**: [OpenClaw Community](https://discord.gg/clawd)
|
|
390
|
+
|
|
391
|
+
## License
|
|
392
|
+
|
|
393
|
+
MIT © 2026 MemoryRelay
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Changelog
|
|
398
|
+
|
|
399
|
+
### v0.2.0 (2026-02-13) - BREAKING CHANGE
|
|
400
|
+
|
|
401
|
+
**Package Renamed to Fix Warnings:**
|
|
402
|
+
- Old: `@memoryrelay/openclaw-plugin`
|
|
403
|
+
- New: `@memoryrelay/plugin-memoryrelay-ai`
|
|
404
|
+
- Plugin ID remains: `plugin-memoryrelay-ai`
|
|
405
|
+
- **Why**: Package name must match plugin ID to avoid config warnings
|
|
406
|
+
- **Impact**: No code changes, just package name alignment
|
|
407
|
+
|
|
408
|
+
**Migration**:
|
|
409
|
+
```bash
|
|
410
|
+
# Uninstall old package
|
|
411
|
+
openclaw plugins uninstall @memoryrelay/openclaw-plugin
|
|
412
|
+
# Install new package
|
|
413
|
+
openclaw plugins install @memoryrelay/plugin-memoryrelay-ai
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### v0.1.2 (2026-02-13)
|
|
417
|
+
|
|
418
|
+
**Bug Fix:**
|
|
419
|
+
- Fixed installation directory mismatch by adding `openclaw.id` to package.json
|
|
420
|
+
- Plugin now correctly installs to `plugin-memoryrelay-ai` directory
|
|
421
|
+
- Resolves "plugin not found" error during `openclaw plugins install`
|
|
422
|
+
|
|
423
|
+
### v0.1.1 (2026-02-13)
|
|
424
|
+
|
|
425
|
+
**Breaking Changes:**
|
|
426
|
+
- Plugin ID changed: `memory-memoryrelay` → `plugin-memoryrelay-ai`
|
|
427
|
+
- Update your `openclaw.json` to use new ID in `plugins.slots.memory` and `plugins.entries`
|
|
428
|
+
|
|
429
|
+
**Documentation Improvements:**
|
|
430
|
+
- ✅ Added agent_id auto-injection documentation
|
|
431
|
+
- ✅ Added size limits (content 1-50K chars, metadata 10KB)
|
|
432
|
+
- ✅ Added rate limit info (30 req/min)
|
|
433
|
+
- ✅ Enhanced tool documentation with return formats
|
|
434
|
+
- ✅ Added response format examples for memory_recall
|
|
435
|
+
|
|
436
|
+
**Migration Guide:**
|
|
437
|
+
```json
|
|
438
|
+
{
|
|
439
|
+
"plugins": {
|
|
440
|
+
"slots": {
|
|
441
|
+
"memory": "plugin-memoryrelay-ai" // Changed
|
|
442
|
+
},
|
|
443
|
+
"entries": {
|
|
444
|
+
"plugin-memoryrelay-ai": { // Changed
|
|
445
|
+
"enabled": true,
|
|
446
|
+
"config": { ... }
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### v0.1.0 (2026-02-12)
|
|
454
|
+
|
|
455
|
+
- Initial release
|
|
456
|
+
- Three tools: memory_store, memory_recall, memory_forget
|
|
457
|
+
- Auto-recall and auto-capture features
|
|
458
|
+
- CLI commands
|
|
459
|
+
- Production deployment on 3 agents
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
**Homepage**: https://memoryrelay.io
|
|
464
|
+
**API**: https://api.memoryrelay.net
|
|
465
|
+
**Source**: https://github.com/memoryrelay/openclaw-plugin
|
package/index.ts
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Memory Plugin - MemoryRelay
|
|
3
|
+
*
|
|
4
|
+
* Long-term memory with vector search using MemoryRelay API.
|
|
5
|
+
* Provides auto-recall and auto-capture via lifecycle hooks.
|
|
6
|
+
*
|
|
7
|
+
* API: https://api.memoryrelay.net
|
|
8
|
+
* Docs: https://memoryrelay.io
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
interface MemoryRelayConfig {
|
|
18
|
+
apiKey: string;
|
|
19
|
+
agentId: string;
|
|
20
|
+
apiUrl?: string;
|
|
21
|
+
autoCapture?: boolean;
|
|
22
|
+
autoRecall?: boolean;
|
|
23
|
+
recallLimit?: number;
|
|
24
|
+
recallThreshold?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Memory {
|
|
28
|
+
id: string;
|
|
29
|
+
content: string;
|
|
30
|
+
agent_id: string;
|
|
31
|
+
user_id: string;
|
|
32
|
+
metadata: Record<string, string>;
|
|
33
|
+
entities: string[];
|
|
34
|
+
created_at: number;
|
|
35
|
+
updated_at: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface SearchResult {
|
|
39
|
+
memory: Memory;
|
|
40
|
+
score: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// MemoryRelay API Client
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
class MemoryRelayClient {
|
|
48
|
+
constructor(
|
|
49
|
+
private readonly apiKey: string,
|
|
50
|
+
private readonly agentId: string,
|
|
51
|
+
private readonly apiUrl: string = "https://api.memoryrelay.net",
|
|
52
|
+
) {}
|
|
53
|
+
|
|
54
|
+
private async request<T>(
|
|
55
|
+
method: string,
|
|
56
|
+
path: string,
|
|
57
|
+
body?: unknown,
|
|
58
|
+
): Promise<T> {
|
|
59
|
+
const url = `${this.apiUrl}${path}`;
|
|
60
|
+
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
method,
|
|
63
|
+
headers: {
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
66
|
+
"User-Agent": "openclaw-memory-memoryrelay/0.1.0",
|
|
67
|
+
},
|
|
68
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const errorData = await response.json().catch(() => ({}));
|
|
73
|
+
throw new Error(
|
|
74
|
+
`MemoryRelay API error: ${response.status} ${response.statusText}` +
|
|
75
|
+
(errorData.message ? ` - ${errorData.message}` : ""),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return response.json();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async store(content: string, metadata?: Record<string, string>): Promise<Memory> {
|
|
83
|
+
return this.request<Memory>("POST", "/v1/memories/memories", {
|
|
84
|
+
content,
|
|
85
|
+
metadata,
|
|
86
|
+
agent_id: this.agentId,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async search(
|
|
91
|
+
query: string,
|
|
92
|
+
limit: number = 5,
|
|
93
|
+
threshold: number = 0.3,
|
|
94
|
+
): Promise<SearchResult[]> {
|
|
95
|
+
const response = await this.request<{ data: SearchResult[] }>(
|
|
96
|
+
"POST",
|
|
97
|
+
"/v1/memories/memories/search",
|
|
98
|
+
{
|
|
99
|
+
query,
|
|
100
|
+
limit,
|
|
101
|
+
threshold,
|
|
102
|
+
agent_id: this.agentId,
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
return response.data || [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async list(limit: number = 20, offset: number = 0): Promise<Memory[]> {
|
|
109
|
+
const response = await this.request<{ data: Memory[] }>(
|
|
110
|
+
"GET",
|
|
111
|
+
`/v1/memories/memories?limit=${limit}&offset=${offset}`,
|
|
112
|
+
);
|
|
113
|
+
return response.data || [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async get(id: string): Promise<Memory> {
|
|
117
|
+
return this.request<Memory>("GET", `/v1/memories/memories/${id}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async delete(id: string): Promise<void> {
|
|
121
|
+
await this.request<void>("DELETE", `/v1/memories/memories/${id}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async health(): Promise<{ status: string }> {
|
|
125
|
+
return this.request<{ status: string }>("GET", "/v1/health");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Pattern Detection (for auto-capture)
|
|
131
|
+
// ============================================================================
|
|
132
|
+
|
|
133
|
+
const CAPTURE_PATTERNS = [
|
|
134
|
+
/remember\s+(?:that\s+)?/i,
|
|
135
|
+
/(?:my|the)\s+(?:name|email|phone|address|preference)/i,
|
|
136
|
+
/important(?:ly)?[:\s]/i,
|
|
137
|
+
/always\s+(?:use|prefer|want)/i,
|
|
138
|
+
/(?:do|don't)\s+(?:like|want|prefer)/i,
|
|
139
|
+
/(?:api|key|token|password|secret)(?:\s+is)?[:\s]/i,
|
|
140
|
+
/(?:ssh|server|host|ip|port)(?:\s+is)?[:\s]/i,
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
function shouldCapture(text: string): boolean {
|
|
144
|
+
if (text.length < 20 || text.length > 2000) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return CAPTURE_PATTERNS.some((pattern) => pattern.test(text));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// Plugin Export
|
|
152
|
+
// ============================================================================
|
|
153
|
+
|
|
154
|
+
export default async function plugin(api: OpenClawPluginApi): Promise<void> {
|
|
155
|
+
const cfg = api.pluginConfig as MemoryRelayConfig | undefined;
|
|
156
|
+
if (!cfg?.apiKey || !cfg?.agentId) {
|
|
157
|
+
api.logger.warn("memory-memoryrelay: missing apiKey or agentId, plugin disabled");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const client = new MemoryRelayClient(
|
|
162
|
+
cfg.apiKey,
|
|
163
|
+
cfg.agentId,
|
|
164
|
+
cfg.apiUrl || "https://api.memoryrelay.net",
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Verify connection on startup
|
|
168
|
+
try {
|
|
169
|
+
await client.health();
|
|
170
|
+
api.logger.info(`memory-memoryrelay: connected to ${cfg.apiUrl || "api.memoryrelay.net"}`);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
api.logger.error(`memory-memoryrelay: health check failed: ${String(err)}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ========================================================================
|
|
177
|
+
// Tools (using JSON Schema directly)
|
|
178
|
+
// ========================================================================
|
|
179
|
+
|
|
180
|
+
// memory_store tool
|
|
181
|
+
api.registerTool(
|
|
182
|
+
{
|
|
183
|
+
name: "memory_store",
|
|
184
|
+
description:
|
|
185
|
+
"Store a new memory in MemoryRelay. Use this to save important information, facts, preferences, or context that should be remembered for future conversations.",
|
|
186
|
+
inputSchema: {
|
|
187
|
+
type: "object",
|
|
188
|
+
properties: {
|
|
189
|
+
content: {
|
|
190
|
+
type: "string",
|
|
191
|
+
description: "The memory content to store. Be specific and include relevant context.",
|
|
192
|
+
},
|
|
193
|
+
metadata: {
|
|
194
|
+
type: "object",
|
|
195
|
+
description: "Optional key-value metadata to attach to the memory",
|
|
196
|
+
additionalProperties: { type: "string" },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
required: ["content"],
|
|
200
|
+
},
|
|
201
|
+
handler: async ({ content, metadata }: { content: string; metadata?: Record<string, string> }) => {
|
|
202
|
+
try {
|
|
203
|
+
const memory = await client.store(content, metadata);
|
|
204
|
+
return {
|
|
205
|
+
content: [
|
|
206
|
+
{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: `Memory stored successfully (id: ${memory.id.slice(0, 8)}...)`,
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
details: { id: memory.id, stored: true },
|
|
212
|
+
};
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: `Failed to store memory: ${String(err)}` }],
|
|
216
|
+
details: { error: String(err) },
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{ name: "memory_store" },
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// memory_recall tool (semantic search)
|
|
225
|
+
api.registerTool(
|
|
226
|
+
{
|
|
227
|
+
name: "memory_recall",
|
|
228
|
+
description:
|
|
229
|
+
"Search memories using natural language. Returns the most relevant memories based on semantic similarity.",
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: {
|
|
233
|
+
query: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "Natural language search query",
|
|
236
|
+
},
|
|
237
|
+
limit: {
|
|
238
|
+
type: "number",
|
|
239
|
+
description: "Maximum results (1-20)",
|
|
240
|
+
minimum: 1,
|
|
241
|
+
maximum: 20,
|
|
242
|
+
default: 5,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
required: ["query"],
|
|
246
|
+
},
|
|
247
|
+
handler: async ({ query, limit = 5 }: { query: string; limit?: number }) => {
|
|
248
|
+
try {
|
|
249
|
+
const results = await client.search(query, limit, cfg.recallThreshold || 0.3);
|
|
250
|
+
|
|
251
|
+
if (results.length === 0) {
|
|
252
|
+
return {
|
|
253
|
+
content: [{ type: "text", text: "No relevant memories found." }],
|
|
254
|
+
details: { count: 0 },
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const formatted = results
|
|
259
|
+
.map(
|
|
260
|
+
(r) =>
|
|
261
|
+
`- [${r.score.toFixed(2)}] ${r.memory.content.slice(0, 200)}${
|
|
262
|
+
r.memory.content.length > 200 ? "..." : ""
|
|
263
|
+
}`,
|
|
264
|
+
)
|
|
265
|
+
.join("\n");
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "text",
|
|
271
|
+
text: `Found ${results.length} relevant memories:\n${formatted}`,
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
details: {
|
|
275
|
+
count: results.length,
|
|
276
|
+
memories: results.map((r) => ({
|
|
277
|
+
id: r.memory.id,
|
|
278
|
+
content: r.memory.content,
|
|
279
|
+
score: r.score,
|
|
280
|
+
})),
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
} catch (err) {
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: `Search failed: ${String(err)}` }],
|
|
286
|
+
details: { error: String(err) },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{ name: "memory_recall" },
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// memory_forget tool
|
|
295
|
+
api.registerTool(
|
|
296
|
+
{
|
|
297
|
+
name: "memory_forget",
|
|
298
|
+
description: "Delete a memory by ID or search for memories to forget.",
|
|
299
|
+
inputSchema: {
|
|
300
|
+
type: "object",
|
|
301
|
+
properties: {
|
|
302
|
+
memoryId: {
|
|
303
|
+
type: "string",
|
|
304
|
+
description: "Memory ID to delete",
|
|
305
|
+
},
|
|
306
|
+
query: {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: "Search query to find memory",
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
handler: async ({ memoryId, query }: { memoryId?: string; query?: string }) => {
|
|
313
|
+
if (memoryId) {
|
|
314
|
+
try {
|
|
315
|
+
await client.delete(memoryId);
|
|
316
|
+
return {
|
|
317
|
+
content: [{ type: "text", text: `Memory ${memoryId.slice(0, 8)}... deleted.` }],
|
|
318
|
+
details: { action: "deleted", id: memoryId },
|
|
319
|
+
};
|
|
320
|
+
} catch (err) {
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: "text", text: `Delete failed: ${String(err)}` }],
|
|
323
|
+
details: { error: String(err) },
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (query) {
|
|
329
|
+
const results = await client.search(query, 5, 0.5);
|
|
330
|
+
|
|
331
|
+
if (results.length === 0) {
|
|
332
|
+
return {
|
|
333
|
+
content: [{ type: "text", text: "No matching memories found." }],
|
|
334
|
+
details: { count: 0 },
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// If single high-confidence match, delete it
|
|
339
|
+
if (results.length === 1 && results[0].score > 0.9) {
|
|
340
|
+
await client.delete(results[0].memory.id);
|
|
341
|
+
return {
|
|
342
|
+
content: [
|
|
343
|
+
{ type: "text", text: `Forgotten: "${results[0].memory.content.slice(0, 60)}..."` },
|
|
344
|
+
],
|
|
345
|
+
details: { action: "deleted", id: results[0].memory.id },
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const list = results
|
|
350
|
+
.map((r) => `- [${r.memory.id.slice(0, 8)}] ${r.memory.content.slice(0, 60)}...`)
|
|
351
|
+
.join("\n");
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
content: [
|
|
355
|
+
{
|
|
356
|
+
type: "text",
|
|
357
|
+
text: `Found ${results.length} candidates. Specify memoryId:\n${list}`,
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
details: { action: "candidates", count: results.length },
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: "text", text: "Provide query or memoryId." }],
|
|
366
|
+
details: { error: "missing_param" },
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
{ name: "memory_forget" },
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// ========================================================================
|
|
374
|
+
// CLI Commands
|
|
375
|
+
// ========================================================================
|
|
376
|
+
|
|
377
|
+
api.registerCli(
|
|
378
|
+
({ program }) => {
|
|
379
|
+
const mem = program.command("memoryrelay").description("MemoryRelay memory plugin commands");
|
|
380
|
+
|
|
381
|
+
mem
|
|
382
|
+
.command("status")
|
|
383
|
+
.description("Check MemoryRelay connection status")
|
|
384
|
+
.action(async () => {
|
|
385
|
+
try {
|
|
386
|
+
const health = await client.health();
|
|
387
|
+
console.log(`Status: ${health.status}`);
|
|
388
|
+
console.log(`Agent ID: ${cfg.agentId}`);
|
|
389
|
+
console.log(`API: ${cfg.apiUrl || "https://api.memoryrelay.net"}`);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.error(`Connection failed: ${String(err)}`);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
mem
|
|
396
|
+
.command("list")
|
|
397
|
+
.description("List recent memories")
|
|
398
|
+
.option("--limit <n>", "Max results", "10")
|
|
399
|
+
.action(async (opts) => {
|
|
400
|
+
const memories = await client.list(parseInt(opts.limit));
|
|
401
|
+
for (const m of memories) {
|
|
402
|
+
console.log(`[${m.id.slice(0, 8)}] ${m.content.slice(0, 80)}...`);
|
|
403
|
+
}
|
|
404
|
+
console.log(`\nTotal: ${memories.length} memories`);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
mem
|
|
408
|
+
.command("search")
|
|
409
|
+
.description("Search memories")
|
|
410
|
+
.argument("<query>", "Search query")
|
|
411
|
+
.option("--limit <n>", "Max results", "5")
|
|
412
|
+
.action(async (query, opts) => {
|
|
413
|
+
const results = await client.search(query, parseInt(opts.limit));
|
|
414
|
+
for (const r of results) {
|
|
415
|
+
console.log(`[${r.score.toFixed(2)}] ${r.memory.content.slice(0, 80)}...`);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
{ commands: ["memoryrelay"] },
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// ========================================================================
|
|
423
|
+
// Lifecycle Hooks
|
|
424
|
+
// ========================================================================
|
|
425
|
+
|
|
426
|
+
// Auto-recall: inject relevant memories before agent starts
|
|
427
|
+
if (cfg.autoRecall) {
|
|
428
|
+
api.on("before_agent_start", async (event) => {
|
|
429
|
+
if (!event.prompt || event.prompt.length < 10) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const results = await client.search(
|
|
435
|
+
event.prompt,
|
|
436
|
+
cfg.recallLimit || 5,
|
|
437
|
+
cfg.recallThreshold || 0.3,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
if (results.length === 0) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const memoryContext = results
|
|
445
|
+
.map((r) => `- ${r.memory.content}`)
|
|
446
|
+
.join("\n");
|
|
447
|
+
|
|
448
|
+
api.logger.info?.(
|
|
449
|
+
`memory-memoryrelay: injecting ${results.length} memories into context`,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
prependContext: `<relevant-memories>\nThe following memories from MemoryRelay may be relevant:\n${memoryContext}\n</relevant-memories>`,
|
|
454
|
+
};
|
|
455
|
+
} catch (err) {
|
|
456
|
+
api.logger.warn?.(`memory-memoryrelay: recall failed: ${String(err)}`);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Auto-capture: analyze and store important information after agent ends
|
|
462
|
+
if (cfg.autoCapture) {
|
|
463
|
+
api.on("agent_end", async (event) => {
|
|
464
|
+
if (!event.success || !event.messages || event.messages.length === 0) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
const texts: string[] = [];
|
|
470
|
+
for (const msg of event.messages) {
|
|
471
|
+
if (!msg || typeof msg !== "object") continue;
|
|
472
|
+
const msgObj = msg as Record<string, unknown>;
|
|
473
|
+
const role = msgObj.role;
|
|
474
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
475
|
+
|
|
476
|
+
const content = msgObj.content;
|
|
477
|
+
if (typeof content === "string") {
|
|
478
|
+
texts.push(content);
|
|
479
|
+
} else if (Array.isArray(content)) {
|
|
480
|
+
for (const block of content) {
|
|
481
|
+
if (
|
|
482
|
+
block &&
|
|
483
|
+
typeof block === "object" &&
|
|
484
|
+
"type" in block &&
|
|
485
|
+
(block as Record<string, unknown>).type === "text" &&
|
|
486
|
+
"text" in block
|
|
487
|
+
) {
|
|
488
|
+
texts.push((block as Record<string, unknown>).text as string);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const toCapture = texts.filter((text) => text && shouldCapture(text));
|
|
495
|
+
if (toCapture.length === 0) return;
|
|
496
|
+
|
|
497
|
+
let stored = 0;
|
|
498
|
+
for (const text of toCapture.slice(0, 3)) {
|
|
499
|
+
// Check for duplicates via search
|
|
500
|
+
const existing = await client.search(text, 1, 0.95);
|
|
501
|
+
if (existing.length > 0) continue;
|
|
502
|
+
|
|
503
|
+
await client.store(text, { source: "auto-capture" });
|
|
504
|
+
stored++;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (stored > 0) {
|
|
508
|
+
api.logger.info?.(`memory-memoryrelay: auto-captured ${stored} memories`);
|
|
509
|
+
}
|
|
510
|
+
} catch (err) {
|
|
511
|
+
api.logger.warn?.(`memory-memoryrelay: capture failed: ${String(err)}`);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
api.logger.info?.(
|
|
517
|
+
`memory-memoryrelay: plugin loaded (autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture})`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "plugin-memoryrelay-ai",
|
|
3
|
+
"kind": "memory",
|
|
4
|
+
"name": "MemoryRelay AI",
|
|
5
|
+
"description": "AI memory service using MemoryRelay API (api.memoryrelay.net)",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"uiHints": {
|
|
8
|
+
"apiKey": {
|
|
9
|
+
"label": "MemoryRelay API Key",
|
|
10
|
+
"sensitive": true,
|
|
11
|
+
"placeholder": "mem_prod_...",
|
|
12
|
+
"help": "API key from memoryrelay.io (or use ${MEMORYRELAY_API_KEY})"
|
|
13
|
+
},
|
|
14
|
+
"agentId": {
|
|
15
|
+
"label": "Agent ID",
|
|
16
|
+
"placeholder": "iris",
|
|
17
|
+
"help": "Unique identifier for this agent in MemoryRelay"
|
|
18
|
+
},
|
|
19
|
+
"apiUrl": {
|
|
20
|
+
"label": "API URL",
|
|
21
|
+
"placeholder": "https://api.memoryrelay.net",
|
|
22
|
+
"advanced": true,
|
|
23
|
+
"help": "MemoryRelay API endpoint"
|
|
24
|
+
},
|
|
25
|
+
"autoCapture": {
|
|
26
|
+
"label": "Auto-Capture",
|
|
27
|
+
"help": "Automatically capture important information from conversations"
|
|
28
|
+
},
|
|
29
|
+
"autoRecall": {
|
|
30
|
+
"label": "Auto-Recall",
|
|
31
|
+
"help": "Automatically inject relevant memories into context"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"configSchema": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": false,
|
|
37
|
+
"properties": {
|
|
38
|
+
"apiKey": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
"agentId": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
},
|
|
44
|
+
"apiUrl": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"default": "https://api.memoryrelay.net"
|
|
47
|
+
},
|
|
48
|
+
"autoCapture": {
|
|
49
|
+
"type": "boolean",
|
|
50
|
+
"default": false
|
|
51
|
+
},
|
|
52
|
+
"autoRecall": {
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"default": true
|
|
55
|
+
},
|
|
56
|
+
"recallLimit": {
|
|
57
|
+
"type": "number",
|
|
58
|
+
"default": 5
|
|
59
|
+
},
|
|
60
|
+
"recallThreshold": {
|
|
61
|
+
"type": "number",
|
|
62
|
+
"default": 0.3
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"required": ["apiKey", "agentId"]
|
|
66
|
+
}
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memoryrelay/plugin-memoryrelay-ai",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "OpenClaw memory plugin for MemoryRelay API - long-term memory with semantic search",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"openclaw",
|
|
9
|
+
"plugin",
|
|
10
|
+
"memory",
|
|
11
|
+
"memoryrelay",
|
|
12
|
+
"semantic-search",
|
|
13
|
+
"vector-search",
|
|
14
|
+
"ai-memory",
|
|
15
|
+
"agent-memory"
|
|
16
|
+
],
|
|
17
|
+
"author": "MemoryRelay",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/memoryrelay/openclaw-plugin.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/memoryrelay/openclaw-plugin/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/memoryrelay/openclaw-plugin#readme",
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"openclaw": ">=2026.2.0"
|
|
29
|
+
},
|
|
30
|
+
"openclaw": {
|
|
31
|
+
"id": "plugin-memoryrelay-ai",
|
|
32
|
+
"extensions": ["./"]
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"index.ts",
|
|
36
|
+
"openclaw.plugin.json",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|