@memclaw/memclaw 0.9.16 → 0.9.17
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 +65 -36
- package/dist/index.js +23 -23
- package/dist/src/config.js +273 -260
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/lagacy/references/setup.md +1 -5
- package/skills/memclaw/SKILL.md +12 -8
- package/skills/memclaw-maintance/SKILL.md +9 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# MemClaw
|
|
2
2
|
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
|
|
3
5
|
Layered semantic memory plugin for OpenClaw with L0/L1/L2 tiered retrieval, automatic service management, and migration support from OpenClaw native memory.
|
|
4
6
|
|
|
5
7
|
## Overview
|
|
@@ -13,6 +15,7 @@ MemClaw is an OpenClaw plugin that provides advanced semantic memory capabilitie
|
|
|
13
15
|
- **Semantic Search**: Vector-based similarity search across all memory layers
|
|
14
16
|
- **Session Management**: Create, list, and close memory sessions
|
|
15
17
|
- **Migration Support**: One-click migration from OpenClaw native memory
|
|
18
|
+
- **Easy Configuration**: Configure LLM/Embedding directly through OpenClaw plugin settings
|
|
16
19
|
- **Cross-Platform**: Supports Windows x64 and macOS Apple Silicon
|
|
17
20
|
|
|
18
21
|
## Architecture
|
|
@@ -53,7 +56,7 @@ OpenClaw + MemClaw Plugin
|
|
|
53
56
|
| Requirement | Details |
|
|
54
57
|
|-------------|---------|
|
|
55
58
|
| **Platforms** | Windows x64, macOS Apple Silicon |
|
|
56
|
-
| **Node.js** | ≥
|
|
59
|
+
| **Node.js** | ≥ 20.0.0 |
|
|
57
60
|
| **OpenClaw** | Installed and configured |
|
|
58
61
|
|
|
59
62
|
### Install Plugin
|
|
@@ -114,9 +117,11 @@ Then enable in `openclaw.json`:
|
|
|
114
117
|
|
|
115
118
|
After making code changes, rebuild with `bun run build` and restart OpenClaw.
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
### Plugin Configuration
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
Configure MemClaw directly through OpenClaw plugin settings in `openclaw.json`:
|
|
120
125
|
|
|
121
126
|
```json
|
|
122
127
|
{
|
|
@@ -127,7 +132,13 @@ Edit your `openclaw.json`:
|
|
|
127
132
|
"config": {
|
|
128
133
|
"serviceUrl": "http://localhost:8085",
|
|
129
134
|
"tenantId": "tenant_claw",
|
|
130
|
-
"autoStartServices": true
|
|
135
|
+
"autoStartServices": true,
|
|
136
|
+
"llmApiBaseUrl": "https://api.openai.com/v1",
|
|
137
|
+
"llmApiKey": "your-llm-api-key",
|
|
138
|
+
"llmModel": "gpt-4o-mini",
|
|
139
|
+
"embeddingApiBaseUrl": "https://api.openai.com/v1",
|
|
140
|
+
"embeddingApiKey": "your-embedding-api-key",
|
|
141
|
+
"embeddingModel": "text-embedding-3-small"
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
144
|
}
|
|
@@ -142,26 +153,33 @@ Edit your `openclaw.json`:
|
|
|
142
153
|
|
|
143
154
|
> **Note**: Set `memorySearch.enabled: false` to disable OpenClaw's built-in memory search and use MemClaw instead.
|
|
144
155
|
|
|
145
|
-
###
|
|
146
|
-
|
|
147
|
-
On first run, MemClaw creates a configuration file:
|
|
148
|
-
|
|
149
|
-
| Platform | Path |
|
|
150
|
-
|----------|------|
|
|
151
|
-
| Windows | `%APPDATA%\memclaw\config.toml` |
|
|
152
|
-
| macOS | `~/Library/Application Support/memclaw/config.toml` |
|
|
156
|
+
### Configuration Options
|
|
153
157
|
|
|
154
|
-
|
|
158
|
+
| Option | Type | Default | Description |
|
|
159
|
+
|--------|------|---------|-------------|
|
|
160
|
+
| `serviceUrl` | string | `http://localhost:8085` | Cortex Memory service URL |
|
|
161
|
+
| `tenantId` | string | `tenant_claw` | Tenant ID for data isolation |
|
|
162
|
+
| `autoStartServices` | boolean | `true` | Auto-start Qdrant and service |
|
|
163
|
+
| `defaultSessionId` | string | `default` | Default session for memory operations |
|
|
164
|
+
| `searchLimit` | number | `10` | Default number of search results |
|
|
165
|
+
| `minScore` | number | `0.6` | Minimum relevance score (0-1) |
|
|
166
|
+
| `qdrantPort` | number | `6334` | Qdrant port (gRPC) |
|
|
167
|
+
| `servicePort` | number | `8085` | cortex-mem-service port |
|
|
168
|
+
| `llmApiBaseUrl` | string | `https://api.openai.com/v1` | LLM API endpoint URL |
|
|
169
|
+
| `llmApiKey` | string | - | LLM API key (required) |
|
|
170
|
+
| `llmModel` | string | `gpt-5-mini` | LLM model name |
|
|
171
|
+
| `embeddingApiBaseUrl` | string | `https://api.openai.com/v1` | Embedding API endpoint URL |
|
|
172
|
+
| `embeddingApiKey` | string | - | Embedding API key (required) |
|
|
173
|
+
| `embeddingModel` | string | `text-embedding-3-small` | Embedding model name |
|
|
155
174
|
|
|
156
|
-
|
|
157
|
-
[llm]
|
|
158
|
-
api_key = "xxx" # REQUIRED: Your LLM API key
|
|
175
|
+
### Configuration via UI
|
|
159
176
|
|
|
160
|
-
|
|
161
|
-
api_key = "xxx" # REQUIRED: Your embedding API key (can be same as llm.api_key)
|
|
162
|
-
```
|
|
177
|
+
You can also configure the plugin through OpenClaw UI:
|
|
163
178
|
|
|
164
|
-
|
|
179
|
+
1. Open OpenClaw Settings (`openclaw.json` or via UI)
|
|
180
|
+
2. Navigate to Plugins → MemClaw → Configuration
|
|
181
|
+
3. Fill in the required fields for LLM and Embedding
|
|
182
|
+
4. Save and **restart OpenClaw Gateway** for changes to take effect
|
|
165
183
|
|
|
166
184
|
## Available Tools
|
|
167
185
|
|
|
@@ -214,42 +232,47 @@ Close a session and trigger memory extraction pipeline (takes 30-60 seconds).
|
|
|
214
232
|
}
|
|
215
233
|
```
|
|
216
234
|
|
|
235
|
+
> **Important**: Call this tool proactively at natural checkpoints, not just when the conversation ends. Ideal timing: after completing important tasks, topic transitions, or accumulating enough conversation content.
|
|
236
|
+
|
|
217
237
|
### cortex_migrate
|
|
218
238
|
|
|
219
239
|
Migrate from OpenClaw native memory to MemClaw. Run once during initial setup.
|
|
220
240
|
|
|
221
|
-
|
|
241
|
+
### cortex_maintenance
|
|
222
242
|
|
|
223
|
-
|
|
224
|
-
|--------|------|---------|-------------|
|
|
225
|
-
| `serviceUrl` | string | `http://localhost:8085` | Cortex Memory service URL |
|
|
226
|
-
| `tenantId` | string | `tenant_claw` | Tenant ID for data isolation |
|
|
227
|
-
| `autoStartServices` | boolean | `true` | Auto-start Qdrant and service |
|
|
228
|
-
| `defaultSessionId` | string | `default` | Default session for memory operations |
|
|
229
|
-
| `searchLimit` | number | `10` | Default number of search results |
|
|
230
|
-
| `minScore` | number | `0.6` | Minimum relevance score (0-1) |
|
|
243
|
+
Perform periodic maintenance on MemClaw data (prune, reindex, ensure-all layers).
|
|
231
244
|
|
|
232
245
|
## Quick Decision Flow
|
|
233
246
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
| Scenario | Tool |
|
|
248
|
+
|----------|------|
|
|
249
|
+
| Need to find information | `cortex_search` |
|
|
250
|
+
| Need more context | `cortex_recall` |
|
|
251
|
+
| Save important information | `cortex_add_memory` |
|
|
252
|
+
| Complete a task/topic | `cortex_close_session` |
|
|
253
|
+
| First-time use with existing memories | `cortex_migrate` |
|
|
254
|
+
|
|
255
|
+
For detailed guidance on tool selection, session lifecycle, and best practices, see the [Skills Documentation](skills/memclaw/SKILL.md).
|
|
239
256
|
|
|
240
257
|
## Troubleshooting
|
|
241
258
|
|
|
259
|
+
### Plugin Not Working
|
|
260
|
+
|
|
261
|
+
1. **Check Configuration**: Open OpenClaw settings and verify MemClaw plugin configuration, especially LLM and Embedding settings
|
|
262
|
+
2. **Restart OpenClaw Gateway**: Configuration changes require a gateway restart to take effect
|
|
263
|
+
3. **Verify Services**: Run `cortex_list_sessions` to check if the service is responding
|
|
264
|
+
|
|
242
265
|
### Services Won't Start
|
|
243
266
|
|
|
244
267
|
1. Check that ports 6333, 6334, 8085 are available
|
|
245
|
-
2. Verify
|
|
268
|
+
2. Verify LLM and Embedding credentials are configured correctly
|
|
246
269
|
3. Run `openclaw skills` to check plugin status
|
|
247
270
|
|
|
248
271
|
### Search Returns No Results
|
|
249
272
|
|
|
250
273
|
1. Run `cortex_list_sessions` to verify sessions exist
|
|
251
274
|
2. Lower `min_score` threshold (default: 0.6)
|
|
252
|
-
3.
|
|
275
|
+
3. Ensure memories have been stored (run `cortex_close_session` to extract memories)
|
|
253
276
|
|
|
254
277
|
### Migration Fails
|
|
255
278
|
|
|
@@ -271,6 +294,12 @@ cortex-mem-cli --config config.toml --tenant tenant_claw layers ensure-all
|
|
|
271
294
|
cortex-mem-cli --config config.toml --tenant tenant_claw vector reindex
|
|
272
295
|
```
|
|
273
296
|
|
|
297
|
+
## Documentation
|
|
298
|
+
|
|
299
|
+
- **[Skills Documentation](skills/memclaw/SKILL.md)** — Agent skill guide with troubleshooting
|
|
300
|
+
- **[Best Practices](skills/memclaw/references/best-practices.md)** — Tool selection, session lifecycle, search strategies
|
|
301
|
+
- **[Tools Reference](skills/memclaw/references/tools.md)** — Detailed tool parameters and examples
|
|
302
|
+
|
|
274
303
|
## License
|
|
275
304
|
|
|
276
305
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
/**
|
|
3
3
|
* MemClaw - Layered Semantic Memory for OpenClaw
|
|
4
4
|
*
|
|
@@ -26,33 +26,33 @@
|
|
|
26
26
|
* }
|
|
27
27
|
* }
|
|
28
28
|
*/
|
|
29
|
-
Object.defineProperty(exports,
|
|
29
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
30
30
|
exports.plugin = void 0;
|
|
31
31
|
exports.default = memclawPlugin;
|
|
32
|
-
const plugin_impl_js_1 = require(
|
|
32
|
+
const plugin_impl_js_1 = require('./plugin-impl.js');
|
|
33
33
|
// Default export - main plugin function
|
|
34
34
|
function memclawPlugin(api) {
|
|
35
|
-
|
|
35
|
+
return (0, plugin_impl_js_1.createPlugin)(api);
|
|
36
36
|
}
|
|
37
37
|
// Named export - object style registration
|
|
38
38
|
exports.plugin = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
39
|
+
id: 'memclaw',
|
|
40
|
+
name: 'MemClaw',
|
|
41
|
+
version: '0.9.17',
|
|
42
|
+
configSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
serviceUrl: { type: 'string', default: 'http://localhost:8085' },
|
|
46
|
+
defaultSessionId: { type: 'string', default: 'default' },
|
|
47
|
+
searchLimit: { type: 'integer', default: 10 },
|
|
48
|
+
minScore: { type: 'number', default: 0.6 },
|
|
49
|
+
tenantId: { type: 'string', default: 'tenant_claw' },
|
|
50
|
+
autoStartServices: { type: 'boolean', default: true }
|
|
51
|
+
},
|
|
52
|
+
required: []
|
|
53
|
+
},
|
|
54
|
+
register(api) {
|
|
55
|
+
return (0, plugin_impl_js_1.createPlugin)(api);
|
|
56
|
+
}
|
|
57
57
|
};
|
|
58
|
-
//# sourceMappingURL=index.js.map
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
package/dist/src/config.js
CHANGED
|
@@ -1,44 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
/**
|
|
3
3
|
* Configuration management for MemClaw
|
|
4
4
|
*
|
|
5
5
|
* Handles platform-specific config paths, config file generation,
|
|
6
6
|
* and auto-opening config files for user editing.
|
|
7
7
|
*/
|
|
8
|
-
var __createBinding =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
8
|
+
var __createBinding =
|
|
9
|
+
(this && this.__createBinding) ||
|
|
10
|
+
(Object.create
|
|
11
|
+
? function (o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () {
|
|
18
|
+
return m[k];
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}
|
|
24
|
+
: function (o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
});
|
|
28
|
+
var __setModuleDefault =
|
|
29
|
+
(this && this.__setModuleDefault) ||
|
|
30
|
+
(Object.create
|
|
31
|
+
? function (o, v) {
|
|
32
|
+
Object.defineProperty(o, 'default', { enumerable: true, value: v });
|
|
33
|
+
}
|
|
34
|
+
: function (o, v) {
|
|
35
|
+
o['default'] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar =
|
|
38
|
+
(this && this.__importStar) ||
|
|
39
|
+
(function () {
|
|
40
|
+
var ownKeys = function (o) {
|
|
41
|
+
ownKeys =
|
|
42
|
+
Object.getOwnPropertyNames ||
|
|
43
|
+
function (o) {
|
|
44
|
+
var ar = [];
|
|
45
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
46
|
+
return ar;
|
|
47
|
+
};
|
|
48
|
+
return ownKeys(o);
|
|
49
|
+
};
|
|
50
|
+
return function (mod) {
|
|
51
|
+
if (mod && mod.__esModule) return mod;
|
|
52
|
+
var result = {};
|
|
53
|
+
if (mod != null)
|
|
54
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++)
|
|
55
|
+
if (k[i] !== 'default') __createBinding(result, mod, k[i]);
|
|
56
|
+
__setModuleDefault(result, mod);
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
})();
|
|
60
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
42
61
|
exports.getDataDir = getDataDir;
|
|
43
62
|
exports.getConfigPath = getConfigPath;
|
|
44
63
|
exports.generateConfigTemplate = generateConfigTemplate;
|
|
@@ -48,28 +67,29 @@ exports.parseConfig = parseConfig;
|
|
|
48
67
|
exports.validateConfig = validateConfig;
|
|
49
68
|
exports.updateConfigFromPlugin = updateConfigFromPlugin;
|
|
50
69
|
exports.mergeConfigWithPlugin = mergeConfigWithPlugin;
|
|
51
|
-
const fs = __importStar(require(
|
|
52
|
-
const path = __importStar(require(
|
|
53
|
-
const os = __importStar(require(
|
|
54
|
-
const child_process_1 = require(
|
|
70
|
+
const fs = __importStar(require('fs'));
|
|
71
|
+
const path = __importStar(require('path'));
|
|
72
|
+
const os = __importStar(require('os'));
|
|
73
|
+
const child_process_1 = require('child_process');
|
|
55
74
|
// Platform-specific paths
|
|
56
75
|
function getDataDir() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
const platform = process.platform;
|
|
77
|
+
if (platform === 'win32') {
|
|
78
|
+
return path.join(
|
|
79
|
+
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
|
80
|
+
'memclaw'
|
|
81
|
+
);
|
|
82
|
+
} else if (platform === 'darwin') {
|
|
83
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'memclaw');
|
|
84
|
+
} else {
|
|
85
|
+
return path.join(os.homedir(), '.local', 'share', 'memclaw');
|
|
86
|
+
}
|
|
67
87
|
}
|
|
68
88
|
function getConfigPath() {
|
|
69
|
-
|
|
89
|
+
return path.join(getDataDir(), 'config.toml');
|
|
70
90
|
}
|
|
71
91
|
function generateConfigTemplate() {
|
|
72
|
-
|
|
92
|
+
return `# MemClaw Configuration
|
|
73
93
|
#
|
|
74
94
|
# This file was auto-generated. Please fill in the required values below.
|
|
75
95
|
# All sections are required - missing sections will cause config to be ignored.
|
|
@@ -100,6 +120,7 @@ timeout_secs = 30
|
|
|
100
120
|
[server]
|
|
101
121
|
host = "localhost"
|
|
102
122
|
port = 8085
|
|
123
|
+
cors_origins = ["*"]
|
|
103
124
|
|
|
104
125
|
# Logging Configuration
|
|
105
126
|
[logging]
|
|
@@ -113,230 +134,222 @@ enable_intent_analysis = false
|
|
|
113
134
|
`;
|
|
114
135
|
}
|
|
115
136
|
function ensureConfigExists() {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
const dataDir = getDataDir();
|
|
138
|
+
const configPath = getConfigPath();
|
|
139
|
+
if (!fs.existsSync(dataDir)) {
|
|
140
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
141
|
+
}
|
|
142
|
+
if (!fs.existsSync(configPath)) {
|
|
143
|
+
const template = generateConfigTemplate();
|
|
144
|
+
fs.writeFileSync(configPath, template, 'utf-8');
|
|
145
|
+
return { created: true, path: configPath };
|
|
146
|
+
}
|
|
147
|
+
return { created: false, path: configPath };
|
|
127
148
|
}
|
|
128
149
|
function openConfigFile(configPath) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
resolve();
|
|
151
|
-
});
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const platform = process.platform;
|
|
152
|
+
let command;
|
|
153
|
+
let args = [];
|
|
154
|
+
if (platform === 'win32') {
|
|
155
|
+
command = 'cmd';
|
|
156
|
+
args = ['/c', 'start', '""', configPath];
|
|
157
|
+
} else if (platform === 'darwin') {
|
|
158
|
+
command = 'open';
|
|
159
|
+
args = [configPath];
|
|
160
|
+
} else {
|
|
161
|
+
command = 'xdg-open';
|
|
162
|
+
args = [configPath];
|
|
163
|
+
}
|
|
164
|
+
const proc = (0, child_process_1.spawn)(command, args, { detached: true, stdio: 'ignore' });
|
|
165
|
+
proc.on('error', (err) => {
|
|
166
|
+
reject(err);
|
|
167
|
+
});
|
|
168
|
+
proc.unref();
|
|
169
|
+
resolve();
|
|
170
|
+
});
|
|
152
171
|
}
|
|
153
172
|
function parseConfig(configPath) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
enable_intent_analysis: false,
|
|
226
|
-
...(config.cortex || {})
|
|
227
|
-
}
|
|
228
|
-
};
|
|
173
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
174
|
+
const config = {};
|
|
175
|
+
let currentSection = '';
|
|
176
|
+
for (const line of content.split('\n')) {
|
|
177
|
+
const trimmed = line.trim();
|
|
178
|
+
// Skip comments and empty lines
|
|
179
|
+
if (trimmed.startsWith('#') || trimmed === '') continue;
|
|
180
|
+
// Section header
|
|
181
|
+
const sectionMatch = trimmed.match(/^\[(\w+)\]$/);
|
|
182
|
+
if (sectionMatch) {
|
|
183
|
+
currentSection = sectionMatch[1];
|
|
184
|
+
config[currentSection] = {};
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
// Key-value pair
|
|
188
|
+
const kvMatch =
|
|
189
|
+
trimmed.match(/^(\w+)\s*=\s*"([^"]*)"(?:\s*$|\s*#)/) ||
|
|
190
|
+
trimmed.match(/^(\w+)\s*=\s*(\d+(?:\.\d+)?)(?:\s*$|\s*#)/) ||
|
|
191
|
+
trimmed.match(/^(\w+)\s*=\s*(true|false)(?:\s*$|\s*#)/);
|
|
192
|
+
if (kvMatch && currentSection) {
|
|
193
|
+
const key = kvMatch[1];
|
|
194
|
+
let value = kvMatch[2];
|
|
195
|
+
// Convert to appropriate type
|
|
196
|
+
if (value === 'true') value = true;
|
|
197
|
+
else if (value === 'false') value = false;
|
|
198
|
+
else if (/^\d+$/.test(value)) value = parseInt(value, 10);
|
|
199
|
+
else if (/^\d+\.\d+$/.test(value)) value = parseFloat(value);
|
|
200
|
+
config[currentSection] = config[currentSection] || {};
|
|
201
|
+
config[currentSection][key] = value;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Apply defaults
|
|
205
|
+
return {
|
|
206
|
+
qdrant: {
|
|
207
|
+
url: 'http://localhost:6334',
|
|
208
|
+
collection_name: 'memclaw',
|
|
209
|
+
timeout_secs: 30,
|
|
210
|
+
...(config.qdrant || {})
|
|
211
|
+
},
|
|
212
|
+
llm: {
|
|
213
|
+
api_base_url: 'https://api.openai.com/v1',
|
|
214
|
+
api_key: '',
|
|
215
|
+
model_efficient: 'gpt-5-mini',
|
|
216
|
+
temperature: 0.1,
|
|
217
|
+
max_tokens: 4096,
|
|
218
|
+
...(config.llm || {})
|
|
219
|
+
},
|
|
220
|
+
embedding: {
|
|
221
|
+
api_base_url: 'https://api.openai.com/v1',
|
|
222
|
+
api_key: '',
|
|
223
|
+
model_name: 'text-embedding-3-small',
|
|
224
|
+
batch_size: 10,
|
|
225
|
+
timeout_secs: 30,
|
|
226
|
+
...(config.embedding || {})
|
|
227
|
+
},
|
|
228
|
+
server: {
|
|
229
|
+
host: 'localhost',
|
|
230
|
+
port: 8085,
|
|
231
|
+
...(config.server || {})
|
|
232
|
+
},
|
|
233
|
+
logging: {
|
|
234
|
+
enabled: false,
|
|
235
|
+
log_directory: 'logs',
|
|
236
|
+
level: 'info',
|
|
237
|
+
...(config.logging || {})
|
|
238
|
+
},
|
|
239
|
+
cortex: {
|
|
240
|
+
enable_intent_analysis: false,
|
|
241
|
+
...(config.cortex || {})
|
|
242
|
+
}
|
|
243
|
+
};
|
|
229
244
|
}
|
|
230
245
|
function validateConfig(config) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
246
|
+
const errors = [];
|
|
247
|
+
if (!config.llm.api_key || config.llm.api_key === '') {
|
|
248
|
+
errors.push('llm.api_key is required');
|
|
249
|
+
}
|
|
250
|
+
if (!config.embedding.api_key || config.embedding.api_key === '') {
|
|
251
|
+
// Allow using llm.api_key for embedding if not specified
|
|
252
|
+
if (config.llm.api_key && config.llm.api_key !== '') {
|
|
253
|
+
config.embedding.api_key = config.llm.api_key;
|
|
254
|
+
} else {
|
|
255
|
+
errors.push('embedding.api_key is required');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
valid: errors.length === 0,
|
|
260
|
+
errors
|
|
261
|
+
};
|
|
248
262
|
}
|
|
249
263
|
/**
|
|
250
264
|
* Update config.toml with values from OpenClaw plugin config
|
|
251
265
|
* Only updates fields that are provided (non-empty) in pluginConfig
|
|
252
266
|
*/
|
|
253
267
|
function updateConfigFromPlugin(pluginConfig) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
return { updated: true, path: configPath };
|
|
268
|
+
const configPath = getConfigPath();
|
|
269
|
+
// Ensure config file exists
|
|
270
|
+
ensureConfigExists();
|
|
271
|
+
// Parse existing config
|
|
272
|
+
const existingConfig = parseConfig(configPath);
|
|
273
|
+
// Track if any changes were made
|
|
274
|
+
let updated = false;
|
|
275
|
+
// Build updated config sections
|
|
276
|
+
const updates = [];
|
|
277
|
+
// LLM config updates
|
|
278
|
+
if (pluginConfig.llmApiKey && pluginConfig.llmApiKey !== '') {
|
|
279
|
+
updates.push({ section: 'llm', key: 'api_key', value: pluginConfig.llmApiKey });
|
|
280
|
+
updated = true;
|
|
281
|
+
}
|
|
282
|
+
if (pluginConfig.llmApiBaseUrl && pluginConfig.llmApiBaseUrl !== '') {
|
|
283
|
+
updates.push({ section: 'llm', key: 'api_base_url', value: pluginConfig.llmApiBaseUrl });
|
|
284
|
+
updated = true;
|
|
285
|
+
}
|
|
286
|
+
if (pluginConfig.llmModel && pluginConfig.llmModel !== '') {
|
|
287
|
+
updates.push({ section: 'llm', key: 'model_efficient', value: pluginConfig.llmModel });
|
|
288
|
+
updated = true;
|
|
289
|
+
}
|
|
290
|
+
// Embedding config updates
|
|
291
|
+
if (pluginConfig.embeddingApiKey && pluginConfig.embeddingApiKey !== '') {
|
|
292
|
+
updates.push({ section: 'embedding', key: 'api_key', value: pluginConfig.embeddingApiKey });
|
|
293
|
+
updated = true;
|
|
294
|
+
}
|
|
295
|
+
if (pluginConfig.embeddingApiBaseUrl && pluginConfig.embeddingApiBaseUrl !== '') {
|
|
296
|
+
updates.push({
|
|
297
|
+
section: 'embedding',
|
|
298
|
+
key: 'api_base_url',
|
|
299
|
+
value: pluginConfig.embeddingApiBaseUrl
|
|
300
|
+
});
|
|
301
|
+
updated = true;
|
|
302
|
+
}
|
|
303
|
+
if (pluginConfig.embeddingModel && pluginConfig.embeddingModel !== '') {
|
|
304
|
+
updates.push({ section: 'embedding', key: 'model_name', value: pluginConfig.embeddingModel });
|
|
305
|
+
updated = true;
|
|
306
|
+
}
|
|
307
|
+
if (!updated) {
|
|
308
|
+
return { updated: false, path: configPath };
|
|
309
|
+
}
|
|
310
|
+
// Read current content
|
|
311
|
+
let content = fs.readFileSync(configPath, 'utf-8');
|
|
312
|
+
// Apply each update
|
|
313
|
+
for (const { section, key, value } of updates) {
|
|
314
|
+
// Escape value for TOML string
|
|
315
|
+
const escapedValue = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
316
|
+
// Pattern to match the key in the correct section
|
|
317
|
+
// This handles both existing keys and missing keys
|
|
318
|
+
const sectionPattern = new RegExp(`(\\[${section}\\][^\\[]*?)(${key}\\s*=\\s*)"[^"]*"`, 's');
|
|
319
|
+
const keyExistsInSection = sectionPattern.test(content);
|
|
320
|
+
if (keyExistsInSection) {
|
|
321
|
+
// Update existing key
|
|
322
|
+
content = content.replace(sectionPattern, `$1$2"${escapedValue}"`);
|
|
323
|
+
} else {
|
|
324
|
+
// Add key to section
|
|
325
|
+
const sectionStartPattern = new RegExp(`(\\[${section}\\]\\n)`, '');
|
|
326
|
+
if (sectionStartPattern.test(content)) {
|
|
327
|
+
content = content.replace(sectionStartPattern, `$1${key} = "${escapedValue}"\n`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Write updated content
|
|
332
|
+
fs.writeFileSync(configPath, content, 'utf-8');
|
|
333
|
+
return { updated: true, path: configPath };
|
|
321
334
|
}
|
|
322
335
|
/**
|
|
323
336
|
* Merge plugin config with file config, preferring plugin config values
|
|
324
337
|
*/
|
|
325
338
|
function mergeConfigWithPlugin(fileConfig, pluginConfig) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
return {
|
|
340
|
+
...fileConfig,
|
|
341
|
+
llm: {
|
|
342
|
+
...fileConfig.llm,
|
|
343
|
+
api_base_url: pluginConfig.llmApiBaseUrl || fileConfig.llm.api_base_url,
|
|
344
|
+
api_key: pluginConfig.llmApiKey || fileConfig.llm.api_key,
|
|
345
|
+
model_efficient: pluginConfig.llmModel || fileConfig.llm.model_efficient
|
|
346
|
+
},
|
|
347
|
+
embedding: {
|
|
348
|
+
...fileConfig.embedding,
|
|
349
|
+
api_base_url: pluginConfig.embeddingApiBaseUrl || fileConfig.embedding.api_base_url,
|
|
350
|
+
api_key: pluginConfig.embeddingApiKey || fileConfig.embedding.api_key,
|
|
351
|
+
model_name: pluginConfig.embeddingModel || fileConfig.embedding.model_name
|
|
352
|
+
}
|
|
353
|
+
};
|
|
341
354
|
}
|
|
342
|
-
//# sourceMappingURL=config.js.map
|
|
355
|
+
//# sourceMappingURL=config.js.map
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "memclaw",
|
|
3
3
|
"name": "MemClaw",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.17",
|
|
5
5
|
"description": "Layered semantic memory for OpenClaw with L0/L1/L2 tiered retrieval, easy setup, and migration from native memory",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"skills": ["skills/memclaw", "skills/memclaw-maintance"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memclaw/memclaw",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.17",
|
|
4
4
|
"description": "MemClaw - The Cortex Memory plugin for OpenClaw. Layered semantic memory for OpenClaw with easy setup and migration",
|
|
5
5
|
"homepage": "https://github.com/sopaco/cortex-mem",
|
|
6
6
|
"repository": {
|
|
@@ -145,6 +145,7 @@ timeout_secs = 30
|
|
|
145
145
|
[server]
|
|
146
146
|
host = "localhost"
|
|
147
147
|
port = 8085
|
|
148
|
+
cors_origins = ["*"]
|
|
148
149
|
|
|
149
150
|
# Logging Configuration
|
|
150
151
|
[logging]
|
|
@@ -174,11 +175,6 @@ Check that Qdrant and cortex-mem-service are accessible:
|
|
|
174
175
|
|
|
175
176
|
If `autoStartServices` is `true` in plugin config, MemClaw will start Qdrant automatically.
|
|
176
177
|
|
|
177
|
-
To start manually, run the Qdrant binary from the platform package with:
|
|
178
|
-
- `--storage-path` pointing to a storage directory
|
|
179
|
-
- `--http-port 6333`
|
|
180
|
-
- `--grpc-port 6334`
|
|
181
|
-
|
|
182
178
|
**Starting cortex-mem-service:**
|
|
183
179
|
|
|
184
180
|
**CRITICAL**: cortex-mem-service MUST be started with `--data-dir` flag pointing to the directory containing `config.toml`.
|
package/skills/memclaw/SKILL.md
CHANGED
|
@@ -34,12 +34,7 @@ The search engine queries all three tiers internally and returns unified results
|
|
|
34
34
|
|
|
35
35
|
## Configuration
|
|
36
36
|
|
|
37
|
-
All configuration is managed through OpenClaw plugin settings.
|
|
38
|
-
|
|
39
|
-
1. Open OpenClaw settings (`openclaw.json` or via UI)
|
|
40
|
-
2. Navigate to Plugins → MemClaw → Configuration
|
|
41
|
-
3. Verify or update the required fields
|
|
42
|
-
4. Save changes and **restart OpenClaw Gateway** for changes to take effect
|
|
37
|
+
All configuration is managed through OpenClaw plugin settings. However, when the plugin is first used, incomplete configuration items may cause it to fail. If the plugin or tools cannot be used, proactively inform the user and assist in completing the necessary configurations. For details, refer to the 'Troubleshooting' section below.
|
|
43
38
|
|
|
44
39
|
## Usage Guide
|
|
45
40
|
|
|
@@ -88,11 +83,12 @@ Open OpenClaw settings and verify MemClaw plugin configuration:
|
|
|
88
83
|
|
|
89
84
|
1. Open `openclaw.json` or navigate to Settings → Plugins → MemClaw
|
|
90
85
|
2. Ensure all required fields are correctly filled, especially the configuration sections related to LLM and Embedding.
|
|
91
|
-
3.
|
|
86
|
+
3. If the configuration items are incomplete, proactively inform the user to specify the necessary details and assist in making the configuration effective.
|
|
87
|
+
4. Save changes and **restart OpenClaw Gateway** for changes to take effect
|
|
92
88
|
|
|
93
89
|
### Step 2: Restart OpenClaw Gateway
|
|
94
90
|
|
|
95
|
-
After making configuration changes, **you MUST restart OpenClaw Gateway** for the changes to take effect.
|
|
91
|
+
After making configuration changes and saved, **you MUST restart OpenClaw Gateway** for the changes to take effect.
|
|
96
92
|
|
|
97
93
|
### Step 3: Verify Services
|
|
98
94
|
|
|
@@ -106,6 +102,14 @@ If issues persist after restart:
|
|
|
106
102
|
| Service connection errors | Verify `serviceUrl` is correct; check if services are running |
|
|
107
103
|
| LLM/Embedding errors | Verify API URLs and credentials in plugin configuration; restart OpenClaw Gateway after changes |
|
|
108
104
|
|
|
105
|
+
Check that Qdrant and cortex-mem-service are accessible:
|
|
106
|
+
> Note: MemClaw does not require users to install any Docker environment. All dependencies are prepared during the openclaw's memclaw plugin installation.
|
|
107
|
+
|
|
108
|
+
| Service | Port | Health Check |
|
|
109
|
+
|---------|------|--------------|
|
|
110
|
+
| Qdrant | 6333 (HTTP), 6334 (gRPC) | HTTP GET to `http://localhost:6333` should return Qdrant version info |
|
|
111
|
+
| cortex-mem-service | 8085 | HTTP GET to `http://localhost:8085/health` should return `{"status":"ok"}` |
|
|
112
|
+
|
|
109
113
|
## References
|
|
110
114
|
|
|
111
115
|
- **`references/best-practices.md`** — Tool selection, session lifecycle, search strategies, and common pitfalls
|
|
@@ -105,6 +105,15 @@ Restart OpenClaw to activate the plugin and start services.
|
|
|
105
105
|
|
|
106
106
|
After restarting, MemClaw will automatically start the required services. If configured correctly, you should be able to use the memory tools normally.
|
|
107
107
|
|
|
108
|
+
Check that Qdrant and cortex-mem-service are accessible:
|
|
109
|
+
|
|
110
|
+
> Note: MemClaw does not require users to install any Docker environment. All dependencies are prepared during the openclaw's memclaw plugin installation.
|
|
111
|
+
|
|
112
|
+
| Service | Port | Health Check |
|
|
113
|
+
|---------|------|--------------|
|
|
114
|
+
| Qdrant | 6333 (HTTP), 6334 (gRPC) | HTTP GET to `http://localhost:6333` should return Qdrant version info |
|
|
115
|
+
| cortex-mem-service | 8085 | HTTP GET to `http://localhost:8085/health` should return `{"status":"ok"}` |
|
|
116
|
+
|
|
108
117
|
### Migrate Existing Memories (Optional)
|
|
109
118
|
|
|
110
119
|
If the user has existing OpenClaw native memories, call the `cortex_migrate` tool to migrate them to MemClaw:
|