@memtensor/memos-cloud-openclaw-plugin 0.1.10-beta.4 → 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 +140 -5
- package/README_ZH.md +140 -5
- package/clawdbot.plugin.json +156 -2
- package/index.js +73 -24
- package/lib/memos-cloud-api.js +76 -7
- package/moltbot.plugin.json +156 -2
- package/openclaw.plugin.json +156 -2
- package/package.json +1 -1
- package/test/direct-session-user-id.test.mjs +94 -0
- package/test/query-strip.test.mjs +41 -0
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
|
|
@@ -109,7 +114,47 @@
|
|
|
109
114
|
},
|
|
110
115
|
"filter": {
|
|
111
116
|
"type": "object",
|
|
112
|
-
"description": "MemOS search filter"
|
|
117
|
+
"description": "MemOS search filter",
|
|
118
|
+
"additionalProperties": true
|
|
119
|
+
},
|
|
120
|
+
"relativity": {
|
|
121
|
+
"type": "number",
|
|
122
|
+
"description": "Search relativity threshold",
|
|
123
|
+
"default": 0.45
|
|
124
|
+
},
|
|
125
|
+
"recallFilterEnabled": {
|
|
126
|
+
"type": "boolean",
|
|
127
|
+
"default": false
|
|
128
|
+
},
|
|
129
|
+
"recallFilterBaseUrl": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "OpenAI-compatible API base URL for recall filtering"
|
|
132
|
+
},
|
|
133
|
+
"recallFilterApiKey": {
|
|
134
|
+
"type": "string"
|
|
135
|
+
},
|
|
136
|
+
"recallFilterModel": {
|
|
137
|
+
"type": "string"
|
|
138
|
+
},
|
|
139
|
+
"recallFilterTimeoutMs": {
|
|
140
|
+
"type": "integer",
|
|
141
|
+
"default": 30000
|
|
142
|
+
},
|
|
143
|
+
"recallFilterRetries": {
|
|
144
|
+
"type": "integer",
|
|
145
|
+
"default": 1
|
|
146
|
+
},
|
|
147
|
+
"recallFilterCandidateLimit": {
|
|
148
|
+
"type": "integer",
|
|
149
|
+
"default": 30
|
|
150
|
+
},
|
|
151
|
+
"recallFilterMaxItemChars": {
|
|
152
|
+
"type": "integer",
|
|
153
|
+
"default": 500
|
|
154
|
+
},
|
|
155
|
+
"recallFilterFailOpen": {
|
|
156
|
+
"type": "boolean",
|
|
157
|
+
"default": true
|
|
113
158
|
},
|
|
114
159
|
"knowledgebaseIds": {
|
|
115
160
|
"type": "array",
|
|
@@ -134,6 +179,13 @@
|
|
|
134
179
|
"type": "boolean",
|
|
135
180
|
"default": false
|
|
136
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
|
+
},
|
|
137
189
|
"appId": {
|
|
138
190
|
"type": "string"
|
|
139
191
|
},
|
|
@@ -162,6 +214,108 @@
|
|
|
162
214
|
"throttleMs": {
|
|
163
215
|
"type": "integer",
|
|
164
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
|
+
}
|
|
165
319
|
}
|
|
166
320
|
},
|
|
167
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
|
+
});
|
|
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
USER_QUERY_MARKER,
|
|
6
|
+
formatPromptBlockFromData,
|
|
6
7
|
sanitizeAddMessagePayload,
|
|
7
8
|
sanitizeSearchPayload,
|
|
8
9
|
stripOpenClawInjectedPrefix,
|
|
@@ -338,3 +339,43 @@ test("sanitizes only user messages in add payload", () => {
|
|
|
338
339
|
],
|
|
339
340
|
});
|
|
340
341
|
});
|
|
342
|
+
|
|
343
|
+
test("formatPromptBlockFromData prefers update_time over create_time", () => {
|
|
344
|
+
const block = formatPromptBlockFromData({
|
|
345
|
+
memory_detail_list: [
|
|
346
|
+
{
|
|
347
|
+
memory_value: "更新后的记忆",
|
|
348
|
+
create_time: "2026-03-17 10:00",
|
|
349
|
+
update_time: "2026-03-18 16:20",
|
|
350
|
+
relativity: 0.9,
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
preference_detail_list: [
|
|
354
|
+
{
|
|
355
|
+
preference: "更新后的偏好",
|
|
356
|
+
preference_type: "explicit",
|
|
357
|
+
create_time: "2026-03-17 11:00",
|
|
358
|
+
update_time: "2026-03-18 16:21",
|
|
359
|
+
relativity: 0.9,
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
assert.match(block, /\-\[2026-03-18 16:20\] 更新后的记忆/);
|
|
365
|
+
assert.match(block, /\-\[2026-03-18 16:21\] \[Explicit Preference\] 更新后的偏好/);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("formatPromptBlockFromData falls back to create_time when update_time is missing", () => {
|
|
369
|
+
const block = formatPromptBlockFromData({
|
|
370
|
+
memory_detail_list: [
|
|
371
|
+
{
|
|
372
|
+
memory_value: "只有创建时间",
|
|
373
|
+
create_time: "2026-03-18 09:30",
|
|
374
|
+
relativity: 0.9,
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
preference_detail_list: [],
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
assert.match(block, /\-\[2026-03-18 09:30\] 只有创建时间/);
|
|
381
|
+
});
|