Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,21 @@ If `MEMOS_API_KEY` is missing, the plugin will warn with setup instructions and
MEMOS_API_KEY=YOUR_TOKEN
```

**Dual Channel Support (Cloud vs Open Source)**
This plugin supports connecting to both MemOS Cloud and the MemOS Open Source project.
- **Configure for Cloud (Default)**:
- `MEMOS_BASE_URL`: Leave default or set to `https://memos.memtensor.cn/api/openmem/v1`
- `MEMOS_API_KEY`: Required. Get from MemOS dashboard.
- `MEMOS_API_TYPE`: Optional, defaults to `cloud`.
- **Configure for Open Source**:
- `MEMOS_BASE_URL`: Set to your local/private open source deployment API (e.g. `http://127.0.0.1:8008`)
- `MEMOS_API_TYPE`: Set to `open_source`. (Will be auto-detected if `BASE_URL` doesn't contain `memos.memtensor.cn`)
- `MEMOS_API_KEY`: Optional. Open source does not strictly require auth. If provided, it will be sent as a `Bearer Token`.

**Optional config**
- `MEMOS_BASE_URL` (default: `https://memos.memtensor.cn/api/openmem/v1`)
- `MEMOS_API_KEY` (required; Token auth) — get it at https://memos-dashboard.openmem.net/cn/apikeys/
- `MEMOS_API_TYPE` (`cloud` or `open_source`, auto-inferred from baseUrl)
- `MEMOS_API_KEY` (required for Cloud; optional for Open Source) — get it at https://memos-dashboard.openmem.net/cn/apikeys/
- `MEMOS_USER_ID` (optional; default: `openclaw-user`)
- `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`)
- `MEMOS_CONVERSATION_ID` (optional override)
Expand Down Expand Up @@ -120,6 +132,7 @@ In `plugins.entries.memos-cloud-openclaw-plugin.config`:
```json
{
"baseUrl": "https://memos.memtensor.cn/api/openmem/v1",
"apiType": "cloud",
"apiKey": "YOUR_API_KEY",
"userId": "memos_user_123",
"useDirectSessionUserId": false,
Expand All @@ -136,6 +149,7 @@ In `plugins.entries.memos-cloud-openclaw-plugin.config`:
"conversationSuffixMode": "none",
"resetOnNew": true,
"knowledgebaseIds": [],
"allowKnowledgebaseIds": [],
"memoryLimitNumber": 6,
"preferenceLimitNumber": 6,
"includePreference": true,
Expand Down Expand Up @@ -233,6 +247,7 @@ Beyond simple on/off toggles, you can configure **different memory parameters fo
"multiAgentMode": true,
"allowedAgents": ["default", "research-agent", "coding-agent"],
"knowledgebaseIds": [],
"allowKnowledgebaseIds": [],
"memoryLimitNumber": 6,
"relativity": 0.45,

Expand Down Expand Up @@ -268,7 +283,7 @@ Beyond simple on/off toggles, you can configure **different memory parameters fo

| Field | Description |
|-------|-------------|
| `knowledgebaseIds` | Knowledge base IDs for `/search/memory` |
| `knowledgebaseIds` | Knowledge base IDs for `/search/memory` (where to recall from) |
| `memoryLimitNumber` | Max memory items to recall |
| `preferenceLimitNumber` | Max preference items to recall |
| `includePreference` | Enable preference recall |
Expand All @@ -287,7 +302,7 @@ Beyond simple on/off toggles, you can configure **different memory parameters fo
| `recallFilterModel` | Model for recall filtering |
| `recallFilterBaseUrl` | Base URL for recall filter model |
| `recallFilterApiKey` | API key for recall filter |
| `allowKnowledgebaseIds` | Knowledge bases for `/add/message` |
| `allowKnowledgebaseIds` | Knowledge bases for `/add/message` (where to save new memories) |
| `tags` | Tags for `/add/message` |
| `throttleMs` | Throttle interval |

Expand Down
21 changes: 18 additions & 3 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,21 @@ source ~/.bashrc
MEMOS_API_KEY=YOUR_TOKEN
```

**双渠道支持 (云服务 vs 开源版本)**
本插件同时支持对接 MemOS 云服务和 MemOS 开源项目。
- **配置为云服务(默认)**:
- `MEMOS_BASE_URL`:默认或设置为 `https://memos.memtensor.cn/api/openmem/v1`
- `MEMOS_API_KEY`:必填,从云平台获取的 Token。
- `MEMOS_API_TYPE`:可选,默认为 `cloud`。
- **配置为开源项目**:
- `MEMOS_BASE_URL`:设置为你本地或私有部署的开源项目 API 地址(例如:`http://127.0.0.1:8008`)
- `MEMOS_API_TYPE`:必须显式设置为 `open_source`,或者你的 `BASE_URL` 不包含 `memos.memtensor.cn` 时会自动识别。
- `MEMOS_API_KEY`:选填。开源项目默认不强制要求鉴权,如果你配置了此项,将以 `Bearer Token` 的形式发送。

