@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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # MemClaw
2
2
 
3
+ [![License](https://img.shields.io/badge/license-MIT-green)](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** | ≥ 22.0.0 |
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
- ### Configure OpenClaw
120
+ ## Configuration
121
+
122
+ ### Plugin Configuration
118
123
 
119
- Edit your `openclaw.json`:
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
- ### Configure LLM
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
- Edit the configuration file and fill in required fields:
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
- ```toml
157
- [llm]
158
- api_key = "xxx" # REQUIRED: Your LLM API key
175
+ ### Configuration via UI
159
176
 
160
- [embedding]
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
- Then restart OpenClaw.
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
- ## Configuration Options
241
+ ### cortex_maintenance
222
242
 
223
- | Option | Type | Default | Description |
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
- 1. **Need to find something** → `cortex_search`
235
- 2. **Need more context** → `cortex_recall`
236
- 3. **Save important information** `cortex_add_memory`
237
- 4. **Conversation complete** `cortex_close_session`
238
- 5. **First time setup** `cortex_migrate`
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 `api_key` fields are filled in config.toml
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. Check service health with `cortex-mem-cli stats`
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
- "use strict";
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, "__esModule", { value: true });
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("./plugin-impl.js");
32
+ const plugin_impl_js_1 = require('./plugin-impl.js');
33
33
  // Default export - main plugin function
34
34
  function memclawPlugin(api) {
35
- return (0, plugin_impl_js_1.createPlugin)(api);
35
+ return (0, plugin_impl_js_1.createPlugin)(api);
36
36
  }
37
37
  // Named export - object style registration
38
38
  exports.plugin = {
39
- id: 'memclaw',
40
- name: 'MemClaw',
41
- version: '0.9.16',
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
- }
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
@@ -1,44 +1,63 @@
1
- "use strict";
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 = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
- Object.defineProperty(exports, "__esModule", { value: true });
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("fs"));
52
- const path = __importStar(require("path"));
53
- const os = __importStar(require("os"));
54
- const child_process_1 = require("child_process");
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
- const platform = process.platform;
58
- if (platform === 'win32') {
59
- return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'memclaw');
60
- }
61
- else if (platform === 'darwin') {
62
- return path.join(os.homedir(), 'Library', 'Application Support', 'memclaw');
63
- }
64
- else {
65
- return path.join(os.homedir(), '.local', 'share', 'memclaw');
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
- return path.join(getDataDir(), 'config.toml');
89
+ return path.join(getDataDir(), 'config.toml');
70
90
  }
71
91
  function generateConfigTemplate() {
72
- return `# MemClaw Configuration
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
- const dataDir = getDataDir();
117
- const configPath = getConfigPath();
118
- if (!fs.existsSync(dataDir)) {
119
- fs.mkdirSync(dataDir, { recursive: true });
120
- }
121
- if (!fs.existsSync(configPath)) {
122
- const template = generateConfigTemplate();
123
- fs.writeFileSync(configPath, template, 'utf-8');
124
- return { created: true, path: configPath };
125
- }
126
- return { created: false, path: configPath };
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
- return new Promise((resolve, reject) => {
130
- const platform = process.platform;
131
- let command;
132
- let args = [];
133
- if (platform === 'win32') {
134
- command = 'cmd';
135
- args = ['/c', 'start', '""', configPath];
136
- }
137
- else if (platform === 'darwin') {
138
- command = 'open';
139
- args = [configPath];
140
- }
141
- else {
142
- command = 'xdg-open';
143
- args = [configPath];
144
- }
145
- const proc = (0, child_process_1.spawn)(command, args, { detached: true, stdio: 'ignore' });
146
- proc.on('error', (err) => {
147
- reject(err);
148
- });
149
- proc.unref();
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
- const content = fs.readFileSync(configPath, 'utf-8');
155
- const config = {};
156
- let currentSection = '';
157
- for (const line of content.split('\n')) {
158
- const trimmed = line.trim();
159
- // Skip comments and empty lines
160
- if (trimmed.startsWith('#') || trimmed === '')
161
- continue;
162
- // Section header
163
- const sectionMatch = trimmed.match(/^\[(\w+)\]$/);
164
- if (sectionMatch) {
165
- currentSection = sectionMatch[1];
166
- config[currentSection] = {};
167
- continue;
168
- }
169
- // Key-value pair
170
- const kvMatch = trimmed.match(/^(\w+)\s*=\s*"([^"]*)"(?:\s*$|\s*#)/) ||
171
- trimmed.match(/^(\w+)\s*=\s*(\d+(?:\.\d+)?)(?:\s*$|\s*#)/) ||
172
- trimmed.match(/^(\w+)\s*=\s*(true|false)(?:\s*$|\s*#)/);
173
- if (kvMatch && currentSection) {
174
- const key = kvMatch[1];
175
- let value = kvMatch[2];
176
- // Convert to appropriate type
177
- if (value === 'true')
178
- value = true;
179
- else if (value === 'false')
180
- value = false;
181
- else if (/^\d+$/.test(value))
182
- value = parseInt(value, 10);
183
- else if (/^\d+\.\d+$/.test(value))
184
- value = parseFloat(value);
185
- config[currentSection] = config[currentSection] || {};
186
- config[currentSection][key] = value;
187
- }
188
- }
189
- // Apply defaults
190
- return {
191
- qdrant: {
192
- url: 'http://localhost:6334',
193
- collection_name: 'memclaw',
194
- timeout_secs: 30,
195
- ...(config.qdrant || {})
196
- },
197
- llm: {
198
- api_base_url: 'https://api.openai.com/v1',
199
- api_key: '',
200
- model_efficient: 'gpt-5-mini',
201
- temperature: 0.1,
202
- max_tokens: 4096,
203
- ...(config.llm || {})
204
- },
205
- embedding: {
206
- api_base_url: 'https://api.openai.com/v1',
207
- api_key: '',
208
- model_name: 'text-embedding-3-small',
209
- batch_size: 10,
210
- timeout_secs: 30,
211
- ...(config.embedding || {})
212
- },
213
- server: {
214
- host: 'localhost',
215
- port: 8085,
216
- ...(config.server || {})
217
- },
218
- logging: {
219
- enabled: false,
220
- log_directory: 'logs',
221
- level: 'info',
222
- ...(config.logging || {})
223
- },
224
- cortex: {
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
- const errors = [];
232
- if (!config.llm.api_key || config.llm.api_key === '') {
233
- errors.push('llm.api_key is required');
234
- }
235
- if (!config.embedding.api_key || config.embedding.api_key === '') {
236
- // Allow using llm.api_key for embedding if not specified
237
- if (config.llm.api_key && config.llm.api_key !== '') {
238
- config.embedding.api_key = config.llm.api_key;
239
- }
240
- else {
241
- errors.push('embedding.api_key is required');
242
- }
243
- }
244
- return {
245
- valid: errors.length === 0,
246
- errors
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
- const configPath = getConfigPath();
255
- // Ensure config file exists
256
- ensureConfigExists();
257
- // Parse existing config
258
- const existingConfig = parseConfig(configPath);
259
- // Track if any changes were made
260
- let updated = false;
261
- // Build updated config sections
262
- const updates = [];
263
- // LLM config updates
264
- if (pluginConfig.llmApiKey && pluginConfig.llmApiKey !== '') {
265
- updates.push({ section: 'llm', key: 'api_key', value: pluginConfig.llmApiKey });
266
- updated = true;
267
- }
268
- if (pluginConfig.llmApiBaseUrl && pluginConfig.llmApiBaseUrl !== '') {
269
- updates.push({ section: 'llm', key: 'api_base_url', value: pluginConfig.llmApiBaseUrl });
270
- updated = true;
271
- }
272
- if (pluginConfig.llmModel && pluginConfig.llmModel !== '') {
273
- updates.push({ section: 'llm', key: 'model_efficient', value: pluginConfig.llmModel });
274
- updated = true;
275
- }
276
- // Embedding config updates
277
- if (pluginConfig.embeddingApiKey && pluginConfig.embeddingApiKey !== '') {
278
- updates.push({ section: 'embedding', key: 'api_key', value: pluginConfig.embeddingApiKey });
279
- updated = true;
280
- }
281
- if (pluginConfig.embeddingApiBaseUrl && pluginConfig.embeddingApiBaseUrl !== '') {
282
- updates.push({
283
- section: 'embedding',
284
- key: 'api_base_url',
285
- value: pluginConfig.embeddingApiBaseUrl
286
- });
287
- updated = true;
288
- }
289
- if (pluginConfig.embeddingModel && pluginConfig.embeddingModel !== '') {
290
- updates.push({ section: 'embedding', key: 'model_name', value: pluginConfig.embeddingModel });
291
- updated = true;
292
- }
293
- if (!updated) {
294
- return { updated: false, path: configPath };
295
- }
296
- // Read current content
297
- let content = fs.readFileSync(configPath, 'utf-8');
298
- // Apply each update
299
- for (const { section, key, value } of updates) {
300
- // Escape value for TOML string
301
- const escapedValue = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
302
- // Pattern to match the key in the correct section
303
- // This handles both existing keys and missing keys
304
- const sectionPattern = new RegExp(`(\\[${section}\\][^\\[]*?)(${key}\\s*=\\s*)"[^"]*"`, 's');
305
- const keyExistsInSection = sectionPattern.test(content);
306
- if (keyExistsInSection) {
307
- // Update existing key
308
- content = content.replace(sectionPattern, `$1$2"${escapedValue}"`);
309
- }
310
- else {
311
- // Add key to section
312
- const sectionStartPattern = new RegExp(`(\\[${section}\\]\\n)`, '');
313
- if (sectionStartPattern.test(content)) {
314
- content = content.replace(sectionStartPattern, `$1${key} = "${escapedValue}"\n`);
315
- }
316
- }
317
- }
318
- // Write updated content
319
- fs.writeFileSync(configPath, content, 'utf-8');
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
- return {
327
- ...fileConfig,
328
- llm: {
329
- ...fileConfig.llm,
330
- api_base_url: pluginConfig.llmApiBaseUrl || fileConfig.llm.api_base_url,
331
- api_key: pluginConfig.llmApiKey || fileConfig.llm.api_key,
332
- model_efficient: pluginConfig.llmModel || fileConfig.llm.model_efficient
333
- },
334
- embedding: {
335
- ...fileConfig.embedding,
336
- api_base_url: pluginConfig.embeddingApiBaseUrl || fileConfig.embedding.api_base_url,
337
- api_key: pluginConfig.embeddingApiKey || fileConfig.embedding.api_key,
338
- model_name: pluginConfig.embeddingModel || fileConfig.embedding.model_name
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "memclaw",
3
3
  "name": "MemClaw",
4
- "version": "0.9.16",
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.16",
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`.
@@ -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. To view or modify:
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. Save the configuration file
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: