@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 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\\package"
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\\package"
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");
@@ -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: cfg.captureStrategy ?? "last_turn",
193
- maxMessageChars: cfg.maxMessageChars ?? 20000,
200
+ captureStrategy,
201
+ maxMessageChars,
194
202
  maxItemChars: cfg.maxItemChars ?? 8000,
195
- includeAssistant: cfg.includeAssistant !== false,
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: cfg.asyncMode ?? true,
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: cfg.throttleMs ?? 0,
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
- return callApi(cfg, "/add/message", payload);
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 markerIndex = text.lastIndexOf(USER_QUERY_MARKER);
387
+ const cleanedText = stripFeishuInjectedPrompt(text);
388
+ const markerIndex = cleanedText.lastIndexOf(USER_QUERY_MARKER);
339
389
  const withoutRecallPrefix =
340
- markerIndex === -1 ? text : text.slice(markerIndex + USER_QUERY_MARKER.length);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memtensor/memos-cloud-openclaw-plugin",
3
- "version": "0.1.10-beta.0",
3
+ "version": "0.1.10-beta.1",
4
4
  "description": "OpenClaw lifecycle plugin for MemOS Cloud (add + recall memory)",
5
5
  "scripts": {
6
6
  "sync-version": "node scripts/sync-version.js",
@@ -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("supports windows newlines", () => {
114
- const input =
115
- "Conversation info (untrusted metadata):\r\n```json\r\n{\"message_id\":\"123\"}\r\n```\r\n\r\n继续";
116
-
117
- assert.equal(stripOpenClawInjectedPrefix(input), "继续");
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 gateway-client sender block and leading weekday timestamp envelope", () => {
121
- const input = [
122
- "Sender (untrusted metadata):",
123
- "```json",
124
- '{"label":"openclaw-tui (gateway-client)","id":"gateway-client"}',
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
- "What is Melanie's hand-painted bowl a reminder of?",
134
+ ["ignored", "[message_id: om_real]", "ou_real: actual message"].join("\n"),
133
135
  );
134
136
  });
135
137
 
136
- test("strips leading pm-on-date envelope after inbound metadata", () => {
137
- const input = [
138
- "Sender (untrusted metadata):",
139
- "```json",
140
- '{"label":"openclaw-tui (gateway-client)","id":"gateway-client"}',
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
  });