**可选配置**
- `MEMOS_BASE_URL`(默认 `https://memos.memtensor.cn/api/openmem/v1`)
- `MEMOS_API_KEY`(必填,Token 认证)—— 获取地址:https://memos-dashboard.openmem.net/cn/apikeys/
- `MEMOS_API_TYPE`(`cloud` 或 `open_source`,根据 baseUrl 自动推断)
- `MEMOS_API_KEY`(云服务必填;开源版本选填)—— 获取地址:https://memos-dashboard.openmem.net/cn/apikeys/
- `MEMOS_USER_ID`(可选,默认 `openclaw-user`)
- `MEMOS_USE_DIRECT_SESSION_USER_ID`(默认 `false`;开启后,对 `agent:main:<provider>:direct:<peer-id>` 这类私聊 sessionKey,会把 `<peer-id>` 作为 MemOS `user_id`)
- `MEMOS_CONVERSATION_ID`(可选覆盖)
Expand Down Expand Up @@ -122,6 +134,7 @@ MEMOS_API_KEY=YOUR_TOKEN
```json
{
"baseUrl": "https://memos.memtensor.cn/api/openmem/v1",
"apiType": "cloud",
"apiKey": "YOUR_API_KEY",
"userId": "memos_user_123",
"useDirectSessionUserId": false,
Expand All @@ -139,6 +152,7 @@ MEMOS_API_KEY=YOUR_TOKEN
"memoryLimitNumber": 6,
"preferenceLimitNumber": 6,
"knowledgebaseIds": [],
"allowKnowledgebaseIds": [],
"includePreference": true,
"includeToolMemory": false,
"toolMemoryLimitNumber": 6,
Expand Down Expand Up @@ -238,6 +252,7 @@ MEMOS_ALLOWED_AGENTS="agent1,agent2"
"multiAgentMode": true,
"allowedAgents": ["default", "research-agent", "coding-agent"],
"knowledgebaseIds": [],
"allowKnowledgebaseIds": [],
"memoryLimitNumber": 6,
"relativity": 0.45,

Expand Down Expand Up @@ -273,7 +288,7 @@ MEMOS_ALLOWED_AGENTS="agent1,agent2"

| 字段 | 说明 |
|------|------|
| `knowledgebaseIds` | `/search/memory` 使用的知识库 ID 列表 |
| `knowledgebaseIds` | `/search/memory` 使用的知识库 ID 列表(从哪些知识库召回记忆) |
| `memoryLimitNumber` | 召回的事实记忆最大条数 |
| `preferenceLimitNumber` | 召回的偏好记忆最大条数 |
| `includePreference` | 是否启用偏好记忆召回 |
Expand All @@ -292,7 +307,7 @@ MEMOS_ALLOWED_AGENTS="agent1,agent2"
| `recallFilterModel` | 过滤模型名 |
| `recallFilterBaseUrl` | 过滤模型接口地址 |
| `recallFilterApiKey` | 过滤模型鉴权密钥 |
| `allowKnowledgebaseIds` | `/add/message` 允许写入的知识库 |
| `allowKnowledgebaseIds` | `/add/message` 允许写入的知识库 ID 列表(配置后,新记忆会被保存到这些指定的知识库中) |
| `tags` | `/add/message` 标签 |
| `throttleMs` | 请求节流间隔 |

Expand Down
92 changes: 72 additions & 20 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function resolveConversationId(cfg, ctx) {
}

export function buildSearchPayload(cfg, prompt, ctx) {
const isCloud = cfg.apiType === "cloud";
const cleanPrompt = stripOpenClawInjectedPrefix(prompt);
const queryRaw = `${cfg.queryPrefix || ""}${cleanPrompt}`;
const query =
Expand All @@ -96,52 +97,99 @@ export function buildSearchPayload(cfg, prompt, ctx) {
source: MEMOS_SOURCE,
};

if (!isCloud) {
payload.mode = "mixture";
payload.internet_search = false;
}

if (!cfg.recallGlobal) {
const conversationId = resolveConversationId(cfg, ctx);
if (conversationId) payload.conversation_id = conversationId;
if (conversationId) {
if (isCloud) {
payload.conversation_id = conversationId;
} else {
payload.session_id = conversationId;
}
}
}

let filterObj = cfg.filter ? JSON.parse(JSON.stringify(cfg.filter)) : null;
const agentId = getEffectiveAgentId(cfg, ctx);

if (agentId) {
const agentFilter = isCloud
? { agent_id: agentId }
: { info: { agentId: `${agentId}` } };

if (filterObj) {
if (Array.isArray(filterObj.and)) {
filterObj.and.push({ agent_id: agentId });
filterObj.and.push(agentFilter);
} else {
filterObj = { and: [filterObj, { agent_id: agentId }] };
filterObj = { and: [filterObj, agentFilter] };
}
} else {
filterObj = { agent_id: agentId };
filterObj = { and: [agentFilter] };
}
}

if (filterObj) payload.filter = filterObj;

if (cfg.knowledgebaseIds?.length) payload.knowledgebase_ids = cfg.knowledgebaseIds;
if (cfg.knowledgebaseIds?.length) {
if (isCloud) {
payload.knowledgebase_ids = cfg.knowledgebaseIds;
} else {
payload.readable_cube_ids = Array.from(new Set([payload.user_id, ...cfg.knowledgebaseIds]));
}
}

payload.memory_limit_number = cfg.memoryLimitNumber;
payload.include_preference = cfg.includePreference;
payload.preference_limit_number = cfg.preferenceLimitNumber;
payload.include_tool_memory = cfg.includeToolMemory;
payload.tool_memory_limit_number = cfg.toolMemoryLimitNumber;
payload.relativity = cfg.relativity;
if (isCloud) {
payload.memory_limit_number = cfg.memoryLimitNumber;
payload.include_preference = cfg.includePreference;
payload.preference_limit_number = cfg.preferenceLimitNumber;
payload.include_tool_memory = cfg.includeToolMemory;
payload.tool_memory_limit_number = cfg.toolMemoryLimitNumber;
payload.relativity = cfg.relativity;
} else {
payload.top_k = cfg.memoryLimitNumber;
payload.include_preference = cfg.includePreference;
payload.pref_top_k = cfg.preferenceLimitNumber;
payload.search_tool_memory = cfg.includeToolMemory;
payload.tool_mem_top_k = cfg.toolMemoryLimitNumber;
payload.threshold = cfg.relativity;
}

return payload;
}

export function buildAddMessagePayload(cfg, messages, ctx) {
const isCloud = cfg.apiType === "cloud";
const conversationId = resolveConversationId(cfg, ctx);

const payload = {
user_id: resolveMemosUserId(cfg, ctx),
conversation_id: resolveConversationId(cfg, ctx),
messages,
source: MEMOS_SOURCE,
};

if (isCloud) {
payload.conversation_id = conversationId;
payload.async_mode = cfg.asyncMode;
payload.allow_public = cfg.allowPublic;
} else {
payload.session_id = conversationId;
payload.async_mode = cfg.asyncMode === false ? "sync" : "async";
}

const agentId = getEffectiveAgentId(cfg, ctx);
if (agentId) payload.agent_id = agentId;
if (cfg.appId) payload.app_id = cfg.appId;
if (cfg.tags?.length) payload.tags = cfg.tags;

if (isCloud) {
if (agentId) payload.agent_id = agentId;
if (cfg.appId) payload.app_id = cfg.appId;
if (cfg.tags?.length) payload.tags = cfg.tags;
} else {
const customTags = [...(cfg.tags || [])];
if (customTags.length) payload.custom_tags = customTags;
}

const info = {
source: "openclaw",
Expand All @@ -151,9 +199,13 @@ export function buildAddMessagePayload(cfg, messages, ctx) {
};
if (Object.keys(info).length > 0) payload.info = info;

payload.allow_public = cfg.allowPublic;
if (cfg.allowKnowledgebaseIds?.length) payload.allow_knowledgebase_ids = cfg.allowKnowledgebaseIds;
payload.async_mode = cfg.asyncMode;
if (cfg.allowKnowledgebaseIds?.length) {
if (isCloud) {
payload.allow_knowledgebase_ids = cfg.allowKnowledgebaseIds;
} else {
payload.writable_cube_ids = Array.from(new Set([payload.user_id, ...cfg.allowKnowledgebaseIds]));
}
}

return payload;
}
Expand Down Expand Up @@ -466,7 +518,7 @@ export default {
if (!agentCfg.recallEnabled) return;
const userPrompt = stripOpenClawInjectedPrefix(event?.prompt || "");
if (!userPrompt || userPrompt.length < 3) return;
if (!agentCfg.apiKey) {
if (agentCfg.apiType === "cloud" && !agentCfg.apiKey) {
warnMissingApiKey(log, "recall");
return;
}
Expand Down Expand Up @@ -498,7 +550,7 @@ export default {
const agentCfg = resolveAgentConfig(cfg, ctx?.agentId);
if (!agentCfg.addEnabled) return;
if (!event?.success || !event?.messages?.length) return;
if (!agentCfg.apiKey) {
if (agentCfg.apiType === "cloud" && !agentCfg.apiKey) {
warnMissingApiKey(log, "add");
return;
}
Expand Down
Loading