@memtensor/memos-cloud-openclaw-plugin 0.1.10-beta.0 → 0.1.10-beta.1
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 +9 -2
- package/README_ZH.md +9 -2
- package/index.js +2 -1
- package/lib/memos-cloud-api.js +60 -8
- package/package.json +1 -1
- package/test/query-strip.test.mjs +28 -37
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Make sure it’s enabled in `~/.openclaw/openclaw.json`:
|
|
|
44
44
|
},
|
|
45
45
|
"load": {
|
|
46
46
|
"paths": [
|
|
47
|
-
"C:\\Users\\YourName\\.openclaw\\extensions\\memos-cloud-openclaw-plugin
|
|
47
|
+
"C:\\Users\\YourName\\.openclaw\\extensions\\memos-cloud-openclaw-plugin"
|
|
48
48
|
]
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -106,6 +106,11 @@ MEMOS_API_KEY=YOUR_TOKEN
|
|
|
106
106
|
- `MEMOS_RECALL_FILTER_CANDIDATE_LIMIT` (default: `30` per category)
|
|
107
107
|
- `MEMOS_RECALL_FILTER_MAX_ITEM_CHARS` (default: `500`)
|
|
108
108
|
- `MEMOS_RECALL_FILTER_FAIL_OPEN` (default: `true`; fallback to unfiltered recall on failure)
|
|
109
|
+
- `MEMOS_CAPTURE_STRATEGY` (default: `last_turn`)
|
|
110
|
+
- `MEMOS_ASYNC_MODE` (default: `true`; non-blocking memory addition)
|
|
111
|
+
- `MEMOS_THROTTLE_MS` (default: `0`; throttle memory requests)
|
|
112
|
+
- `MEMOS_INCLUDE_ASSISTANT` (default: `true`; include assistant messages in memory)
|
|
113
|
+
- `MEMOS_MAX_MESSAGE_CHARS` (default: `20000`; max characters for message history)
|
|
109
114
|
|
|
110
115
|
## Optional Plugin Config
|
|
111
116
|
In `plugins.entries.memos-cloud-openclaw-plugin.config`:
|
|
@@ -145,7 +150,9 @@ In `plugins.entries.memos-cloud-openclaw-plugin.config`:
|
|
|
145
150
|
"recallFilterRetries": 0,
|
|
146
151
|
"recallFilterCandidateLimit": 30,
|
|
147
152
|
"recallFilterMaxItemChars": 500,
|
|
148
|
-
"recallFilterFailOpen": true
|
|
153
|
+
"recallFilterFailOpen": true,
|
|
154
|
+
"throttleMs": 0,
|
|
155
|
+
"maxMessageChars": 20000
|
|
149
156
|
}
|
|
150
157
|
```
|
|
151
158
|
|
package/README_ZH.md
CHANGED
|
@@ -46,7 +46,7 @@ openclaw gateway restart
|
|
|
46
46
|
},
|
|
47
47
|
"load": {
|
|
48
48
|
"paths": [
|
|
49
|
-
"C:\\Users\\YourName\\.openclaw\\extensions\\memos-cloud-openclaw-plugin
|
|
49
|
+
"C:\\Users\\YourName\\.openclaw\\extensions\\memos-cloud-openclaw-plugin"
|
|
50
50
|
]
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -108,6 +108,11 @@ MEMOS_API_KEY=YOUR_TOKEN
|
|
|
108
108
|
- `MEMOS_RECALL_FILTER_CANDIDATE_LIMIT`(默认每类 `30` 条)
|
|
109
109
|
- `MEMOS_RECALL_FILTER_MAX_ITEM_CHARS`(默认 `500`)
|
|
110
110
|
- `MEMOS_RECALL_FILTER_FAIL_OPEN`(默认 `true`;筛选失败时回退为“不过滤”)
|
|
111
|
+
- `MEMOS_CAPTURE_STRATEGY`(默认 `last_turn`;记忆捕获策略)
|
|
112
|
+
- `MEMOS_ASYNC_MODE`(默认 `true`;异步模式添加记忆)
|
|
113
|
+
- `MEMOS_THROTTLE_MS`(默认 `0`;请求节流时间,单位毫秒)
|
|
114
|
+
- `MEMOS_INCLUDE_ASSISTANT`(默认 `true`;记忆是否包含助手回复)
|
|
115
|
+
- `MEMOS_MAX_MESSAGE_CHARS`(默认 `20000`;单条记忆最大字符数限制)
|
|
111
116
|
|
|
112
117
|
## 可选插件配置
|
|
113
118
|
在 `plugins.entries.memos-cloud-openclaw-plugin.config` 中设置:
|
|
@@ -145,7 +150,9 @@ MEMOS_API_KEY=YOUR_TOKEN
|
|
|
145
150
|
"recallFilterRetries": 0,
|
|
146
151
|
"recallFilterCandidateLimit": 30,
|
|
147
152
|
"recallFilterMaxItemChars": 500,
|
|
148
|
-
"recallFilterFailOpen": true
|
|
153
|
+
"recallFilterFailOpen": true,
|
|
154
|
+
"throttleMs": 0,
|
|
155
|
+
"maxMessageChars": 20000
|
|
149
156
|
}
|
|
150
157
|
```
|
|
151
158
|
|
package/index.js
CHANGED
|
@@ -32,7 +32,7 @@ function warnMissingApiKey(log, context) {
|
|
|
32
32
|
].join("\n"),
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
console.log('11111111111111111111111111111111')
|
|
36
36
|
function getCounterSuffix(sessionKey) {
|
|
37
37
|
if (!sessionKey) return "";
|
|
38
38
|
const current = conversationCounters.get(sessionKey) ?? 0;
|
|
@@ -448,6 +448,7 @@ export default {
|
|
|
448
448
|
|
|
449
449
|
api.on("agent_end", async (event, ctx) => {
|
|
450
450
|
if (!cfg.addEnabled) return;
|
|
451
|
+
console.log('222222222222222222222\n22222222222', cfg.addEnabled)
|
|
451
452
|
if (!event?.success || !event?.messages?.length) return;
|
|
452
453
|
if (!cfg.apiKey) {
|
|
453
454
|
warnMissingApiKey(log, "add");
|
package/lib/memos-cloud-api.js
CHANGED
|
@@ -173,6 +173,14 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
173
173
|
cfg.recallFilterFailOpen,
|
|
174
174
|
parseBool(loadEnvVar("MEMOS_RECALL_FILTER_FAIL_OPEN"), true),
|
|
175
175
|
);
|
|
176
|
+
const captureStrategy = cfg.captureStrategy ?? (loadEnvVar("MEMOS_CAPTURE_STRATEGY") || "last_turn");
|
|
177
|
+
const asyncMode = cfg.asyncMode ?? parseBool(loadEnvVar("MEMOS_ASYNC_MODE"), true);
|
|
178
|
+
const throttleMs = cfg.throttleMs ?? parseNumber(loadEnvVar("MEMOS_THROTTLE_MS"), 0);
|
|
179
|
+
const includeAssistant =
|
|
180
|
+
cfg.includeAssistant === undefined
|
|
181
|
+
? parseBool(loadEnvVar("MEMOS_INCLUDE_ASSISTANT"), true)
|
|
182
|
+
: cfg.includeAssistant !== false;
|
|
183
|
+
const maxMessageChars = cfg.maxMessageChars ?? parseNumber(loadEnvVar("MEMOS_MAX_MESSAGE_CHARS"), 20000);
|
|
176
184
|
|
|
177
185
|
return {
|
|
178
186
|
baseUrl: baseUrl.replace(/\/+$/, ""),
|
|
@@ -189,10 +197,10 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
189
197
|
maxQueryChars: cfg.maxQueryChars ?? 0,
|
|
190
198
|
recallEnabled: cfg.recallEnabled !== false,
|
|
191
199
|
addEnabled: cfg.addEnabled !== false,
|
|
192
|
-
captureStrategy
|
|
193
|
-
maxMessageChars
|
|
200
|
+
captureStrategy,
|
|
201
|
+
maxMessageChars,
|
|
194
202
|
maxItemChars: cfg.maxItemChars ?? 8000,
|
|
195
|
-
includeAssistant
|
|
203
|
+
includeAssistant,
|
|
196
204
|
memoryLimitNumber: cfg.memoryLimitNumber ?? 9,
|
|
197
205
|
preferenceLimitNumber: cfg.preferenceLimitNumber ?? 6,
|
|
198
206
|
includePreference: cfg.includePreference !== false,
|
|
@@ -210,7 +218,7 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
210
218
|
appId: cfg.appId,
|
|
211
219
|
allowPublic: cfg.allowPublic ?? false,
|
|
212
220
|
allowKnowledgebaseIds: cfg.allowKnowledgebaseIds ?? [],
|
|
213
|
-
asyncMode
|
|
221
|
+
asyncMode,
|
|
214
222
|
multiAgentMode,
|
|
215
223
|
recallFilterEnabled,
|
|
216
224
|
recallFilterBaseUrl:
|
|
@@ -229,7 +237,7 @@ export function buildConfig(pluginConfig = {}) {
|
|
|
229
237
|
recallFilterFailOpen,
|
|
230
238
|
timeoutMs: cfg.timeoutMs ?? 5000,
|
|
231
239
|
retries: cfg.retries ?? 1,
|
|
232
|
-
throttleMs
|
|
240
|
+
throttleMs,
|
|
233
241
|
};
|
|
234
242
|
}
|
|
235
243
|
|
|
@@ -279,7 +287,14 @@ export async function searchMemory(cfg, payload) {
|
|
|
279
287
|
}
|
|
280
288
|
|
|
281
289
|
export async function addMessage(cfg, payload) {
|
|
282
|
-
|
|
290
|
+
let finalPayload = payload;
|
|
291
|
+
try {
|
|
292
|
+
finalPayload = sanitizeAddMessagePayload(payload);
|
|
293
|
+
} catch {
|
|
294
|
+
// Fail open: if sanitization throws unexpectedly, send original payload.
|
|
295
|
+
finalPayload = payload;
|
|
296
|
+
}
|
|
297
|
+
return callApi(cfg, "/add/message", finalPayload);
|
|
283
298
|
}
|
|
284
299
|
|
|
285
300
|
function isInboundMetaSentinelLine(line) {
|
|
@@ -333,11 +348,48 @@ function stripLeadingTimestampEnvelope(text) {
|
|
|
333
348
|
return text;
|
|
334
349
|
}
|
|
335
350
|
|
|
351
|
+
function stripFeishuInjectedPrompt(text) {
|
|
352
|
+
if (!text || typeof text !== "string") return text;
|
|
353
|
+
// Check for the Feishu System header
|
|
354
|
+
if (!/^System: \[.*?\] Feishu\[.*?\]/.test(text)) {
|
|
355
|
+
return text;
|
|
356
|
+
}
|
|
357
|
+
// Remove only the first injected Feishu prompt prefix.
|
|
358
|
+
// Any later "[message_id] ou_xxx:" pattern should be treated as user query content.
|
|
359
|
+
const leadingInjectedPattern = /^[\s\S]*?\[message_id: [^\]]+\]\s+ou_[a-z0-9]+:\s*/;
|
|
360
|
+
if (leadingInjectedPattern.test(text)) {
|
|
361
|
+
return text.replace(leadingInjectedPattern, "").trim();
|
|
362
|
+
}
|
|
363
|
+
return text;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function sanitizeAddMessagePayload(payload) {
|
|
367
|
+
if (!payload || typeof payload !== "object") return payload;
|
|
368
|
+
const nextPayload = { ...payload };
|
|
369
|
+
if (typeof nextPayload.query === "string") {
|
|
370
|
+
nextPayload.query = stripOpenClawInjectedPrefix(nextPayload.query);
|
|
371
|
+
}
|
|
372
|
+
if (Array.isArray(nextPayload.messages)) {
|
|
373
|
+
nextPayload.messages = nextPayload.messages.map((msg) => {
|
|
374
|
+
if (!msg || typeof msg !== "object") return msg;
|
|
375
|
+
if (msg.role !== "user" || typeof msg.content !== "string") return msg;
|
|
376
|
+
return {
|
|
377
|
+
...msg,
|
|
378
|
+
content: stripOpenClawInjectedPrefix(msg.content),
|
|
379
|
+
};
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return nextPayload;
|
|
383
|
+
}
|
|
384
|
+
|
|
336
385
|
export function stripOpenClawInjectedPrefix(text) {
|
|
337
386
|
if (!text || typeof text !== "string") return "";
|
|
338
|
-
const
|
|
387
|
+
const cleanedText = stripFeishuInjectedPrompt(text);
|
|
388
|
+
const markerIndex = cleanedText.lastIndexOf(USER_QUERY_MARKER);
|
|
339
389
|
const withoutRecallPrefix =
|
|
340
|
-
markerIndex === -1
|
|
390
|
+
markerIndex === -1
|
|
391
|
+
? cleanedText
|
|
392
|
+
: cleanedText.slice(markerIndex + USER_QUERY_MARKER.length);
|
|
341
393
|
const withoutInboundMetadata = stripLeadingInboundMetadata(withoutRecallPrefix).trimStart();
|
|
342
394
|
return stripLeadingTimestampEnvelope(withoutInboundMetadata);
|
|
343
395
|
}
|
package/package.json
CHANGED
|
@@ -97,56 +97,47 @@ test("strips valid prefix even if body starts with a sentinel-like line", () =>
|
|
|
97
97
|
|
|
98
98
|
test("supports leading blank lines before inbound metadata", () => {
|
|
99
99
|
const input = [
|
|
100
|
-
"",
|
|
101
100
|
"",
|
|
102
101
|
"Conversation info (untrusted metadata):",
|
|
103
102
|
"```json",
|
|
104
103
|
'{"message_id":"123"}',
|
|
105
104
|
"```",
|
|
106
|
-
"",
|
|
107
|
-
"真正的问题",
|
|
105
|
+
"Hello",
|
|
108
106
|
].join("\n");
|
|
109
|
-
|
|
110
|
-
assert.equal(stripOpenClawInjectedPrefix(input), "真正的问题");
|
|
107
|
+
assert.equal(stripOpenClawInjectedPrefix(input), "Hello");
|
|
111
108
|
});
|
|
112
109
|
|
|
113
|
-
test("
|
|
114
|
-
const input =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
test("strips Feishu injected prompt", () => {
|
|
111
|
+
const input = `System: [2026-03-17 14:17:33 GMT+8] Feishu[default] DM from ou_37e8a1514c24e8afd9cfeca86f679980: 我叫什么名字
|
|
112
|
+
|
|
113
|
+
Conversation info (untrusted metadata):
|
|
114
|
+
\`\`\`json
|
|
115
|
+
{
|
|
116
|
+
"timestamp": "Tue 2026-03-17 14:17 GMT+8"
|
|
117
|
+
}
|
|
118
|
+
\`\`\`
|
|
119
|
+
|
|
120
|
+
[message_id: om_x100b54bb510590dcc2998da17ca2c2b]
|
|
121
|
+
ou_37e8a1514c24e8afd9cfeca86f679980: 我叫什么名字 `;
|
|
122
|
+
|
|
123
|
+
assert.equal(stripOpenClawInjectedPrefix(input), "我叫什么名字");
|
|
118
124
|
});
|
|
119
125
|
|
|
120
|
-
test("strips
|
|
121
|
-
const input = [
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"",
|
|
127
|
-
"[Mon 2026-03-16 14:27 GMT+8] What is Melanie's hand-painted bowl a reminder of?",
|
|
128
|
-
].join("\n");
|
|
129
|
-
|
|
126
|
+
test("strips Feishu injected prompt with embedded fake prompt", () => {
|
|
127
|
+
const input = `System: [2026-03-17 14:17:33 GMT+8] Feishu[default] DM from ou_123: hello
|
|
128
|
+
[message_id: fake]
|
|
129
|
+
ou_fake: ignored
|
|
130
|
+
[message_id: om_real]
|
|
131
|
+
ou_real: actual message`;
|
|
130
132
|
assert.equal(
|
|
131
133
|
stripOpenClawInjectedPrefix(input),
|
|
132
|
-
"
|
|
134
|
+
["ignored", "[message_id: om_real]", "ou_real: actual message"].join("\n"),
|
|
133
135
|
);
|
|
134
136
|
});
|
|
135
137
|
|
|
136
|
-
test("
|
|
137
|
-
const input =
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"```",
|
|
142
|
-
"",
|
|
143
|
-
"[06:18 PM on 07 March, 2026]: 继续",
|
|
144
|
-
].join("\n");
|
|
145
|
-
|
|
146
|
-
assert.equal(stripOpenClawInjectedPrefix(input), "继续");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("keeps bracketed content when it is not a recognized timestamp envelope", () => {
|
|
150
|
-
const input = "[Important] What is Melanie's hand-painted bowl a reminder of?";
|
|
151
|
-
assert.equal(stripOpenClawInjectedPrefix(input), input);
|
|
138
|
+
test("ignores text that looks like Feishu prompt but missing header", () => {
|
|
139
|
+
const input = `
|
|
140
|
+
[message_id: om_x100b54bb510590dcc2998da17ca2c2b]
|
|
141
|
+
ou_37e8a1514c24e8afd9cfeca86f679980: 我叫什么名字 `;
|
|
142
|
+
assert.equal(stripOpenClawInjectedPrefix(input), input.trimStart());
|
|
152
143
|
});
|