@memtensor/memos-cloud-openclaw-plugin 0.1.10 → 0.1.11-beta.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/README.md +136 -1
- package/README_ZH.md +136 -1
- package/clawdbot.plugin.json +115 -1
- package/index.js +54 -18
- package/lib/memos-cloud-api.js +68 -3
- package/moltbot.plugin.json +115 -1
- package/openclaw.plugin.json +115 -1
- package/package.json +1 -1
- package/test/direct-session-user-id.test.mjs +94 -0
package/README.md
CHANGED
|
@@ -55,7 +55,8 @@ Make sure it’s enabled in `~/.openclaw/openclaw.json`:
|
|
|
55
55
|
Restart the gateway after config changes.
|
|
56
56
|
|
|
57
57
|
## Environment Variables
|
|
58
|
-
The plugin
|
|
58
|
+
The plugin resolves runtime config in this order: **plugin config → env files → process environment**.
|
|
59
|
+
Among env files, it tries them in order (**openclaw → moltbot → clawdbot**). For each key, the first file with a value wins.
|
|
59
60
|
If none of these files exist (or the key is missing), it falls back to the process environment.
|
|
60
61
|
|
|
61
62
|
**Where to configure**
|
|
@@ -91,9 +92,14 @@ MEMOS_API_KEY=YOUR_TOKEN
|
|
|
91
92
|
- `MEMOS_BASE_URL` (default: `https://memos.memtensor.cn/api/openmem/v1`)
|
|
92
93
|
- `MEMOS_API_KEY` (required; Token auth) — get it at https://memos-dashboard.openmem.net/cn/apikeys/
|
|
93
94
|
- `MEMOS_USER_ID` (optional; default: `openclaw-user`)
|
|
95
|
+
- `MEMOS_USE_DIRECT_SESSION_USER_ID` (default: `false`; when enabled, direct session keys like `agent:main:<provider>:direct:<peer-id>` use `<peer-id>` as MemOS `user_id`)
|
|
94
96
|
- `MEMOS_CONVERSATION_ID` (optional override)
|
|
97
|
+
- `MEMOS_KNOWLEDGEBASE_IDS` (optional; comma-separated global knowledge base IDs for `/search/memory`, e.g., `"kb-123, kb-456"`)
|
|
98
|
+
- `MEMOS_ALLOW_KNOWLEDGEBASE_IDS` (optional; comma-separated knowledge base IDs for `/add/message`, e.g., `"kb-123"`)
|
|
99
|
+
- `MEMOS_TAGS` (optional; comma-separated tags for `/add/message`, default: `"openclaw"`, e.g., `"openclaw, dev"`)
|
|
95
100
|
- `MEMOS_RECALL_GLOBAL` (default: `true`; when true, search does **not** pass conversation_id)
|
|
96
101
|
- `MEMOS_MULTI_AGENT_MODE` (default: `false`; enable multi-agent data isolation)
|
|
102
|
+
- `MEMOS_ALLOWED_AGENTS` (optional; comma-separated allowlist for multi-agent mode, e.g. `"agent1,agent2"`; empty means all agents enabled)
|
|
97
103
|
- `MEMOS_CONVERSATION_PREFIX` / `MEMOS_CONVERSATION_SUFFIX` (optional)
|
|
98
104
|
- `MEMOS_CONVERSATION_SUFFIX_MODE` (`none` | `counter`, default: `none`)
|
|
99
105
|
- `MEMOS_CONVERSATION_RESET_ON_NEW` (default: `true`, requires hooks.internal.enabled)
|
|
@@ -119,6 +125,7 @@ In `plugins.entries.memos-cloud-openclaw-plugin.config`:
|
|
|
119
125
|
"baseUrl": "https://memos.memtensor.cn/api/openmem/v1",
|
|
120
126
|
"apiKey": "YOUR_API_KEY",
|
|
121
127
|
"userId": "memos_user_123",
|
|
128
|
+
"useDirectSessionUserId": false,
|
|
122
129
|
"conversationId": "openclaw-main",
|
|
123
130
|
"queryPrefix": "important user context preferences decisions ",
|
|
124
131
|
"recallEnabled": true,
|
|
@@ -141,6 +148,7 @@ In `plugins.entries.memos-cloud-openclaw-plugin.config`:
|
|
|
141
148
|
"tags": ["openclaw"],
|
|
142
149
|
"agentId": "",
|
|
143
150
|
"multiAgentMode": false,
|
|
151
|
+
"allowedAgents": [],
|
|
144
152
|
"asyncMode": true,
|
|
145
153
|
"recallFilterEnabled": false,
|
|
146
154
|
"recallFilterBaseUrl": "http://127.0.0.1:11434/v1",
|
|
@@ -174,6 +182,133 @@ The plugin provides native support for multi-agent architectures (via the `agent
|
|
|
174
182
|
- **Data Isolation**: The `agent_id` is automatically injected into both `/search/memory` and `/add/message` requests. This ensures completely isolated memory and message histories for different agents, even under the same user or session.
|
|
175
183
|
- **Static Override**: You can also force a specific agent ID by setting `"agentId": "your_agent_id"` in the plugin's `config`.
|
|
176
184
|
|
|
185
|
+
### Per-Agent Memory Toggle
|
|
186
|
+
|
|
187
|
+
In multi-agent mode, you can use `MEMOS_ALLOWED_AGENTS` to control exactly which agents have memory enabled. Agents not in the allowlist will skip both memory recall and memory capture entirely.
|
|
188
|
+
|
|
189
|
+
**Environment variable** (in `~/.openclaw/.env`):
|
|
190
|
+
```env
|
|
191
|
+
MEMOS_MULTI_AGENT_MODE=true
|
|
192
|
+
MEMOS_ALLOWED_AGENTS="agent1,agent2"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Separate multiple agent IDs with commas.
|
|
196
|
+
|
|
197
|
+
**Plugin config** (in `openclaw.json`):
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"plugins": {
|
|
201
|
+
"entries": {
|
|
202
|
+
"memos-cloud-openclaw-plugin": {
|
|
203
|
+
"enabled": true,
|
|
204
|
+
"config": {
|
|
205
|
+
"multiAgentMode": true,
|
|
206
|
+
"allowedAgents": ["agent1", "agent2"]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Behavior**:
|
|
215
|
+
| Config | Effect |
|
|
216
|
+
|--------|--------|
|
|
217
|
+
| `MEMOS_ALLOWED_AGENTS` unset or empty | All agents have memory enabled |
|
|
218
|
+
| `MEMOS_ALLOWED_AGENTS="agent1,agent2"` | Only `agent1` and `agent2` are enabled; others are skipped |
|
|
219
|
+
| `MEMOS_ALLOWED_AGENTS="agent1"` | Only `agent1` is enabled; all other agents are skipped |
|
|
220
|
+
| `MEMOS_MULTI_AGENT_MODE=false` | Allowlist has no effect; all requests use single-agent mode |
|
|
221
|
+
|
|
222
|
+
> **Note**: The allowlist only takes effect when `multiAgentMode=true`. When multi-agent mode is off, memory works for all agents and the allowlist is ignored.
|
|
223
|
+
|
|
224
|
+
### Per-Agent Configuration (agentOverrides)
|
|
225
|
+
|
|
226
|
+
Beyond simple on/off toggles, you can configure **different memory parameters for each agent** using `agentOverrides`. Each agent can have its own knowledge base, recall limits, relativity threshold, and more.
|
|
227
|
+
|
|
228
|
+
**Plugin config** (in `openclaw.json`):
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"plugins": {
|
|
232
|
+
"entries": {
|
|
233
|
+
"memos-cloud-openclaw-plugin": {
|
|
234
|
+
"enabled": true,
|
|
235
|
+
"config": {
|
|
236
|
+
"multiAgentMode": true,
|
|
237
|
+
"allowedAgents": ["default", "research-agent", "coding-agent"],
|
|
238
|
+
"knowledgebaseIds": [],
|
|
239
|
+
"memoryLimitNumber": 6,
|
|
240
|
+
"relativity": 0.45,
|
|
241
|
+
|
|
242
|
+
"agentOverrides": {
|
|
243
|
+
"research-agent": {
|
|
244
|
+
"knowledgebaseIds": ["kb-research-papers", "kb-academic"],
|
|
245
|
+
"memoryLimitNumber": 12,
|
|
246
|
+
"relativity": 0.3,
|
|
247
|
+
"includeToolMemory": true,
|
|
248
|
+
"captureStrategy": "full_session",
|
|
249
|
+
"queryPrefix": "research context: "
|
|
250
|
+
},
|
|
251
|
+
"coding-agent": {
|
|
252
|
+
"knowledgebaseIds": ["kb-codebase", "kb-api-docs"],
|
|
253
|
+
"memoryLimitNumber": 9,
|
|
254
|
+
"relativity": 0.5,
|
|
255
|
+
"addEnabled": false
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Environment variable** (in `~/.openclaw/.env`):
|
|
266
|
+
You can use `MEMOS_AGENT_OVERRIDES` to configure a JSON string to override global parameters. Note: `.env` configuration has a lower priority than `agentOverrides` in `openclaw.json`.
|
|
267
|
+
```env
|
|
268
|
+
MEMOS_AGENT_OVERRIDES='{"research-agent": {"memoryLimitNumber": 12, "relativity": 0.3}, "coding-agent": {"memoryLimitNumber": 9}}'
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**How it works**:
|
|
272
|
+
- Fields in `agentOverrides.<agentId>` override the global defaults for that specific agent.
|
|
273
|
+
- Only the fields you specify are overridden; all other parameters inherit from the global config.
|
|
274
|
+
- If no override exists for an agent, it uses the global config as-is.
|
|
275
|
+
|
|
276
|
+
**Overridable fields**:
|
|
277
|
+
|
|
278
|
+
| Field | Description |
|
|
279
|
+
|-------|-------------|
|
|
280
|
+
| `knowledgebaseIds` | Knowledge base IDs for `/search/memory` |
|
|
281
|
+
| `memoryLimitNumber` | Max memory items to recall |
|
|
282
|
+
| `preferenceLimitNumber` | Max preference items to recall |
|
|
283
|
+
| `includePreference` | Enable preference recall |
|
|
284
|
+
| `includeToolMemory` | Enable tool memory recall |
|
|
285
|
+
| `toolMemoryLimitNumber` | Max tool memory items |
|
|
286
|
+
| `relativity` | Relevance threshold (0-1) |
|
|
287
|
+
| `recallEnabled` | Enable/disable recall for this agent |
|
|
288
|
+
| `addEnabled` | Enable/disable memory capture for this agent |
|
|
289
|
+
| `captureStrategy` | `last_turn` or `full_session` |
|
|
290
|
+
| `queryPrefix` | Prefix for search queries |
|
|
291
|
+
| `maxItemChars` | Max chars per memory item in prompt |
|
|
292
|
+
| `maxMessageChars` | Max chars per message when adding |
|
|
293
|
+
| `includeAssistant` | Include assistant messages in capture |
|
|
294
|
+
| `recallGlobal` | Global recall (skip conversation_id) |
|
|
295
|
+
| `recallFilterEnabled` | Enable model-based recall filtering |
|
|
296
|
+
| `recallFilterModel` | Model for recall filtering |
|
|
297
|
+
| `recallFilterBaseUrl` | Base URL for recall filter model |
|
|
298
|
+
| `recallFilterApiKey` | API key for recall filter |
|
|
299
|
+
| `allowKnowledgebaseIds` | Knowledge bases for `/add/message` |
|
|
300
|
+
| `tags` | Tags for `/add/message` |
|
|
301
|
+
| `throttleMs` | Throttle interval |
|
|
302
|
+
|
|
303
|
+
## Direct Session User ID
|
|
304
|
+
- **Default behavior**: the plugin still uses the configured `userId` (or `MEMOS_USER_ID`) and stays fully backward compatible.
|
|
305
|
+
- **Enable mode**: set `"useDirectSessionUserId": true` in plugin config or `MEMOS_USE_DIRECT_SESSION_USER_ID=true` in env.
|
|
306
|
+
- **What it does**: when enabled, session keys like `agent:main:<provider>:direct:<peer-id>` reuse `<peer-id>` as MemOS `user_id`.
|
|
307
|
+
- **What it does not do**: non-direct session keys such as `agent:main:<provider>:channel:<channel-id>` keep using the configured fallback `userId`.
|
|
308
|
+
- **Request paths affected**: the same resolver is used by both `buildSearchPayload()` and `buildAddMessagePayload()`, so recall and add stay consistent.
|
|
309
|
+
- **Config precedence**: runtime config still follows the same rule as the rest of the plugin - plugin config first, then `.env` files (`~/.openclaw/.env` -> `~/.moltbot/.env` -> `~/.clawdbot/.env`), then process env.
|
|
310
|
+
|
|
311
|
+
|
|
177
312
|
## Notes
|
|
178
313
|
- `conversation_id` defaults to OpenClaw `sessionKey` (unless `conversationId` is provided). **TODO**: consider binding to OpenClaw `sessionId` directly.
|
|
179
314
|
- Optional **prefix/suffix** via env or config; `conversationSuffixMode=counter` increments on `/new` (requires `hooks.internal.enabled`).
|
package/README_ZH.md
CHANGED
|
@@ -57,7 +57,8 @@ openclaw gateway restart
|
|
|
57
57
|
修改配置后需要重启 gateway。
|
|
58
58
|
|
|
59
59
|
## 环境变量
|
|
60
|
-
|
|
60
|
+
插件运行时配置的优先级是:**插件 config → env 文件 → 进程环境变量**。
|
|
61
|
+
在 env 文件层,按顺序读取(**openclaw → moltbot → clawdbot**),每个键优先使用最先匹配到的值。
|
|
61
62
|
若三个文件都不存在(或该键未找到),才会回退到进程环境变量。
|
|
62
63
|
|
|
63
64
|
**配置位置**
|
|
@@ -93,9 +94,14 @@ MEMOS_API_KEY=YOUR_TOKEN
|
|
|
93
94
|
- `MEMOS_BASE_URL`(默认 `https://memos.memtensor.cn/api/openmem/v1`)
|
|
94
95
|
- `MEMOS_API_KEY`(必填,Token 认证)—— 获取地址:https://memos-dashboard.openmem.net/cn/apikeys/
|
|
95
96
|
- `MEMOS_USER_ID`(可选,默认 `openclaw-user`)
|
|
97
|
+
- `MEMOS_USE_DIRECT_SESSION_USER_ID`(默认 `false`;开启后,对 `agent:main:<provider>:direct:<peer-id>` 这类私聊 sessionKey,会把 `<peer-id>` 作为 MemOS `user_id`)
|
|
96
98
|
- `MEMOS_CONVERSATION_ID`(可选覆盖)
|
|
99
|
+
- `MEMOS_KNOWLEDGEBASE_IDS`(可选;逗号分隔的全局知识库 ID 列表,用于 `/search/memory`,例如:`"kb-123, kb-456"`)
|
|
100
|
+
- `MEMOS_ALLOW_KNOWLEDGEBASE_IDS`(可选;逗号分隔的知识库 ID 列表,用于 `/add/message`,例如:`"kb-123"`)
|
|
101
|
+
- `MEMOS_TAGS`(可选;逗号分隔的标签列表,用于 `/add/message`,默认:`"openclaw"`,例如:`"openclaw, dev"`)
|
|
97
102
|
- `MEMOS_RECALL_GLOBAL`(默认 `true`;为 true 时检索不传 conversation_id)
|
|
98
103
|
- `MEMOS_MULTI_AGENT_MODE`(默认 `false`;是否开启多 Agent 数据隔离模式)
|
|
104
|
+
- `MEMOS_ALLOWED_AGENTS`(可选;多 Agent 模式下的白名单,逗号分隔,例如 `"agent1,agent2"`;为空则所有 Agent 均启用)
|
|
99
105
|
- `MEMOS_CONVERSATION_PREFIX` / `MEMOS_CONVERSATION_SUFFIX`(可选)
|
|
100
106
|
- `MEMOS_CONVERSATION_SUFFIX_MODE`(`none` | `counter`,默认 `none`)
|
|
101
107
|
- `MEMOS_CONVERSATION_RESET_ON_NEW`(默认 `true`,需 hooks.internal.enabled)
|
|
@@ -121,6 +127,7 @@ MEMOS_API_KEY=YOUR_TOKEN
|
|
|
121
127
|
"baseUrl": "https://memos.memtensor.cn/api/openmem/v1",
|
|
122
128
|
"apiKey": "YOUR_API_KEY",
|
|
123
129
|
"userId": "memos_user_123",
|
|
130
|
+
"useDirectSessionUserId": false,
|
|
124
131
|
"conversationId": "openclaw-main",
|
|
125
132
|
"queryPrefix": "important user context preferences decisions ",
|
|
126
133
|
"recallEnabled": true,
|
|
@@ -141,6 +148,7 @@ MEMOS_API_KEY=YOUR_TOKEN
|
|
|
141
148
|
"tags": ["openclaw"],
|
|
142
149
|
"agentId": "",
|
|
143
150
|
"multiAgentMode": false,
|
|
151
|
+
"allowedAgents": [],
|
|
144
152
|
"asyncMode": true,
|
|
145
153
|
"recallFilterEnabled": false,
|
|
146
154
|
"recallFilterBaseUrl": "http://127.0.0.1:11434/v1",
|
|
@@ -179,6 +187,133 @@ MEMOS_API_KEY=YOUR_TOKEN
|
|
|
179
187
|
- **数据隔离**:在调用 `/search/memory`(检索记忆)和 `/add/message`(添加记录)时会自动附带该 `agent_id`,从而保证即使是同一用户下的不同 Agent 之间,记忆和反馈数据也是完全隔离的。
|
|
180
188
|
- **静态配置**:如果需要,也可在上述插件的 `config` 中显式指定 `"agentId": "your_agent_id"` 作为固定值。
|
|
181
189
|
|
|
190
|
+
### 按 Agent 开关记忆插件
|
|
191
|
+
|
|
192
|
+
在多 Agent 模式下,可以通过 `MEMOS_ALLOWED_AGENTS` 精确控制哪些 Agent 启用记忆功能。未在白名单中的 Agent 将完全跳过记忆召回和记忆添加。
|
|
193
|
+
|
|
194
|
+
**环境变量配置**(在 `~/.openclaw/.env` 中设置):
|
|
195
|
+
```env
|
|
196
|
+
MEMOS_MULTI_AGENT_MODE=true
|
|
197
|
+
MEMOS_ALLOWED_AGENTS="agent1,agent2"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
多个 Agent ID 之间用英文逗号分隔。
|
|
201
|
+
|
|
202
|
+
**插件配置**(在 `openclaw.json` 中设置):
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"plugins": {
|
|
206
|
+
"entries": {
|
|
207
|
+
"memos-cloud-openclaw-plugin": {
|
|
208
|
+
"enabled": true,
|
|
209
|
+
"config": {
|
|
210
|
+
"multiAgentMode": true,
|
|
211
|
+
"allowedAgents": ["agent1", "agent2"]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**行为规则**:
|
|
220
|
+
| 配置 | 效果 |
|
|
221
|
+
|------|------|
|
|
222
|
+
| `MEMOS_ALLOWED_AGENTS` 未设置或为空 | 所有 Agent 均启用记忆 |
|
|
223
|
+
| `MEMOS_ALLOWED_AGENTS="agent1,agent2"` | 仅 `agent1` 和 `agent2` 启用,其余跳过 |
|
|
224
|
+
| `MEMOS_ALLOWED_AGENTS="agent1"` | 仅 `agent1` 启用,其他 Agent 均跳过 |
|
|
225
|
+
| `MEMOS_MULTI_AGENT_MODE=false` | 白名单不生效,所有请求按单 Agent 模式处理 |
|
|
226
|
+
|
|
227
|
+
> **注意**:白名单仅在 `multiAgentMode=true` 时生效。关闭多 Agent 模式时,所有 Agent 的记忆功能均正常工作,白名单配置被忽略。
|
|
228
|
+
|
|
229
|
+
### 按 Agent 独立配置参数(agentOverrides)
|
|
230
|
+
|
|
231
|
+
除了按 Agent 开关记忆功能外,你还可以通过 `agentOverrides` 为**每个 Agent 配置不同的记忆参数**,包括知识库、召回条数、相关性阈值等。
|
|
232
|
+
|
|
233
|
+
**插件配置**(在 `openclaw.json` 中设置):
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"plugins": {
|
|
237
|
+
"entries": {
|
|
238
|
+
"memos-cloud-openclaw-plugin": {
|
|
239
|
+
"enabled": true,
|
|
240
|
+
"config": {
|
|
241
|
+
"multiAgentMode": true,
|
|
242
|
+
"allowedAgents": ["default", "research-agent", "coding-agent"],
|
|
243
|
+
"knowledgebaseIds": [],
|
|
244
|
+
"memoryLimitNumber": 6,
|
|
245
|
+
"relativity": 0.45,
|
|
246
|
+
|
|
247
|
+
"agentOverrides": {
|
|
248
|
+
"research-agent": {
|
|
249
|
+
"knowledgebaseIds": ["kb-research-papers", "kb-academic"],
|
|
250
|
+
"memoryLimitNumber": 12,
|
|
251
|
+
"relativity": 0.3,
|
|
252
|
+
"includeToolMemory": true,
|
|
253
|
+
"captureStrategy": "full_session",
|
|
254
|
+
"queryPrefix": "research context: "
|
|
255
|
+
},
|
|
256
|
+
"coding-agent": {
|
|
257
|
+
"knowledgebaseIds": ["kb-codebase", "kb-api-docs"],
|
|
258
|
+
"memoryLimitNumber": 9,
|
|
259
|
+
"relativity": 0.5,
|
|
260
|
+
"addEnabled": false
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**环境变量配置**(在 `~/.openclaw/.env` 中设置):
|
|
271
|
+
你可以使用 `MEMOS_AGENT_OVERRIDES` 来配置一个 JSON 字符串,覆盖全局参数。注意:`.env` 中的配置优先级低于 `openclaw.json` 中的 `agentOverrides` 配置。
|
|
272
|
+
```env
|
|
273
|
+
MEMOS_AGENT_OVERRIDES='{"research-agent": {"memoryLimitNumber": 12, "relativity": 0.3}, "coding-agent": {"memoryLimitNumber": 9}}'
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**工作原理**:
|
|
277
|
+
- `agentOverrides.<agentId>` 中的字段会覆盖该 Agent 对应的全局默认值
|
|
278
|
+
- 只需写需要覆盖的字段,其余参数从全局配置继承
|
|
279
|
+
- 若某个 Agent 没有对应的 override 条目,则完全使用全局配置
|
|
280
|
+
|
|
281
|
+
**可覆盖字段**:
|
|
282
|
+
|
|
283
|
+
| 字段 | 说明 |
|
|
284
|
+
|------|------|
|
|
285
|
+
| `knowledgebaseIds` | `/search/memory` 使用的知识库 ID 列表 |
|
|
286
|
+
| `memoryLimitNumber` | 召回的事实记忆最大条数 |
|
|
287
|
+
| `preferenceLimitNumber` | 召回的偏好记忆最大条数 |
|
|
288
|
+
| `includePreference` | 是否启用偏好记忆召回 |
|
|
289
|
+
| `includeToolMemory` | 是否启用工具记忆召回 |
|
|
290
|
+
| `toolMemoryLimitNumber` | 工具记忆最大条数 |
|
|
291
|
+
| `relativity` | 相关性阈值(0-1) |
|
|
292
|
+
| `recallEnabled` | 该 Agent 是否启用记忆检索 |
|
|
293
|
+
| `addEnabled` | 该 Agent 是否启用记忆写入 |
|
|
294
|
+
| `captureStrategy` | `last_turn` 或 `full_session` |
|
|
295
|
+
| `queryPrefix` | 搜索查询前缀 |
|
|
296
|
+
| `maxItemChars` | 注入 prompt 时每条记忆的最大字符数 |
|
|
297
|
+
| `maxMessageChars` | 写入记忆时每条消息的最大字符数 |
|
|
298
|
+
| `includeAssistant` | 写入记忆时是否包含助手回复 |
|
|
299
|
+
| `recallGlobal` | 全局召回(不传 conversation_id) |
|
|
300
|
+
| `recallFilterEnabled` | 是否启用模型二次过滤 |
|
|
301
|
+
| `recallFilterModel` | 过滤模型名 |
|
|
302
|
+
| `recallFilterBaseUrl` | 过滤模型接口地址 |
|
|
303
|
+
| `recallFilterApiKey` | 过滤模型鉴权密钥 |
|
|
304
|
+
| `allowKnowledgebaseIds` | `/add/message` 允许写入的知识库 |
|
|
305
|
+
| `tags` | `/add/message` 标签 |
|
|
306
|
+
| `throttleMs` | 请求节流间隔 |
|
|
307
|
+
|
|
308
|
+
## 私聊 Session User ID(Direct Session User ID)
|
|
309
|
+
- **默认行为**:仍然使用配置里的 `userId`(或 `MEMOS_USER_ID`),完全兼容旧行为。
|
|
310
|
+
- **开启方式**:在插件 config 中设置 `"useDirectSessionUserId": true`,或在环境变量中设置 `MEMOS_USE_DIRECT_SESSION_USER_ID=true`。
|
|
311
|
+
- **行为说明**:开启后,像 `agent:main:<provider>:direct:<peer-id>` 这样的私聊 sessionKey,会把 `<peer-id>` 当作 MemOS `user_id`。
|
|
312
|
+
- **不会影响的场景**:像 `agent:main:<provider>:channel:<channel-id>` 这类非私聊 sessionKey,仍继续使用配置好的 fallback `userId`。
|
|
313
|
+
- **作用范围**:同一套解析逻辑同时作用于 `buildSearchPayload()` 和 `buildAddMessagePayload()`,保证 recall 与 add 一致。
|
|
314
|
+
- **配置优先级**:仍遵循插件现有规则——插件 config 优先,其次是 `.env` 文件(`~/.openclaw/.env` -> `~/.moltbot/.env` -> `~/.clawdbot/.env`),最后才回退到进程环境变量。
|
|
315
|
+
|
|
316
|
+
|
|
182
317
|
## 说明
|
|
183
318
|
- 未显式指定 `conversation_id` 时,默认使用 OpenClaw `sessionKey`。**TODO**:后续考虑直接绑定 OpenClaw `sessionId`。
|
|
184
319
|
- 可配置前后缀;`conversationSuffixMode=counter` 时会在 `/new` 递增(需 `hooks.internal.enabled`)。
|
package/clawdbot.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "memos-cloud-openclaw-plugin",
|
|
3
3
|
"name": "MemOS Cloud OpenClaw Plugin",
|
|
4
4
|
"description": "MemOS Cloud recall + add memory via lifecycle hooks",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.11-beta.0",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
],
|
|
42
42
|
"default": "none"
|
|
43
43
|
},
|
|
44
|
+
"useDirectSessionUserId": {
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"description": "When enabled, direct-session keys like agent:main:<provider>:direct:<id> use the direct id as MemOS user_id instead of the default configured userId.",
|
|
47
|
+
"default": false
|
|
48
|
+
},
|
|
44
49
|
"resetOnNew": {
|
|
45
50
|
"type": "boolean",
|
|
46
51
|
"default": true
|
|
@@ -174,6 +179,13 @@
|
|
|
174
179
|
"type": "boolean",
|
|
175
180
|
"default": false
|
|
176
181
|
},
|
|
182
|
+
"allowedAgents": {
|
|
183
|
+
"type": "array",
|
|
184
|
+
"items": {
|
|
185
|
+
"type": "string"
|
|
186
|
+
},
|
|
187
|
+
"description": "When multiAgentMode is true, only these agent IDs will activate the memory plugin. Comma-separated in env var MEMOS_ALLOWED_AGENTS. Empty list means all agents are allowed."
|
|
188
|
+
},
|
|
177
189
|
"appId": {
|
|
178
190
|
"type": "string"
|
|
179
191
|
},
|
|
@@ -202,6 +214,108 @@
|
|
|
202
214
|
"throttleMs": {
|
|
203
215
|
"type": "integer",
|
|
204
216
|
"default": 0
|
|
217
|
+
},
|
|
218
|
+
"agentOverrides": {
|
|
219
|
+
"type": "object",
|
|
220
|
+
"description": "Per-agent config overrides. Keys are agent IDs, values override global defaults for that agent.",
|
|
221
|
+
"additionalProperties": {
|
|
222
|
+
"type": "object",
|
|
223
|
+
"properties": {
|
|
224
|
+
"knowledgebaseIds": {
|
|
225
|
+
"type": "array",
|
|
226
|
+
"items": {
|
|
227
|
+
"type": "string"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"memoryLimitNumber": {
|
|
231
|
+
"type": "integer"
|
|
232
|
+
},
|
|
233
|
+
"preferenceLimitNumber": {
|
|
234
|
+
"type": "integer"
|
|
235
|
+
},
|
|
236
|
+
"includePreference": {
|
|
237
|
+
"type": "boolean"
|
|
238
|
+
},
|
|
239
|
+
"includeToolMemory": {
|
|
240
|
+
"type": "boolean"
|
|
241
|
+
},
|
|
242
|
+
"toolMemoryLimitNumber": {
|
|
243
|
+
"type": "integer"
|
|
244
|
+
},
|
|
245
|
+
"includeSkill": {
|
|
246
|
+
"type": "boolean"
|
|
247
|
+
},
|
|
248
|
+
"skillLimitNumber": {
|
|
249
|
+
"type": "integer"
|
|
250
|
+
},
|
|
251
|
+
"relativity": {
|
|
252
|
+
"type": "number"
|
|
253
|
+
},
|
|
254
|
+
"filter": {
|
|
255
|
+
"type": "object",
|
|
256
|
+
"additionalProperties": true
|
|
257
|
+
},
|
|
258
|
+
"recallEnabled": {
|
|
259
|
+
"type": "boolean"
|
|
260
|
+
},
|
|
261
|
+
"addEnabled": {
|
|
262
|
+
"type": "boolean"
|
|
263
|
+
},
|
|
264
|
+
"captureStrategy": {
|
|
265
|
+
"type": "string",
|
|
266
|
+
"enum": [
|
|
267
|
+
"last_turn",
|
|
268
|
+
"full_session"
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
"queryPrefix": {
|
|
272
|
+
"type": "string"
|
|
273
|
+
},
|
|
274
|
+
"maxQueryChars": {
|
|
275
|
+
"type": "integer"
|
|
276
|
+
},
|
|
277
|
+
"maxItemChars": {
|
|
278
|
+
"type": "integer"
|
|
279
|
+
},
|
|
280
|
+
"maxMessageChars": {
|
|
281
|
+
"type": "integer"
|
|
282
|
+
},
|
|
283
|
+
"includeAssistant": {
|
|
284
|
+
"type": "boolean"
|
|
285
|
+
},
|
|
286
|
+
"recallGlobal": {
|
|
287
|
+
"type": "boolean"
|
|
288
|
+
},
|
|
289
|
+
"recallFilterEnabled": {
|
|
290
|
+
"type": "boolean"
|
|
291
|
+
},
|
|
292
|
+
"recallFilterModel": {
|
|
293
|
+
"type": "string"
|
|
294
|
+
},
|
|
295
|
+
"recallFilterBaseUrl": {
|
|
296
|
+
"type": "string"
|
|
297
|
+
},
|
|
298
|
+
"recallFilterApiKey": {
|
|
299
|
+
"type": "string"
|
|
300
|
+
},
|
|
301
|
+
"allowKnowledgebaseIds": {
|
|
302
|
+
"type": "array",
|
|
303
|
+
"items": {
|
|
304
|
+
"type": "string"
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"tags": {
|
|
308
|
+
"type": "array",
|
|
309
|
+
"items": {
|
|
310
|
+
"type": "string"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"throttleMs": {
|
|
314
|
+
"type": "integer"
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"additionalProperties": false
|
|
318
|
+
}
|
|
205
319
|
}
|
|
206
320
|
},
|
|
207
321
|
"additionalProperties": false
|
package/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
extractResultData,
|
|
6
6
|
extractText,
|
|
7
7
|
formatRecallHookResult,
|
|
8
|
+
isAgentAllowed,
|
|
9
|
+
resolveAgentConfig,
|
|
8
10
|
searchMemory,
|
|
9
11
|
stripOpenClawInjectedPrefix,
|
|
10
12
|
} from "./lib/memos-cloud-api.js";
|
|
@@ -53,6 +55,21 @@ function getEffectiveAgentId(cfg, ctx) {
|
|
|
53
55
|
return agentId === "main" ? undefined : agentId;
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
export function extractDirectSessionUserId(sessionKey) {
|
|
59
|
+
if (!sessionKey || typeof sessionKey !== "string") return "";
|
|
60
|
+
const parts = sessionKey.split(":");
|
|
61
|
+
const directIndex = parts.lastIndexOf("direct");
|
|
62
|
+
if (directIndex === -1) return "";
|
|
63
|
+
return parts[directIndex + 1] || "";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function resolveMemosUserId(cfg, ctx) {
|
|
67
|
+
const fallback = cfg?.userId || "openclaw-user";
|
|
68
|
+
if (!cfg?.useDirectSessionUserId) return fallback;
|
|
69
|
+
const directUserId = extractDirectSessionUserId(ctx?.sessionKey);
|
|
70
|
+
return directUserId || fallback;
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
function resolveConversationId(cfg, ctx) {
|
|
57
74
|
if (cfg.conversationId) return cfg.conversationId;
|
|
58
75
|
// TODO: consider binding conversation_id directly to OpenClaw sessionId (prefer ctx.sessionId).
|
|
@@ -65,7 +82,7 @@ function resolveConversationId(cfg, ctx) {
|
|
|
65
82
|
return `${prefix}openclaw-${Date.now()}${dynamicSuffix}${suffix}`;
|
|
66
83
|
}
|
|
67
84
|
|
|
68
|
-
function buildSearchPayload(cfg, prompt, ctx) {
|
|
85
|
+
export function buildSearchPayload(cfg, prompt, ctx) {
|
|
69
86
|
const cleanPrompt = stripOpenClawInjectedPrefix(prompt);
|
|
70
87
|
const queryRaw = `${cfg.queryPrefix || ""}${cleanPrompt}`;
|
|
71
88
|
const query =
|
|
@@ -74,7 +91,7 @@ function buildSearchPayload(cfg, prompt, ctx) {
|
|
|
74
91
|
: queryRaw;
|
|
75
92
|
|
|
76
93
|
const payload = {
|
|
77
|
-
user_id: cfg
|
|
94
|
+
user_id: resolveMemosUserId(cfg, ctx),
|
|
78
95
|
query,
|
|
79
96
|
source: MEMOS_SOURCE,
|
|
80
97
|
};
|
|
@@ -113,9 +130,9 @@ function buildSearchPayload(cfg, prompt, ctx) {
|
|
|
113
130
|
return payload;
|
|
114
131
|
}
|
|
115
132
|
|
|
116
|
-
function buildAddMessagePayload(cfg, messages, ctx) {
|
|
133
|
+
export function buildAddMessagePayload(cfg, messages, ctx) {
|
|
117
134
|
const payload = {
|
|
118
|
-
user_id: cfg
|
|
135
|
+
user_id: resolveMemosUserId(cfg, ctx),
|
|
119
136
|
conversation_id: resolveConversationId(cfg, ctx),
|
|
120
137
|
messages,
|
|
121
138
|
source: MEMOS_SOURCE,
|
|
@@ -413,6 +430,15 @@ export default {
|
|
|
413
430
|
log.warn?.(`[memos-cloud] No .env found in ${searchPaths}; falling back to process env or plugin config.`);
|
|
414
431
|
}
|
|
415
432
|
|
|
433
|
+
if (cfg.multiAgentMode && cfg.allowedAgents?.length > 0) {
|
|
434
|
+
log.info?.(`[memos-cloud] Multi-agent mode enabled. Allowed agents: [${cfg.allowedAgents.join(", ")}]`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const overrideAgentIds = Object.keys(cfg._agentOverrides || {});
|
|
438
|
+
if (overrideAgentIds.length > 0) {
|
|
439
|
+
log.info?.(`[memos-cloud] Per-agent overrides configured for: [${overrideAgentIds.join(", ")}]`);
|
|
440
|
+
}
|
|
441
|
+
|
|
416
442
|
if (cfg.conversationSuffixMode === "counter" && cfg.resetOnNew) {
|
|
417
443
|
if (api.config?.hooks?.internal?.enabled !== true) {
|
|
418
444
|
log.warn?.("[memos-cloud] command:new hook requires hooks.internal.enabled = true");
|
|
@@ -432,24 +458,29 @@ export default {
|
|
|
432
458
|
}
|
|
433
459
|
|
|
434
460
|
api.on("before_agent_start", async (event, ctx) => {
|
|
435
|
-
if (!cfg
|
|
461
|
+
if (!isAgentAllowed(cfg, ctx)) {
|
|
462
|
+
log.info?.(`[memos-cloud] recall skipped: agent "${ctx?.agentId}" not in allowedAgents [${cfg.allowedAgents?.join(", ")}]`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const agentCfg = resolveAgentConfig(cfg, ctx?.agentId);
|
|
466
|
+
if (!agentCfg.recallEnabled) return;
|
|
436
467
|
const userPrompt = stripOpenClawInjectedPrefix(event?.prompt || "");
|
|
437
468
|
if (!userPrompt || userPrompt.length < 3) return;
|
|
438
|
-
if (!
|
|
469
|
+
if (!agentCfg.apiKey) {
|
|
439
470
|
warnMissingApiKey(log, "recall");
|
|
440
471
|
return;
|
|
441
472
|
}
|
|
442
473
|
|
|
443
474
|
try {
|
|
444
|
-
const payload = buildSearchPayload(
|
|
445
|
-
const result = await searchMemory(
|
|
475
|
+
const payload = buildSearchPayload(agentCfg, userPrompt, ctx);
|
|
476
|
+
const result = await searchMemory(agentCfg, payload);
|
|
446
477
|
const resultData = extractResultData(result);
|
|
447
478
|
if (!resultData) return;
|
|
448
|
-
const filteredData = await maybeFilterRecallData(
|
|
479
|
+
const filteredData = await maybeFilterRecallData(agentCfg, resultData, userPrompt, log);
|
|
449
480
|
const hookResult = formatRecallHookResult({ data: filteredData }, {
|
|
450
481
|
wrapTagBlocks: true,
|
|
451
482
|
relativity: payload.relativity,
|
|
452
|
-
maxItemChars:
|
|
483
|
+
maxItemChars: agentCfg.maxItemChars,
|
|
453
484
|
});
|
|
454
485
|
if (!hookResult.appendSystemContext && !hookResult.prependContext) return;
|
|
455
486
|
|
|
@@ -460,29 +491,34 @@ export default {
|
|
|
460
491
|
});
|
|
461
492
|
|
|
462
493
|
api.on("agent_end", async (event, ctx) => {
|
|
463
|
-
if (!cfg
|
|
494
|
+
if (!isAgentAllowed(cfg, ctx)) {
|
|
495
|
+
log.info?.(`[memos-cloud] add skipped: agent "${ctx?.agentId}" not in allowedAgents [${cfg.allowedAgents?.join(", ")}]`);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const agentCfg = resolveAgentConfig(cfg, ctx?.agentId);
|
|
499
|
+
if (!agentCfg.addEnabled) return;
|
|
464
500
|
if (!event?.success || !event?.messages?.length) return;
|
|
465
|
-
if (!
|
|
501
|
+
if (!agentCfg.apiKey) {
|
|
466
502
|
warnMissingApiKey(log, "add");
|
|
467
503
|
return;
|
|
468
504
|
}
|
|
469
505
|
|
|
470
506
|
const now = Date.now();
|
|
471
|
-
if (
|
|
507
|
+
if (agentCfg.throttleMs && now - lastCaptureTime < agentCfg.throttleMs) {
|
|
472
508
|
return;
|
|
473
509
|
}
|
|
474
510
|
lastCaptureTime = now;
|
|
475
511
|
|
|
476
512
|
try {
|
|
477
513
|
const messages =
|
|
478
|
-
|
|
479
|
-
? pickFullSessionMessages(event.messages,
|
|
480
|
-
: pickLastTurnMessages(event.messages,
|
|
514
|
+
agentCfg.captureStrategy === "full_session"
|
|
515
|
+
? pickFullSessionMessages(event.messages, agentCfg)
|
|
516
|
+
: pickLastTurnMessages(event.messages, agentCfg);
|
|
481
517
|
|
|
482
518
|
if (!messages.length) return;
|
|
483
519
|
|
|
484
|
-
const payload = buildAddMessagePayload(
|
|
485
|
-
await addMessage(
|
|
520
|
+
const payload = buildAddMessagePayload(agentCfg, messages, ctx);
|
|
521
|
+
await addMessage(agentCfg, payload);
|
|
486
522
|
} catch (err) {
|
|
487
523
|
log.warn?.(`[memos-cloud] add failed: ${String(err)}`);
|
|
488
524
|
}
|
package/lib/memos-cloud-api.js
CHANGED
|
@@ -157,6 +157,28 @@ function parseNumber(value, fallback) {
|
|
|
157
157
|
return Number.isFinite(n) ? n : fallback;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
function parseStringArray(value) {
|
|
161
|
+
if (!value) return [];
|
|
162
|
+
if (Array.isArray(value)) return value.map((v) => String(v).trim()).filter(Boolean);
|
|
163
|
+
return String(value)
|
|
164
|
+
.split(",")
|
|
165
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, ""))
|
|
166
|
+
.filter(Boolean);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseJsonObject(value) {
|
|
170
|
+
if (!value || typeof value !== "string") return null;
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(value);
|
|
173
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
174
|
+
return parsed;
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
// ignore parse error
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
160
182
|
export function buildConfig(pluginConfig = {}) {
|
|
161
183
|
const cfg = pluginConfig ?? {};
|
|
162
184
|
|
|
@@ -184,6 +206,10 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
184
206
|
parseBool(loadEnvVar("MEMOS_MULTI_AGENT_MODE"), false),
|
|
185
207
|
);
|
|
186
208
|
|
|
209
|
+
const allowedAgents = parseStringArray(
|
|
210
|
+
cfg.allowedAgents ?? loadEnvVar("MEMOS_ALLOWED_AGENTS"),
|
|
211
|
+
);
|
|
212
|
+
|
|
187
213
|
const recallFilterEnabled = parseBool(
|
|
188
214
|
cfg.recallFilterEnabled,
|
|
189
215
|
parseBool(loadEnvVar("MEMOS_RECALL_FILTER_ENABLED"), false),
|
|
@@ -200,6 +226,10 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
200
226
|
? parseBool(loadEnvVar("MEMOS_INCLUDE_ASSISTANT"), true)
|
|
201
227
|
: cfg.includeAssistant !== false;
|
|
202
228
|
const maxMessageChars = cfg.maxMessageChars ?? parseNumber(loadEnvVar("MEMOS_MAX_MESSAGE_CHARS"), 20000);
|
|
229
|
+
const useDirectSessionUserId = parseBool(
|
|
230
|
+
cfg.useDirectSessionUserId,
|
|
231
|
+
parseBool(loadEnvVar("MEMOS_USE_DIRECT_SESSION_USER_ID"), false),
|
|
232
|
+
);
|
|
203
233
|
|
|
204
234
|
return {
|
|
205
235
|
baseUrl: baseUrl.replace(/\/+$/, ""),
|
|
@@ -209,6 +239,7 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
209
239
|
conversationIdPrefix,
|
|
210
240
|
conversationIdSuffix,
|
|
211
241
|
conversationSuffixMode,
|
|
242
|
+
useDirectSessionUserId,
|
|
212
243
|
recallGlobal,
|
|
213
244
|
resetOnNew,
|
|
214
245
|
envFileStatus: getEnvFileStatus(),
|
|
@@ -230,15 +261,16 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
230
261
|
return v ? parseFloat(v) : 0.45;
|
|
231
262
|
})()),
|
|
232
263
|
filter: cfg.filter,
|
|
233
|
-
knowledgebaseIds: cfg.knowledgebaseIds ?? [],
|
|
234
|
-
tags: cfg.tags ?? ["openclaw"],
|
|
264
|
+
knowledgebaseIds: cfg.knowledgebaseIds ?? (loadEnvVar("MEMOS_KNOWLEDGEBASE_IDS") ? parseStringArray(loadEnvVar("MEMOS_KNOWLEDGEBASE_IDS")) : []),
|
|
265
|
+
tags: cfg.tags ?? (loadEnvVar("MEMOS_TAGS") ? parseStringArray(loadEnvVar("MEMOS_TAGS")) : ["openclaw"]),
|
|
235
266
|
info: cfg.info ?? {},
|
|
236
267
|
agentId: cfg.agentId,
|
|
237
268
|
appId: cfg.appId,
|
|
238
269
|
allowPublic: cfg.allowPublic ?? false,
|
|
239
|
-
allowKnowledgebaseIds: cfg.allowKnowledgebaseIds ?? [],
|
|
270
|
+
allowKnowledgebaseIds: cfg.allowKnowledgebaseIds ?? (loadEnvVar("MEMOS_ALLOW_KNOWLEDGEBASE_IDS") ? parseStringArray(loadEnvVar("MEMOS_ALLOW_KNOWLEDGEBASE_IDS")) : []),
|
|
240
271
|
asyncMode,
|
|
241
272
|
multiAgentMode,
|
|
273
|
+
allowedAgents,
|
|
242
274
|
recallFilterEnabled,
|
|
243
275
|
recallFilterBaseUrl:
|
|
244
276
|
(cfg.recallFilterBaseUrl ?? loadEnvVar("MEMOS_RECALL_FILTER_BASE_URL") ?? "").replace(/\/+$/, ""),
|
|
@@ -257,9 +289,35 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
257
289
|
timeoutMs: cfg.timeoutMs ?? 5000,
|
|
258
290
|
retries: cfg.retries ?? 1,
|
|
259
291
|
throttleMs,
|
|
292
|
+
_agentOverrides: cfg.agentOverrides ?? parseJsonObject(loadEnvVar("MEMOS_AGENT_OVERRIDES")) ?? {},
|
|
260
293
|
};
|
|
261
294
|
}
|
|
262
295
|
|
|
296
|
+
const AGENT_OVERRIDABLE_KEYS = [
|
|
297
|
+
"knowledgebaseIds", "memoryLimitNumber", "preferenceLimitNumber",
|
|
298
|
+
"includePreference", "includeToolMemory", "toolMemoryLimitNumber",
|
|
299
|
+
"relativity",
|
|
300
|
+
"recallEnabled", "addEnabled", "captureStrategy", "queryPrefix",
|
|
301
|
+
"maxItemChars", "maxMessageChars", "includeAssistant",
|
|
302
|
+
"recallGlobal", "recallFilterEnabled", "recallFilterModel",
|
|
303
|
+
"recallFilterBaseUrl", "recallFilterApiKey",
|
|
304
|
+
"allowKnowledgebaseIds", "tags", "throttleMs",
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
export function resolveAgentConfig(baseCfg, agentId) {
|
|
308
|
+
if (!agentId || !baseCfg._agentOverrides) return baseCfg;
|
|
309
|
+
const overrides = baseCfg._agentOverrides[agentId];
|
|
310
|
+
if (!overrides || typeof overrides !== "object") return baseCfg;
|
|
311
|
+
|
|
312
|
+
const merged = { ...baseCfg };
|
|
313
|
+
for (const key of AGENT_OVERRIDABLE_KEYS) {
|
|
314
|
+
if (key in overrides) {
|
|
315
|
+
merged[key] = overrides[key];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return merged;
|
|
319
|
+
}
|
|
320
|
+
|
|
263
321
|
export async function callApi({ baseUrl, apiKey, timeoutMs = 5000, retries = 1 }, path, body) {
|
|
264
322
|
if (!apiKey) {
|
|
265
323
|
throw new Error("Missing MEMOS API key (Token auth)");
|
|
@@ -317,6 +375,13 @@ function sanitizeAddMessageEntry(entry) {
|
|
|
317
375
|
return { ...entry, content };
|
|
318
376
|
}
|
|
319
377
|
|
|
378
|
+
export function isAgentAllowed(cfg, ctx) {
|
|
379
|
+
if (!cfg.multiAgentMode) return true;
|
|
380
|
+
if (!cfg.allowedAgents || cfg.allowedAgents.length === 0) return true;
|
|
381
|
+
const agentId = ctx?.agentId || cfg.agentId || "main";
|
|
382
|
+
return cfg.allowedAgents.includes(agentId);
|
|
383
|
+
}
|
|
384
|
+
|
|
320
385
|
export async function searchMemory(cfg, payload) {
|
|
321
386
|
return callApi(cfg, "/search/memory", sanitizeSearchPayload(payload));
|
|
322
387
|
}
|
package/moltbot.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "memos-cloud-openclaw-plugin",
|
|
3
3
|
"name": "MemOS Cloud OpenClaw Plugin",
|
|
4
4
|
"description": "MemOS Cloud recall + add memory via lifecycle hooks",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.11-beta.0",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
],
|
|
42
42
|
"default": "none"
|
|
43
43
|
},
|
|
44
|
+
"useDirectSessionUserId": {
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"description": "When enabled, direct-session keys like agent:main:<provider>:direct:<id> use the direct id as MemOS user_id instead of the default configured userId.",
|
|
47
|
+
"default": false
|
|
48
|
+
},
|
|
44
49
|
"resetOnNew": {
|
|
45
50
|
"type": "boolean",
|
|
46
51
|
"default": true
|
|
@@ -174,6 +179,13 @@
|
|
|
174
179
|
"type": "boolean",
|
|
175
180
|
"default": false
|
|
176
181
|
},
|
|
182
|
+
"allowedAgents": {
|
|
183
|
+
"type": "array",
|
|
184
|
+
"items": {
|
|
185
|
+
"type": "string"
|
|
186
|
+
},
|
|
187
|
+
"description": "When multiAgentMode is true, only these agent IDs will activate the memory plugin. Comma-separated in env var MEMOS_ALLOWED_AGENTS. Empty list means all agents are allowed."
|
|
188
|
+
},
|
|
177
189
|
"appId": {
|
|
178
190
|
"type": "string"
|
|
179
191
|
},
|
|
@@ -202,6 +214,108 @@
|
|
|
202
214
|
"throttleMs": {
|
|
203
215
|
"type": "integer",
|
|
204
216
|
"default": 0
|
|
217
|
+
},
|
|
218
|
+
"agentOverrides": {
|
|
219
|
+
"type": "object",
|
|
220
|
+
"description": "Per-agent config overrides. Keys are agent IDs, values override global defaults for that agent.",
|
|
221
|
+
"additionalProperties": {
|
|
222
|
+
"type": "object",
|
|
223
|
+
"properties": {
|
|
224
|
+
"knowledgebaseIds": {
|
|
225
|
+
"type": "array",
|
|
226
|
+
"items": {
|
|
227
|
+
"type": "string"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"memoryLimitNumber": {
|
|
231
|
+
"type": "integer"
|
|
232
|
+
},
|
|
233
|
+
"preferenceLimitNumber": {
|
|
234
|
+
"type": "integer"
|
|
235
|
+
},
|
|
236
|
+
"includePreference": {
|
|
237
|
+
"type": "boolean"
|
|
238
|
+
},
|
|
239
|
+
"includeToolMemory": {
|
|
240
|
+
"type": "boolean"
|
|
241
|
+
},
|
|
242
|
+
"toolMemoryLimitNumber": {
|
|
243
|
+
"type": "integer"
|
|
244
|
+
},
|
|
245
|
+
"includeSkill": {
|
|
246
|
+
"type": "boolean"
|
|
247
|
+
},
|
|
248
|
+
"skillLimitNumber": {
|
|
249
|
+
"type": "integer"
|
|
250
|
+
},
|
|
251
|
+
"relativity": {
|
|
252
|
+
"type": "number"
|
|
253
|
+
},
|
|
254
|
+
"filter": {
|
|
255
|
+
"type": "object",
|
|
256
|
+
"additionalProperties": true
|
|
257
|
+
},
|
|
258
|
+
"recallEnabled": {
|
|
259
|
+
"type": "boolean"
|
|
260
|
+
},
|
|
261
|
+
"addEnabled": {
|
|
262
|
+
"type": "boolean"
|
|
263
|
+
},
|
|
264
|
+
"captureStrategy": {
|
|
265
|
+
"type": "string",
|
|
266
|
+
"enum": [
|
|
267
|
+
"last_turn",
|
|
268
|
+
"full_session"
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
"queryPrefix": {
|
|
272
|
+
"type": "string"
|
|
273
|
+
},
|
|
274
|
+
"maxQueryChars": {
|
|
275
|
+
"type": "integer"
|
|
276
|
+
},
|
|
277
|
+
"maxItemChars": {
|
|
278
|
+
"type": "integer"
|
|
279
|
+
},
|
|
280
|
+
"maxMessageChars": {
|
|
281
|
+
"type": "integer"
|
|
282
|
+
},
|
|
283
|
+
"includeAssistant": {
|
|
284
|
+
"type": "boolean"
|
|
285
|
+
},
|
|
286
|
+
"recallGlobal": {
|
|
287
|
+
"type": "boolean"
|
|
288
|
+
},
|
|
289
|
+
"recallFilterEnabled": {
|
|
290
|
+
"type": "boolean"
|
|
291
|
+
},
|
|
292
|
+
"recallFilterModel": {
|
|
293
|
+
"type": "string"
|
|
294
|
+
},
|
|
295
|
+
"recallFilterBaseUrl": {
|
|
296
|
+
"type": "string"
|
|
297
|
+
},
|
|
298
|
+
"recallFilterApiKey": {
|
|
299
|
+
"type": "string"
|
|
300
|
+
},
|
|
301
|
+
"allowKnowledgebaseIds": {
|
|
302
|
+
"type": "array",
|
|
303
|
+
"items": {
|
|
304
|
+
"type": "string"
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"tags": {
|
|
308
|
+
"type": "array",
|
|
309
|
+
"items": {
|
|
310
|
+
"type": "string"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"throttleMs": {
|
|
314
|
+
"type": "integer"
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"additionalProperties": false
|
|
318
|
+
}
|
|
205
319
|
}
|
|
206
320
|
},
|
|
207
321
|
"additionalProperties": false
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "memos-cloud-openclaw-plugin",
|
|
3
3
|
"name": "MemOS Cloud OpenClaw Plugin",
|
|
4
4
|
"description": "MemOS Cloud recall + add memory via lifecycle hooks",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.11-beta.0",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"configSchema": {
|
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
],
|
|
42
42
|
"default": "none"
|
|
43
43
|
},
|
|
44
|
+
"useDirectSessionUserId": {
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"description": "When enabled, direct-session keys like agent:main:<provider>:direct:<id> use the direct id as MemOS user_id instead of the default configured userId.",
|
|
47
|
+
"default": false
|
|
48
|
+
},
|
|
44
49
|
"resetOnNew": {
|
|
45
50
|
"type": "boolean",
|
|
46
51
|
"default": true
|
|
@@ -174,6 +179,13 @@
|
|
|
174
179
|
"type": "boolean",
|
|
175
180
|
"default": false
|
|
176
181
|
},
|
|
182
|
+
"allowedAgents": {
|
|
183
|
+
"type": "array",
|
|
184
|
+
"items": {
|
|
185
|
+
"type": "string"
|
|
186
|
+
},
|
|
187
|
+
"description": "When multiAgentMode is true, only these agent IDs will activate the memory plugin. Comma-separated in env var MEMOS_ALLOWED_AGENTS. Empty list means all agents are allowed."
|
|
188
|
+
},
|
|
177
189
|
"appId": {
|
|
178
190
|
"type": "string"
|
|
179
191
|
},
|
|
@@ -202,6 +214,108 @@
|
|
|
202
214
|
"throttleMs": {
|
|
203
215
|
"type": "integer",
|
|
204
216
|
"default": 0
|
|
217
|
+
},
|
|
218
|
+
"agentOverrides": {
|
|
219
|
+
"type": "object",
|
|
220
|
+
"description": "Per-agent config overrides. Keys are agent IDs, values override global defaults for that agent.",
|
|
221
|
+
"additionalProperties": {
|
|
222
|
+
"type": "object",
|
|
223
|
+
"properties": {
|
|
224
|
+
"knowledgebaseIds": {
|
|
225
|
+
"type": "array",
|
|
226
|
+
"items": {
|
|
227
|
+
"type": "string"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"memoryLimitNumber": {
|
|
231
|
+
"type": "integer"
|
|
232
|
+
},
|
|
233
|
+
"preferenceLimitNumber": {
|
|
234
|
+
"type": "integer"
|
|
235
|
+
},
|
|
236
|
+
"includePreference": {
|
|
237
|
+
"type": "boolean"
|
|
238
|
+
},
|
|
239
|
+
"includeToolMemory": {
|
|
240
|
+
"type": "boolean"
|
|
241
|
+
},
|
|
242
|
+
"toolMemoryLimitNumber": {
|
|
243
|
+
"type": "integer"
|
|
244
|
+
},
|
|
245
|
+
"includeSkill": {
|
|
246
|
+
"type": "boolean"
|
|
247
|
+
},
|
|
248
|
+
"skillLimitNumber": {
|
|
249
|
+
"type": "integer"
|
|
250
|
+
},
|
|
251
|
+
"relativity": {
|
|
252
|
+
"type": "number"
|
|
253
|
+
},
|
|
254
|
+
"filter": {
|
|
255
|
+
"type": "object",
|
|
256
|
+
"additionalProperties": true
|
|
257
|
+
},
|
|
258
|
+
"recallEnabled": {
|
|
259
|
+
"type": "boolean"
|
|
260
|
+
},
|
|
261
|
+
"addEnabled": {
|
|
262
|
+
"type": "boolean"
|
|
263
|
+
},
|
|
264
|
+
"captureStrategy": {
|
|
265
|
+
"type": "string",
|
|
266
|
+
"enum": [
|
|
267
|
+
"last_turn",
|
|
268
|
+
"full_session"
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
"queryPrefix": {
|
|
272
|
+
"type": "string"
|
|
273
|
+
},
|
|
274
|
+
"maxQueryChars": {
|
|
275
|
+
"type": "integer"
|
|
276
|
+
},
|
|
277
|
+
"maxItemChars": {
|
|
278
|
+
"type": "integer"
|
|
279
|
+
},
|
|
280
|
+
"maxMessageChars": {
|
|
281
|
+
"type": "integer"
|
|
282
|
+
},
|
|
283
|
+
"includeAssistant": {
|
|
284
|
+
"type": "boolean"
|
|
285
|
+
},
|
|
286
|
+
"recallGlobal": {
|
|
287
|
+
"type": "boolean"
|
|
288
|
+
},
|
|
289
|
+
"recallFilterEnabled": {
|
|
290
|
+
"type": "boolean"
|
|
291
|
+
},
|
|
292
|
+
"recallFilterModel": {
|
|
293
|
+
"type": "string"
|
|
294
|
+
},
|
|
295
|
+
"recallFilterBaseUrl": {
|
|
296
|
+
"type": "string"
|
|
297
|
+
},
|
|
298
|
+
"recallFilterApiKey": {
|
|
299
|
+
"type": "string"
|
|
300
|
+
},
|
|
301
|
+
"allowKnowledgebaseIds": {
|
|
302
|
+
"type": "array",
|
|
303
|
+
"items": {
|
|
304
|
+
"type": "string"
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"tags": {
|
|
308
|
+
"type": "array",
|
|
309
|
+
"items": {
|
|
310
|
+
"type": "string"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"throttleMs": {
|
|
314
|
+
"type": "integer"
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"additionalProperties": false
|
|
318
|
+
}
|
|
205
319
|
}
|
|
206
320
|
},
|
|
207
321
|
"additionalProperties": false
|
package/package.json
CHANGED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { buildConfig } from "../lib/memos-cloud-api.js";
|
|
5
|
+
import {
|
|
6
|
+
buildAddMessagePayload,
|
|
7
|
+
buildSearchPayload,
|
|
8
|
+
extractDirectSessionUserId,
|
|
9
|
+
resolveMemosUserId,
|
|
10
|
+
} from "../index.js";
|
|
11
|
+
|
|
12
|
+
test("buildConfig keeps useDirectSessionUserId disabled by default", () => {
|
|
13
|
+
const previous = process.env.MEMOS_USE_DIRECT_SESSION_USER_ID;
|
|
14
|
+
delete process.env.MEMOS_USE_DIRECT_SESSION_USER_ID;
|
|
15
|
+
try {
|
|
16
|
+
const cfg = buildConfig({});
|
|
17
|
+
assert.equal(cfg.useDirectSessionUserId, false);
|
|
18
|
+
} finally {
|
|
19
|
+
if (previous === undefined) {
|
|
20
|
+
delete process.env.MEMOS_USE_DIRECT_SESSION_USER_ID;
|
|
21
|
+
} else {
|
|
22
|
+
process.env.MEMOS_USE_DIRECT_SESSION_USER_ID = previous;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("extractDirectSessionUserId returns the id for direct session keys", () => {
|
|
28
|
+
assert.equal(
|
|
29
|
+
extractDirectSessionUserId("agent:main:discord:direct:1160853368999247882"),
|
|
30
|
+
"1160853368999247882",
|
|
31
|
+
);
|
|
32
|
+
assert.equal(extractDirectSessionUserId("agent:main:telegram:direct:8361983702"), "8361983702");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("extractDirectSessionUserId ignores non-direct session keys", () => {
|
|
36
|
+
assert.equal(extractDirectSessionUserId("agent:main:discord:channel:1482035270651220051"), "");
|
|
37
|
+
assert.equal(extractDirectSessionUserId(""), "");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("resolveMemosUserId falls back to configured userId when switch is off", () => {
|
|
41
|
+
const cfg = { userId: "openclaw-user", useDirectSessionUserId: false };
|
|
42
|
+
const ctx = { sessionKey: "agent:main:discord:direct:1160853368999247882" };
|
|
43
|
+
assert.equal(resolveMemosUserId(cfg, ctx), "openclaw-user");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("resolveMemosUserId uses direct id when switch is on", () => {
|
|
47
|
+
const cfg = { userId: "openclaw-user", useDirectSessionUserId: true };
|
|
48
|
+
const ctx = { sessionKey: "agent:main:discord:direct:1160853368999247882" };
|
|
49
|
+
assert.equal(resolveMemosUserId(cfg, ctx), "1160853368999247882");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("buildSearchPayload uses direct session id as user_id for private chats", () => {
|
|
53
|
+
const cfg = {
|
|
54
|
+
userId: "openclaw-user",
|
|
55
|
+
useDirectSessionUserId: true,
|
|
56
|
+
queryPrefix: "",
|
|
57
|
+
maxQueryChars: 0,
|
|
58
|
+
recallGlobal: true,
|
|
59
|
+
knowledgebaseIds: [],
|
|
60
|
+
memoryLimitNumber: 6,
|
|
61
|
+
includePreference: true,
|
|
62
|
+
preferenceLimitNumber: 6,
|
|
63
|
+
includeToolMemory: false,
|
|
64
|
+
toolMemoryLimitNumber: 0,
|
|
65
|
+
relativity: 0.45,
|
|
66
|
+
multiAgentMode: false,
|
|
67
|
+
};
|
|
68
|
+
const ctx = { sessionKey: "agent:main:discord:direct:1160853368999247882" };
|
|
69
|
+
|
|
70
|
+
const payload = buildSearchPayload(cfg, "你好", ctx);
|
|
71
|
+
assert.equal(payload.user_id, "1160853368999247882");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("buildAddMessagePayload keeps configured userId for non-direct chats", () => {
|
|
75
|
+
const cfg = {
|
|
76
|
+
userId: "openclaw-user",
|
|
77
|
+
useDirectSessionUserId: true,
|
|
78
|
+
multiAgentMode: false,
|
|
79
|
+
appId: "",
|
|
80
|
+
tags: [],
|
|
81
|
+
info: {},
|
|
82
|
+
allowPublic: false,
|
|
83
|
+
allowKnowledgebaseIds: [],
|
|
84
|
+
asyncMode: true,
|
|
85
|
+
conversationId: "",
|
|
86
|
+
conversationIdPrefix: "",
|
|
87
|
+
conversationIdSuffix: "",
|
|
88
|
+
conversationSuffixMode: "none",
|
|
89
|
+
};
|
|
90
|
+
const ctx = { sessionKey: "agent:main:discord:channel:1482035270651220051" };
|
|
91
|
+
|
|
92
|
+
const payload = buildAddMessagePayload(cfg, [{ role: "user", content: "hi" }], ctx);
|
|
93
|
+
assert.equal(payload.user_id, "openclaw-user");
|
|
94
|
+
});
|