@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.
@@ -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.10-beta.0",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memtensor/memos-cloud-openclaw-plugin",
3
- "version": "0.1.10-beta.4",
3
+ "version": "0.1.11-beta.0",
4
4
  "description": "OpenClaw lifecycle plugin for MemOS Cloud (add + recall memory)",
5
5
  "scripts": {
6
6
  "sync-version": "node scripts/sync-version.js",
@@ -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
+ });