@humanlikememory/human-like-mem 0.3.12 → 0.3.14
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 +70 -70
- package/README_ZH.md +70 -70
- package/index.js +129 -477
- package/openclaw.plugin.json +3 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
# Human-Like Memory Plugin for OpenClaw
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@humanlikememory/human-like-mem)
|
|
4
|
-
|
|
5
|
-
Long-term memory plugin for OpenClaw with automatic memory recall and memory storage.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @humanlikememory/human-like-mem
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Enable In OpenClaw
|
|
14
|
-
|
|
15
|
-
Edit `~/.openclaw/openclaw.json`:
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"plugins": {
|
|
20
|
-
"entries": {
|
|
21
|
-
"human-like-mem": {
|
|
22
|
-
"enabled": true
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Required Environment Variables
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
export HUMAN_LIKE_MEM_API_KEY="mp_xxxxxx"
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Optional Environment Variables
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
export HUMAN_LIKE_MEM_BASE_URL="https://human-like.me"
|
|
39
|
-
export HUMAN_LIKE_MEM_USER_ID="your-user-id"
|
|
40
|
-
export HUMAN_LIKE_MEM_AGENT_ID="main"
|
|
41
|
-
export HUMAN_LIKE_MEM_LIMIT_NUMBER="6"
|
|
42
|
-
export HUMAN_LIKE_MEM_MIN_SCORE="0.1"
|
|
43
|
-
export HUMAN_LIKE_MEM_MIN_TURNS="5"
|
|
44
|
-
export HUMAN_LIKE_MEM_SESSION_TIMEOUT="300000"
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## How It Works
|
|
48
|
-
|
|
49
|
-
- Before response: the plugin retrieves relevant memories and injects them into context.
|
|
50
|
-
- After response: the plugin caches the conversation and flushes memory by turn threshold or timeout.
|
|
51
|
-
|
|
52
|
-
Default storage behavior:
|
|
53
|
-
|
|
54
|
-
- `minTurnsToStore = 5`
|
|
55
|
-
- `maxTurnsToStore = minTurnsToStore * 2`
|
|
56
|
-
- `sessionTimeoutMs = 300000` (5 minutes)
|
|
57
|
-
|
|
58
|
-
## Troubleshooting
|
|
59
|
-
|
|
60
|
-
- If you see `HUMAN_LIKE_MEM_API_KEY not configured`, make sure the key is set in your runtime environment.
|
|
61
|
-
- If you see request timeout logs, increase plugin `timeoutMs` (for example `30000`).
|
|
62
|
-
- Check logs with:
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
openclaw logs --plain --limit 200
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## License
|
|
69
|
-
|
|
70
|
-
Apache-2.0
|
|
1
|
+
# Human-Like Memory Plugin for OpenClaw
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@humanlikememory/human-like-mem)
|
|
4
|
+
|
|
5
|
+
Long-term memory plugin for OpenClaw with automatic memory recall and memory storage.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @humanlikememory/human-like-mem
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Enable In OpenClaw
|
|
14
|
+
|
|
15
|
+
Edit `~/.openclaw/openclaw.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"plugins": {
|
|
20
|
+
"entries": {
|
|
21
|
+
"human-like-mem": {
|
|
22
|
+
"enabled": true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Required Environment Variables
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export HUMAN_LIKE_MEM_API_KEY="mp_xxxxxx"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Optional Environment Variables
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export HUMAN_LIKE_MEM_BASE_URL="https://plugin.human-like.me"
|
|
39
|
+
export HUMAN_LIKE_MEM_USER_ID="your-user-id"
|
|
40
|
+
export HUMAN_LIKE_MEM_AGENT_ID="main"
|
|
41
|
+
export HUMAN_LIKE_MEM_LIMIT_NUMBER="6"
|
|
42
|
+
export HUMAN_LIKE_MEM_MIN_SCORE="0.1"
|
|
43
|
+
export HUMAN_LIKE_MEM_MIN_TURNS="5"
|
|
44
|
+
export HUMAN_LIKE_MEM_SESSION_TIMEOUT="300000"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## How It Works
|
|
48
|
+
|
|
49
|
+
- Before response: the plugin retrieves relevant memories and injects them into context.
|
|
50
|
+
- After response: the plugin caches the conversation and flushes memory by turn threshold or timeout.
|
|
51
|
+
|
|
52
|
+
Default storage behavior:
|
|
53
|
+
|
|
54
|
+
- `minTurnsToStore = 5`
|
|
55
|
+
- `maxTurnsToStore = minTurnsToStore * 2`
|
|
56
|
+
- `sessionTimeoutMs = 300000` (5 minutes)
|
|
57
|
+
|
|
58
|
+
## Troubleshooting
|
|
59
|
+
|
|
60
|
+
- If you see `HUMAN_LIKE_MEM_API_KEY not configured`, make sure the key is set in your runtime environment.
|
|
61
|
+
- If you see request timeout logs, increase plugin `timeoutMs` (for example `30000`).
|
|
62
|
+
- Check logs with:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
openclaw logs --plain --limit 200
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
Apache-2.0
|
package/README_ZH.md
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
# Human-Like Memory Plugin for OpenClaw
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@humanlikememory/human-like-mem)
|
|
4
|
-
|
|
5
|
-
OpenClaw 长期记忆插件,支持自动召回和自动存储对话记忆。
|
|
6
|
-
|
|
7
|
-
## 安装
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @humanlikememory/human-like-mem
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## 在 OpenClaw 中启用
|
|
14
|
-
|
|
15
|
-
编辑 `~/.openclaw/openclaw.json`:
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"plugins": {
|
|
20
|
-
"entries": {
|
|
21
|
-
"human-like-mem": {
|
|
22
|
-
"enabled": true
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## 必填环境变量
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
export HUMAN_LIKE_MEM_API_KEY="mp_xxxxxx"
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## 可选环境变量
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
export HUMAN_LIKE_MEM_BASE_URL="https://human-like.me"
|
|
39
|
-
export HUMAN_LIKE_MEM_USER_ID="your-user-id"
|
|
40
|
-
export HUMAN_LIKE_MEM_AGENT_ID="main"
|
|
41
|
-
export HUMAN_LIKE_MEM_LIMIT_NUMBER="6"
|
|
42
|
-
export HUMAN_LIKE_MEM_MIN_SCORE="0.1"
|
|
43
|
-
export HUMAN_LIKE_MEM_MIN_TURNS="5"
|
|
44
|
-
export HUMAN_LIKE_MEM_SESSION_TIMEOUT="300000"
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## 工作机制
|
|
48
|
-
|
|
49
|
-
- 回答前:插件检索相关记忆并注入上下文。
|
|
50
|
-
- 回答后:插件缓存会话,在满足轮次阈值或超时后写入记忆。
|
|
51
|
-
|
|
52
|
-
默认存储策略:
|
|
53
|
-
|
|
54
|
-
- `minTurnsToStore = 5`
|
|
55
|
-
- `maxTurnsToStore = minTurnsToStore * 2`
|
|
56
|
-
- `sessionTimeoutMs = 300000`(5 分钟)
|
|
57
|
-
|
|
58
|
-
## 常见问题
|
|
59
|
-
|
|
60
|
-
- 如果日志出现 `HUMAN_LIKE_MEM_API_KEY not configured`,请确认运行时环境变量已生效。
|
|
61
|
-
- 如果日志出现请求超时,请把插件 `timeoutMs` 调大(例如 `30000`)。
|
|
62
|
-
- 查看日志:
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
openclaw logs --plain --limit 200
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## 许可证
|
|
69
|
-
|
|
70
|
-
Apache-2.0
|
|
1
|
+
# Human-Like Memory Plugin for OpenClaw
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@humanlikememory/human-like-mem)
|
|
4
|
+
|
|
5
|
+
OpenClaw 长期记忆插件,支持自动召回和自动存储对话记忆。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @humanlikememory/human-like-mem
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 在 OpenClaw 中启用
|
|
14
|
+
|
|
15
|
+
编辑 `~/.openclaw/openclaw.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"plugins": {
|
|
20
|
+
"entries": {
|
|
21
|
+
"human-like-mem": {
|
|
22
|
+
"enabled": true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 必填环境变量
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export HUMAN_LIKE_MEM_API_KEY="mp_xxxxxx"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 可选环境变量
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export HUMAN_LIKE_MEM_BASE_URL="https://plugin.human-like.me"
|
|
39
|
+
export HUMAN_LIKE_MEM_USER_ID="your-user-id"
|
|
40
|
+
export HUMAN_LIKE_MEM_AGENT_ID="main"
|
|
41
|
+
export HUMAN_LIKE_MEM_LIMIT_NUMBER="6"
|
|
42
|
+
export HUMAN_LIKE_MEM_MIN_SCORE="0.1"
|
|
43
|
+
export HUMAN_LIKE_MEM_MIN_TURNS="5"
|
|
44
|
+
export HUMAN_LIKE_MEM_SESSION_TIMEOUT="300000"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 工作机制
|
|
48
|
+
|
|
49
|
+
- 回答前:插件检索相关记忆并注入上下文。
|
|
50
|
+
- 回答后:插件缓存会话,在满足轮次阈值或超时后写入记忆。
|
|
51
|
+
|
|
52
|
+
默认存储策略:
|
|
53
|
+
|
|
54
|
+
- `minTurnsToStore = 5`
|
|
55
|
+
- `maxTurnsToStore = minTurnsToStore * 2`
|
|
56
|
+
- `sessionTimeoutMs = 300000`(5 分钟)
|
|
57
|
+
|
|
58
|
+
## 常见问题
|
|
59
|
+
|
|
60
|
+
- 如果日志出现 `HUMAN_LIKE_MEM_API_KEY not configured`,请确认运行时环境变量已生效。
|
|
61
|
+
- 如果日志出现请求超时,请把插件 `timeoutMs` 调大(例如 `30000`)。
|
|
62
|
+
- 查看日志:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
openclaw logs --plain --limit 200
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 许可证
|
|
69
|
+
|
|
70
|
+
Apache-2.0
|
package/index.js
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* @license Apache-2.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const PLUGIN_VERSION = "0.3.4";
|
|
10
|
-
const USER_QUERY_MARKER = "--- User Query ---";
|
|
11
|
-
const CACHE_DEDUP_WINDOW_MS = 4000;
|
|
9
|
+
const PLUGIN_VERSION = "0.3.4";
|
|
10
|
+
const USER_QUERY_MARKER = "--- User Query ---";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Session cache for tracking conversation history
|
|
@@ -36,7 +35,7 @@ function warnMissingApiKey(log) {
|
|
|
36
35
|
Get your API key from: https://human-like.me
|
|
37
36
|
Then set:
|
|
38
37
|
export HUMAN_LIKE_MEM_API_KEY="mp_xxxxxx"
|
|
39
|
-
export HUMAN_LIKE_MEM_BASE_URL="https://human-like.me"
|
|
38
|
+
export HUMAN_LIKE_MEM_BASE_URL="https://plugin.human-like.me"
|
|
40
39
|
`;
|
|
41
40
|
if (log?.warn) {
|
|
42
41
|
log.warn(msg);
|
|
@@ -115,101 +114,31 @@ function extractFeishuTailBlock(text) {
|
|
|
115
114
|
/**
|
|
116
115
|
* Whether text is only transport metadata rather than a real utterance.
|
|
117
116
|
*/
|
|
118
|
-
function isMetadataOnlyText(text) {
|
|
119
|
-
if (!text) return true;
|
|
120
|
-
const value = String(text).trim();
|
|
121
|
-
if (!value) return true;
|
|
122
|
-
if (/^\[message_id:\s*[^\]]+\]$/i.test(value)) return true;
|
|
123
|
-
if (/^\[\[reply_to[^\]]*\]\]$/i.test(value)) return true;
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Remove injected relay metadata blocks/lines from user content.
|
|
129
|
-
*/
|
|
130
|
-
function stripInjectedMetadata(text) {
|
|
131
|
-
if (!text || typeof text !== "string") return "";
|
|
132
|
-
|
|
133
|
-
let result = text.replace(/\r\n/g, "\n");
|
|
134
|
-
|
|
135
|
-
// Remove labeled untrusted metadata JSON blocks.
|
|
136
|
-
// Examples:
|
|
137
|
-
// Conversation info (untrusted metadata): ```json ... ```
|
|
138
|
-
// Sender (untrusted metadata): ```json ... ```
|
|
139
|
-
result = result.replace(
|
|
140
|
-
/(^|\n)[^\n]*\(untrusted metadata(?:,\s*for context)?\)\s*:\s*```json[\s\S]*?```(?=\n|$)/gi,
|
|
141
|
-
"\n"
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
// Remove transport/system hint lines that are not user utterances.
|
|
145
|
-
result = result.replace(/^\[System:[^\n]*\]\s*$/gim, "");
|
|
146
|
-
result = result.replace(/^\[message_id:\s*[^\]]+\]\s*$/gim, "");
|
|
147
|
-
|
|
148
|
-
// Collapse extra blank lines.
|
|
149
|
-
result = result.replace(/\n{3,}/g, "\n\n").trim();
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Keep the latest meaningful line from cleaned metadata-injected payload.
|
|
155
|
-
*/
|
|
156
|
-
function extractLatestUtteranceFromCleanText(text) {
|
|
157
|
-
if (!text || typeof text !== "string") return "";
|
|
158
|
-
const lines = text
|
|
159
|
-
.split("\n")
|
|
160
|
-
.map((line) => line.trim())
|
|
161
|
-
.filter(Boolean)
|
|
162
|
-
.filter((line) => !isMetadataOnlyText(line));
|
|
163
|
-
if (lines.length === 0) return "";
|
|
164
|
-
return lines[lines.length - 1];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function escapeRegex(text) {
|
|
168
|
-
return String(text).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Prefix utterance with parsed sender name when available.
|
|
173
|
-
* This preserves who said it in multi-user group contexts.
|
|
174
|
-
*/
|
|
175
|
-
function attachSenderPrefix(utterance, senderName) {
|
|
176
|
-
const content = String(utterance || "").trim();
|
|
177
|
-
if (!content) return "";
|
|
178
|
-
const sender = String(senderName || "").trim();
|
|
179
|
-
if (!sender) return content;
|
|
180
|
-
|
|
181
|
-
const senderRegex = new RegExp(`^${escapeRegex(sender)}\\s*[::]`);
|
|
182
|
-
if (senderRegex.test(content)) return content;
|
|
183
|
-
return `${sender}: ${content}`;
|
|
184
|
-
}
|
|
117
|
+
function isMetadataOnlyText(text) {
|
|
118
|
+
if (!text) return true;
|
|
119
|
+
const value = String(text).trim();
|
|
120
|
+
if (!value) return true;
|
|
121
|
+
if (/^\[message_id:\s*[^\]]+\]$/i.test(value)) return true;
|
|
122
|
+
if (/^\[\[reply_to[^\]]*\]\]$/i.test(value)) return true;
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
185
125
|
|
|
186
126
|
/**
|
|
187
127
|
* Normalize user message text before caching/storing.
|
|
188
128
|
* For channel-formatted payloads (e.g. Feishu), keep only the actual user utterance
|
|
189
129
|
* and drop prepended system transcript lines.
|
|
190
130
|
*/
|
|
191
|
-
function normalizeUserMessageContent(content) {
|
|
192
|
-
const text = stripPrependedPrompt(content);
|
|
193
|
-
if (!text) return "";
|
|
194
|
-
|
|
195
|
-
const normalized = String(text).replace(/\r\n/g, "\n").trim();
|
|
196
|
-
if (!normalized) return "";
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (strippedMetadata && strippedMetadata !== normalized) {
|
|
203
|
-
const latestUtterance = extractLatestUtteranceFromCleanText(strippedMetadata);
|
|
204
|
-
if (latestUtterance && !isMetadataOnlyText(latestUtterance)) {
|
|
205
|
-
return attachSenderPrefix(latestUtterance, parsedIdentity?.userName);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Feishu: preserve the final traceable tail block (contains platform user id/message_id).
|
|
210
|
-
const feishuTailBlock = extractFeishuTailBlock(normalized);
|
|
211
|
-
if (feishuTailBlock && !isMetadataOnlyText(feishuTailBlock)) {
|
|
212
|
-
return feishuTailBlock;
|
|
131
|
+
function normalizeUserMessageContent(content) {
|
|
132
|
+
const text = stripPrependedPrompt(content);
|
|
133
|
+
if (!text) return "";
|
|
134
|
+
|
|
135
|
+
const normalized = String(text).replace(/\r\n/g, "\n").trim();
|
|
136
|
+
if (!normalized) return "";
|
|
137
|
+
|
|
138
|
+
// Feishu: preserve the final traceable tail block (contains platform user id/message_id).
|
|
139
|
+
const feishuTailBlock = extractFeishuTailBlock(normalized);
|
|
140
|
+
if (feishuTailBlock && !isMetadataOnlyText(feishuTailBlock)) {
|
|
141
|
+
return feishuTailBlock;
|
|
213
142
|
}
|
|
214
143
|
|
|
215
144
|
// Generic channel relays: fallback to latest "System: ...: <message>" line.
|
|
@@ -231,18 +160,16 @@ function normalizeUserMessageContent(content) {
|
|
|
231
160
|
|
|
232
161
|
// Discord-style channel-formatted payload:
|
|
233
162
|
// [from: username (id)] actual message
|
|
234
|
-
const discordTail = normalized.match(
|
|
235
|
-
/\[from:\s*[^\(\]\n]+?\s*\(\d{6,}\)\]\s*([\s\S]*?)$/i
|
|
236
|
-
);
|
|
237
|
-
if (discordTail && discordTail[1]) {
|
|
238
|
-
const candidate = discordTail[1].trim();
|
|
239
|
-
if (!isMetadataOnlyText(candidate)) return candidate;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return isMetadataOnlyText(normalized)
|
|
243
|
-
|
|
244
|
-
: attachSenderPrefix(normalized, parsedIdentity?.userName);
|
|
245
|
-
}
|
|
163
|
+
const discordTail = normalized.match(
|
|
164
|
+
/\[from:\s*[^\(\]\n]+?\s*\(\d{6,}\)\]\s*([\s\S]*?)$/i
|
|
165
|
+
);
|
|
166
|
+
if (discordTail && discordTail[1]) {
|
|
167
|
+
const candidate = discordTail[1].trim();
|
|
168
|
+
if (!isMetadataOnlyText(candidate)) return candidate;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return isMetadataOnlyText(normalized) ? "" : normalized;
|
|
172
|
+
}
|
|
246
173
|
|
|
247
174
|
/**
|
|
248
175
|
* Normalize assistant message text before caching/storing.
|
|
@@ -256,139 +183,12 @@ function normalizeAssistantMessageContent(content) {
|
|
|
256
183
|
return normalized;
|
|
257
184
|
}
|
|
258
185
|
|
|
259
|
-
/**
|
|
260
|
-
* Parse
|
|
261
|
-
*
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
* ```
|
|
266
|
-
*/
|
|
267
|
-
function parseJsonFencesAfterLabel(text, labelPattern) {
|
|
268
|
-
if (!text || typeof text !== "string") return [];
|
|
269
|
-
|
|
270
|
-
const results = [];
|
|
271
|
-
const regex = new RegExp(`${labelPattern}\\s*:\\s*\`\`\`json\\s*([\\s\\S]*?)\\s*\`\`\``, "gi");
|
|
272
|
-
let match;
|
|
273
|
-
while ((match = regex.exec(text)) !== null) {
|
|
274
|
-
if (!match[1]) continue;
|
|
275
|
-
try {
|
|
276
|
-
const parsed = JSON.parse(match[1]);
|
|
277
|
-
if (parsed && typeof parsed === "object") {
|
|
278
|
-
results.push(parsed);
|
|
279
|
-
}
|
|
280
|
-
} catch (_) {
|
|
281
|
-
// Ignore malformed JSON blocks and continue matching others.
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return results;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Normalize potential user id values into non-empty strings.
|
|
289
|
-
*/
|
|
290
|
-
function normalizeParsedUserId(value) {
|
|
291
|
-
if (value === undefined || value === null) return "";
|
|
292
|
-
const normalized = String(value).trim();
|
|
293
|
-
return normalized || "";
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Extract user id from a display label like:
|
|
298
|
-
* "用户250389 (ou_xxx)" / "name (123456789)".
|
|
299
|
-
*/
|
|
300
|
-
function extractUserIdFromLabel(label) {
|
|
301
|
-
const text = normalizeParsedUserId(label);
|
|
302
|
-
if (!text) return "";
|
|
303
|
-
const match = text.match(/\(\s*((?:ou|on|u)_[A-Za-z0-9]+|\d{6,})\s*\)$/i);
|
|
304
|
-
return match?.[1] ? match[1] : "";
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Extract transport message id from current relay payload.
|
|
309
|
-
*/
|
|
310
|
-
function extractRelayMessageId(text) {
|
|
311
|
-
if (!text || typeof text !== "string") return "";
|
|
312
|
-
|
|
313
|
-
const conversationMetaList = parseJsonFencesAfterLabel(
|
|
314
|
-
text,
|
|
315
|
-
"Conversation info \\(untrusted metadata\\)"
|
|
316
|
-
);
|
|
317
|
-
for (let i = conversationMetaList.length - 1; i >= 0; i--) {
|
|
318
|
-
const conversationMeta = conversationMetaList[i];
|
|
319
|
-
const messageId =
|
|
320
|
-
normalizeParsedUserId(conversationMeta?.message_id) ||
|
|
321
|
-
normalizeParsedUserId(conversationMeta?.messageId);
|
|
322
|
-
if (messageId) return messageId;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const messageIdMatch = text.match(/\[message_id:\s*([^\]\n]+)\]/i);
|
|
326
|
-
if (messageIdMatch?.[1]) return messageIdMatch[1].trim();
|
|
327
|
-
|
|
328
|
-
return "";
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Parse identity from new "untrusted metadata" JSON blocks.
|
|
333
|
-
* Priority:
|
|
334
|
-
* 1) Sender (untrusted metadata).id
|
|
335
|
-
* 2) Conversation info (untrusted metadata).sender_id
|
|
336
|
-
*/
|
|
337
|
-
function parseIdentityFromUntrustedMetadata(text) {
|
|
338
|
-
if (!text || typeof text !== "string") return null;
|
|
339
|
-
|
|
340
|
-
const senderMetaList = parseJsonFencesAfterLabel(text, "Sender \\(untrusted metadata\\)");
|
|
341
|
-
for (let i = senderMetaList.length - 1; i >= 0; i--) {
|
|
342
|
-
const senderMeta = senderMetaList[i];
|
|
343
|
-
const userId =
|
|
344
|
-
normalizeParsedUserId(senderMeta?.id) ||
|
|
345
|
-
extractUserIdFromLabel(senderMeta?.label);
|
|
346
|
-
if (userId) {
|
|
347
|
-
return {
|
|
348
|
-
platform: null,
|
|
349
|
-
userId,
|
|
350
|
-
userName: senderMeta?.name || senderMeta?.username || senderMeta?.label || null,
|
|
351
|
-
source: "sender-untrusted-metadata-json",
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const conversationMetaList = parseJsonFencesAfterLabel(
|
|
357
|
-
text,
|
|
358
|
-
"Conversation info \\(untrusted metadata\\)"
|
|
359
|
-
);
|
|
360
|
-
for (let i = conversationMetaList.length - 1; i >= 0; i--) {
|
|
361
|
-
const conversationMeta = conversationMetaList[i];
|
|
362
|
-
const userId =
|
|
363
|
-
normalizeParsedUserId(conversationMeta?.sender_id) ||
|
|
364
|
-
normalizeParsedUserId(conversationMeta?.senderId);
|
|
365
|
-
if (userId) {
|
|
366
|
-
return {
|
|
367
|
-
platform: null,
|
|
368
|
-
userId,
|
|
369
|
-
userName: conversationMeta?.sender || null,
|
|
370
|
-
source: "conversation-untrusted-metadata-json",
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Parse platform identity hints from channel-formatted message text.
|
|
380
|
-
* Returns null when no platform-specific id can be extracted.
|
|
381
|
-
*/
|
|
382
|
-
function parsePlatformIdentity(text) {
|
|
383
|
-
if (!text || typeof text !== "string") return null;
|
|
384
|
-
|
|
385
|
-
// New Discord format first:
|
|
386
|
-
// Sender (untrusted metadata): ```json { "id": "116..." } ```
|
|
387
|
-
// Conversation info (untrusted metadata): ```json { "sender_id": "116..." } ```
|
|
388
|
-
const metadataIdentity = parseIdentityFromUntrustedMetadata(text);
|
|
389
|
-
if (metadataIdentity?.userId) {
|
|
390
|
-
return metadataIdentity;
|
|
391
|
-
}
|
|
186
|
+
/**
|
|
187
|
+
* Parse platform identity hints from channel-formatted message text.
|
|
188
|
+
* Returns null when no platform-specific id can be extracted.
|
|
189
|
+
*/
|
|
190
|
+
function parsePlatformIdentity(text) {
|
|
191
|
+
if (!text || typeof text !== "string") return null;
|
|
392
192
|
|
|
393
193
|
// Discord example:
|
|
394
194
|
// [from: huang yongqing (1470374017541079042)]
|
|
@@ -420,39 +220,18 @@ function parsePlatformIdentity(text) {
|
|
|
420
220
|
/**
|
|
421
221
|
* Parse all platform user ids from a text blob.
|
|
422
222
|
*/
|
|
423
|
-
function parseAllPlatformUserIds(text) {
|
|
424
|
-
if (!text || typeof text !== "string") return [];
|
|
425
|
-
|
|
426
|
-
const ids = [];
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
//
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (parsedId) ids.push(parsedId);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// New format: "Conversation info (untrusted metadata)" JSON blocks.
|
|
439
|
-
const conversationMetaList = parseJsonFencesAfterLabel(
|
|
440
|
-
text,
|
|
441
|
-
"Conversation info \\(untrusted metadata\\)"
|
|
442
|
-
);
|
|
443
|
-
for (const conversationMeta of conversationMetaList) {
|
|
444
|
-
const parsedId =
|
|
445
|
-
normalizeParsedUserId(conversationMeta?.sender_id) ||
|
|
446
|
-
normalizeParsedUserId(conversationMeta?.senderId);
|
|
447
|
-
if (parsedId) ids.push(parsedId);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Discord example:
|
|
451
|
-
// [from: huang yongqing (1470374017541079042)]
|
|
452
|
-
const discordRegex = /\[from:\s*[^\(\]\n]+?\s*\((\d{6,})\)\]/gi;
|
|
453
|
-
while ((match = discordRegex.exec(text)) !== null) {
|
|
454
|
-
if (match[1]) ids.push(match[1]);
|
|
455
|
-
}
|
|
223
|
+
function parseAllPlatformUserIds(text) {
|
|
224
|
+
if (!text || typeof text !== "string") return [];
|
|
225
|
+
|
|
226
|
+
const ids = [];
|
|
227
|
+
|
|
228
|
+
// Discord example:
|
|
229
|
+
// [from: huang yongqing (1470374017541079042)]
|
|
230
|
+
const discordRegex = /\[from:\s*[^\(\]\n]+?\s*\((\d{6,})\)\]/gi;
|
|
231
|
+
let match;
|
|
232
|
+
while ((match = discordRegex.exec(text)) !== null) {
|
|
233
|
+
if (match[1]) ids.push(match[1]);
|
|
234
|
+
}
|
|
456
235
|
|
|
457
236
|
// Feishu example:
|
|
458
237
|
// [Feishu ...:ou_17b624... Wed ...] username: message
|
|
@@ -467,8 +246,8 @@ function parseAllPlatformUserIds(text) {
|
|
|
467
246
|
/**
|
|
468
247
|
* Collect distinct user ids parsed from all messages.
|
|
469
248
|
*/
|
|
470
|
-
function collectUniqueUserIdsFromMessages(messages, fallbackUserId) {
|
|
471
|
-
const unique = new Set();
|
|
249
|
+
function collectUniqueUserIdsFromMessages(messages, fallbackUserId) {
|
|
250
|
+
const unique = new Set();
|
|
472
251
|
|
|
473
252
|
if (Array.isArray(messages)) {
|
|
474
253
|
for (const msg of messages) {
|
|
@@ -484,96 +263,12 @@ function collectUniqueUserIdsFromMessages(messages, fallbackUserId) {
|
|
|
484
263
|
unique.add(fallbackUserId);
|
|
485
264
|
}
|
|
486
265
|
|
|
487
|
-
return Array.from(unique);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const match = text.match(
|
|
494
|
-
/^(.*?)\s*\(\s*(?:(?:ou|on|u)_[A-Za-z0-9]+|\d{6,})\s*\)\s*$/i
|
|
495
|
-
);
|
|
496
|
-
return match?.[1] ? match[1].trim() : text;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Parse name-id pairs from supported relay payload formats.
|
|
501
|
-
*/
|
|
502
|
-
function parseNameIdPairsFromText(text) {
|
|
503
|
-
if (!text || typeof text !== "string") return [];
|
|
504
|
-
const pairs = [];
|
|
505
|
-
|
|
506
|
-
// New format: Sender (untrusted metadata)
|
|
507
|
-
const senderMetaList = parseJsonFencesAfterLabel(text, "Sender \\(untrusted metadata\\)");
|
|
508
|
-
for (const senderMeta of senderMetaList) {
|
|
509
|
-
const id =
|
|
510
|
-
normalizeParsedUserId(senderMeta?.id) ||
|
|
511
|
-
extractUserIdFromLabel(senderMeta?.label);
|
|
512
|
-
const name =
|
|
513
|
-
normalizeParsedUserId(senderMeta?.name) ||
|
|
514
|
-
normalizeParsedUserId(senderMeta?.username) ||
|
|
515
|
-
extractUserNameFromLabel(senderMeta?.label);
|
|
516
|
-
if (name && id) pairs.push([name, id]);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// New format: Conversation info (untrusted metadata)
|
|
520
|
-
const conversationMetaList = parseJsonFencesAfterLabel(
|
|
521
|
-
text,
|
|
522
|
-
"Conversation info \\(untrusted metadata\\)"
|
|
523
|
-
);
|
|
524
|
-
for (const conversationMeta of conversationMetaList) {
|
|
525
|
-
const id =
|
|
526
|
-
normalizeParsedUserId(conversationMeta?.sender_id) ||
|
|
527
|
-
normalizeParsedUserId(conversationMeta?.senderId);
|
|
528
|
-
const name = normalizeParsedUserId(conversationMeta?.sender);
|
|
529
|
-
if (name && id) pairs.push([name, id]);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Old Discord: [from: name (id)]
|
|
533
|
-
const discordRegex = /\[from:\s*([^\(\]\n]+?)\s*\((\d{6,})\)\]/gi;
|
|
534
|
-
let match;
|
|
535
|
-
while ((match = discordRegex.exec(text)) !== null) {
|
|
536
|
-
const name = normalizeParsedUserId(match[1]);
|
|
537
|
-
const id = normalizeParsedUserId(match[2]);
|
|
538
|
-
if (name && id) pairs.push([name, id]);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Old Feishu: [Feishu ...:ou_xxx ...] name:
|
|
542
|
-
const feishuRegex = /\[Feishu[^\]]*:((?:ou|on|u)_[A-Za-z0-9]+)[^\]]*\]\s*([^:\n]+):/gi;
|
|
543
|
-
while ((match = feishuRegex.exec(text)) !== null) {
|
|
544
|
-
const id = normalizeParsedUserId(match[1]);
|
|
545
|
-
const name = normalizeParsedUserId(match[2]);
|
|
546
|
-
if (name && id) pairs.push([name, id]);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
return pairs;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Collect distinct name->id mapping from all messages.
|
|
554
|
-
*/
|
|
555
|
-
function collectNameIdMapFromMessages(messages) {
|
|
556
|
-
const map = {};
|
|
557
|
-
|
|
558
|
-
if (!Array.isArray(messages)) return map;
|
|
559
|
-
|
|
560
|
-
for (const msg of messages) {
|
|
561
|
-
if (!msg) continue;
|
|
562
|
-
const sourceText = msg.rawContent !== undefined ? msg.rawContent : msg.content;
|
|
563
|
-
const text = typeof sourceText === "string" ? sourceText : extractText(sourceText);
|
|
564
|
-
const pairs = parseNameIdPairsFromText(text);
|
|
565
|
-
for (const [name, id] of pairs) {
|
|
566
|
-
if (!name || !id) continue;
|
|
567
|
-
map[name] = id;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return map;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Get latest user message text from cached messages.
|
|
576
|
-
*/
|
|
266
|
+
return Array.from(unique);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get latest user message text from cached messages.
|
|
271
|
+
*/
|
|
577
272
|
function getLatestUserMessageText(messages) {
|
|
578
273
|
if (!Array.isArray(messages)) return "";
|
|
579
274
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -589,9 +284,9 @@ function getLatestUserMessageText(messages) {
|
|
|
589
284
|
* Resolve request identity with fallback:
|
|
590
285
|
* platform user id -> configured user id -> "openclaw-user"
|
|
591
286
|
*/
|
|
592
|
-
function resolveRequestIdentity(promptText, cfg) {
|
|
593
|
-
const parsed = parsePlatformIdentity(promptText);
|
|
594
|
-
if (parsed?.userId) {
|
|
287
|
+
function resolveRequestIdentity(promptText, cfg, ctx) {
|
|
288
|
+
const parsed = parsePlatformIdentity(promptText);
|
|
289
|
+
if (parsed?.userId) {
|
|
595
290
|
return {
|
|
596
291
|
userId: parsed.userId,
|
|
597
292
|
userName: parsed.userName || null,
|
|
@@ -600,7 +295,7 @@ function resolveRequestIdentity(promptText, cfg) {
|
|
|
600
295
|
};
|
|
601
296
|
}
|
|
602
297
|
|
|
603
|
-
if (cfg?.configuredUserId) {
|
|
298
|
+
if (cfg?.configuredUserId) {
|
|
604
299
|
return {
|
|
605
300
|
userId: cfg.configuredUserId,
|
|
606
301
|
userName: null,
|
|
@@ -609,8 +304,17 @@ function resolveRequestIdentity(promptText, cfg) {
|
|
|
609
304
|
};
|
|
610
305
|
}
|
|
611
306
|
|
|
612
|
-
|
|
613
|
-
|
|
307
|
+
if (ctx?.userId) {
|
|
308
|
+
return {
|
|
309
|
+
userId: ctx.userId,
|
|
310
|
+
userName: null,
|
|
311
|
+
platform: null,
|
|
312
|
+
source: "ctx-user-id",
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
userId: "openclaw-user",
|
|
614
318
|
userName: null,
|
|
615
319
|
platform: null,
|
|
616
320
|
source: "default-user-id",
|
|
@@ -803,7 +507,7 @@ async function retrieveMemory(prompt, cfg, ctx, log) {
|
|
|
803
507
|
if (log?.info) {
|
|
804
508
|
log.info(`[Memory Plugin] Recall request URL: ${url}`);
|
|
805
509
|
}
|
|
806
|
-
const identity = resolveRequestIdentity(prompt, cfg);
|
|
510
|
+
const identity = resolveRequestIdentity(prompt, cfg, ctx);
|
|
807
511
|
const userId = sanitizeUserId(identity.userId);
|
|
808
512
|
const payload = {
|
|
809
513
|
query: prompt,
|
|
@@ -862,25 +566,18 @@ async function addMemory(messages, cfg, ctx, log) {
|
|
|
862
566
|
}
|
|
863
567
|
const sessionId = resolveSessionId(ctx, null) || `session-${Date.now()}`;
|
|
864
568
|
const latestUserText = getLatestUserMessageText(messages);
|
|
865
|
-
const identity = resolveRequestIdentity(latestUserText, cfg);
|
|
569
|
+
const identity = resolveRequestIdentity(latestUserText, cfg, ctx);
|
|
866
570
|
const userId = sanitizeUserId(identity.userId);
|
|
867
|
-
const metadataUserIds = (() => {
|
|
868
|
-
const parsed = collectUniqueUserIdsFromMessages(messages, null)
|
|
869
|
-
.map((id) => sanitizeUserId(id))
|
|
870
|
-
.filter(Boolean);
|
|
871
|
-
if (parsed.length > 0) {
|
|
872
|
-
return Array.from(new Set(parsed));
|
|
873
|
-
}
|
|
874
|
-
return [userId];
|
|
875
|
-
})();
|
|
876
|
-
const
|
|
877
|
-
const parsed = collectNameIdMapFromMessages(messages);
|
|
878
|
-
if (identity?.userName && userId && !parsed[identity.userName]) {
|
|
879
|
-
parsed[identity.userName] = userId;
|
|
880
|
-
}
|
|
881
|
-
return parsed;
|
|
882
|
-
})();
|
|
883
|
-
const agentId = cfg.agentId || ctx?.agentId || "main";
|
|
571
|
+
const metadataUserIds = (() => {
|
|
572
|
+
const parsed = collectUniqueUserIdsFromMessages(messages, null)
|
|
573
|
+
.map((id) => sanitizeUserId(id))
|
|
574
|
+
.filter(Boolean);
|
|
575
|
+
if (parsed.length > 0) {
|
|
576
|
+
return Array.from(new Set(parsed));
|
|
577
|
+
}
|
|
578
|
+
return [userId];
|
|
579
|
+
})();
|
|
580
|
+
const agentId = cfg.agentId || ctx?.agentId || "main";
|
|
884
581
|
|
|
885
582
|
const payload = {
|
|
886
583
|
user_id: userId,
|
|
@@ -894,22 +591,21 @@ async function addMemory(messages, cfg, ctx, log) {
|
|
|
894
591
|
async_mode: true,
|
|
895
592
|
custom_workflows: {
|
|
896
593
|
stream_params: {
|
|
897
|
-
metadata: JSON.stringify({
|
|
898
|
-
user_ids: metadataUserIds,
|
|
899
|
-
agent_ids: [agentId],
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
},
|
|
594
|
+
metadata: JSON.stringify({
|
|
595
|
+
user_ids: metadataUserIds,
|
|
596
|
+
agent_ids: [agentId],
|
|
597
|
+
session_id: sessionId,
|
|
598
|
+
scenario: cfg.scenario || "openclaw-plugin",
|
|
599
|
+
}),
|
|
600
|
+
},
|
|
601
|
+
},
|
|
906
602
|
};
|
|
907
603
|
|
|
908
604
|
if (log?.debug) {
|
|
909
|
-
log.debug(
|
|
910
|
-
`[Memory Plugin] add/message payload: user_id=${userId}, agent_id=${agentId}, conversation_id=${sessionId}, metadata.user_ids=${JSON.stringify(metadataUserIds)}, metadata.agent_ids=${JSON.stringify([agentId])}
|
|
911
|
-
);
|
|
912
|
-
}
|
|
605
|
+
log.debug(
|
|
606
|
+
`[Memory Plugin] add/message payload: user_id=${userId}, agent_id=${agentId}, conversation_id=${sessionId}, metadata.user_ids=${JSON.stringify(metadataUserIds)}, metadata.agent_ids=${JSON.stringify([agentId])}`
|
|
607
|
+
);
|
|
608
|
+
}
|
|
913
609
|
|
|
914
610
|
try {
|
|
915
611
|
const result = await httpRequest(url, {
|
|
@@ -1154,44 +850,18 @@ function getSessionCache(sessionId) {
|
|
|
1154
850
|
/**
|
|
1155
851
|
* Add message to session cache
|
|
1156
852
|
*/
|
|
1157
|
-
function addToSessionCache(sessionId, message) {
|
|
1158
|
-
const cache = getSessionCache(sessionId);
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
incoming.messageId &&
|
|
1170
|
-
prev.messageId &&
|
|
1171
|
-
String(incoming.messageId) === String(prev.messageId);
|
|
1172
|
-
const sameContent =
|
|
1173
|
-
sameRole &&
|
|
1174
|
-
String(prev.content || "") === String(incoming.content || "") &&
|
|
1175
|
-
String(prev.rawContent || "") === String(incoming.rawContent || "");
|
|
1176
|
-
const withinWindow = now - (prev.cachedAt || 0) <= CACHE_DEDUP_WINDOW_MS;
|
|
1177
|
-
|
|
1178
|
-
// Dedup duplicate hook triggers for the same relay message.
|
|
1179
|
-
if ((sameRole && sameMessageId) || (sameContent && withinWindow)) {
|
|
1180
|
-
cache.lastActivity = now;
|
|
1181
|
-
return cache;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
cache.messages.push(incoming);
|
|
1186
|
-
cache.lastActivity = Date.now();
|
|
1187
|
-
|
|
1188
|
-
// Count turns (user message after assistant = new turn)
|
|
1189
|
-
if (incoming.role === "user" && cache.messages.length > 1) {
|
|
1190
|
-
const prevMsg = cache.messages[cache.messages.length - 2];
|
|
1191
|
-
if (prevMsg && prevMsg.role === "assistant") {
|
|
1192
|
-
cache.turnCount++;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
853
|
+
function addToSessionCache(sessionId, message) {
|
|
854
|
+
const cache = getSessionCache(sessionId);
|
|
855
|
+
cache.messages.push(message);
|
|
856
|
+
cache.lastActivity = Date.now();
|
|
857
|
+
|
|
858
|
+
// Count turns (user message after assistant = new turn)
|
|
859
|
+
if (message.role === "user" && cache.messages.length > 1) {
|
|
860
|
+
const prevMsg = cache.messages[cache.messages.length - 2];
|
|
861
|
+
if (prevMsg && prevMsg.role === "assistant") {
|
|
862
|
+
cache.turnCount++;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
1195
865
|
|
|
1196
866
|
return cache;
|
|
1197
867
|
}
|
|
@@ -1348,18 +1018,12 @@ function registerPlugin(api) {
|
|
|
1348
1018
|
return;
|
|
1349
1019
|
}
|
|
1350
1020
|
|
|
1351
|
-
const sessionId = resolveSessionId(ctx, event) || `session-${Date.now()}`;
|
|
1352
|
-
const userContent = normalizeUserMessageContent(prompt);
|
|
1353
|
-
const rawUserContent = stripPrependedPrompt(prompt);
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
role: "user",
|
|
1358
|
-
content: userContent,
|
|
1359
|
-
rawContent: rawUserContent,
|
|
1360
|
-
messageId: messageId || undefined,
|
|
1361
|
-
});
|
|
1362
|
-
}
|
|
1021
|
+
const sessionId = resolveSessionId(ctx, event) || `session-${Date.now()}`;
|
|
1022
|
+
const userContent = normalizeUserMessageContent(prompt);
|
|
1023
|
+
const rawUserContent = stripPrependedPrompt(prompt);
|
|
1024
|
+
if (userContent) {
|
|
1025
|
+
addToSessionCache(sessionId, { role: "user", content: userContent, rawContent: rawUserContent });
|
|
1026
|
+
}
|
|
1363
1027
|
|
|
1364
1028
|
try {
|
|
1365
1029
|
const memories = await retrieveMemory(prompt, cfg, ctx, log);
|
|
@@ -1486,18 +1150,12 @@ function createHooksPlugin(config) {
|
|
|
1486
1150
|
return;
|
|
1487
1151
|
}
|
|
1488
1152
|
|
|
1489
|
-
const sessionId = resolveSessionId(ctx, event) || `session-${Date.now()}`;
|
|
1490
|
-
const userContent = normalizeUserMessageContent(prompt);
|
|
1491
|
-
const rawUserContent = stripPrependedPrompt(prompt);
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
role: "user",
|
|
1496
|
-
content: userContent,
|
|
1497
|
-
rawContent: rawUserContent,
|
|
1498
|
-
messageId: messageId || undefined,
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1153
|
+
const sessionId = resolveSessionId(ctx, event) || `session-${Date.now()}`;
|
|
1154
|
+
const userContent = normalizeUserMessageContent(prompt);
|
|
1155
|
+
const rawUserContent = stripPrependedPrompt(prompt);
|
|
1156
|
+
if (userContent) {
|
|
1157
|
+
addToSessionCache(sessionId, { role: "user", content: userContent, rawContent: rawUserContent });
|
|
1158
|
+
}
|
|
1501
1159
|
|
|
1502
1160
|
try {
|
|
1503
1161
|
const memories = await retrieveMemory(prompt, cfg, ctx, log);
|
|
@@ -1565,19 +1223,13 @@ function createHooksPlugin(config) {
|
|
|
1565
1223
|
const content = msg.role === "user"
|
|
1566
1224
|
? normalizeUserMessageContent(msg.content)
|
|
1567
1225
|
: normalizeAssistantMessageContent(msg.content);
|
|
1568
|
-
const rawSource = msg.role === "user"
|
|
1569
|
-
? stripPrependedPrompt(msg.content)
|
|
1570
|
-
: extractText(msg.content);
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
content,
|
|
1576
|
-
rawContent: rawSource || undefined,
|
|
1577
|
-
messageId: messageId || undefined,
|
|
1578
|
-
});
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1226
|
+
const rawSource = msg.role === "user"
|
|
1227
|
+
? stripPrependedPrompt(msg.content)
|
|
1228
|
+
: extractText(msg.content);
|
|
1229
|
+
if (content) {
|
|
1230
|
+
addToSessionCache(sessionId, { role: msg.role, content, rawContent: rawSource || undefined });
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1581
1233
|
} else {
|
|
1582
1234
|
// Only add last assistant message
|
|
1583
1235
|
for (let i = event.messages.length - 1; i >= 0; i--) {
|
|
@@ -1659,7 +1311,7 @@ function buildConfig(config) {
|
|
|
1659
1311
|
const configuredUserId = config?.userId || process.env.HUMAN_LIKE_MEM_USER_ID;
|
|
1660
1312
|
|
|
1661
1313
|
return {
|
|
1662
|
-
baseUrl: config?.baseUrl || process.env.HUMAN_LIKE_MEM_BASE_URL || "https://human-like.me",
|
|
1314
|
+
baseUrl: config?.baseUrl || process.env.HUMAN_LIKE_MEM_BASE_URL || "https://plugin.human-like.me",
|
|
1663
1315
|
apiKey: config?.apiKey || process.env.HUMAN_LIKE_MEM_API_KEY,
|
|
1664
1316
|
configuredUserId: configuredUserId,
|
|
1665
1317
|
userId: configuredUserId || "openclaw-user",
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "human-like-mem",
|
|
4
4
|
"name": "Human-Like Memory Plugin",
|
|
5
5
|
"description": "Long-term memory plugin with automatic recall and storage.",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.14",
|
|
7
7
|
"kind": "lifecycle",
|
|
8
8
|
"main": "./index.js",
|
|
9
9
|
"author": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"baseUrl": {
|
|
26
26
|
"type": "string",
|
|
27
27
|
"description": "Memory API base URL",
|
|
28
|
-
"default": "https://human-like.me"
|
|
28
|
+
"default": "https://plugin.human-like.me"
|
|
29
29
|
},
|
|
30
30
|
"userId": {
|
|
31
31
|
"type": "string",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"baseUrl": {
|
|
91
91
|
"type": "string",
|
|
92
92
|
"description": "Memory API base URL",
|
|
93
|
-
"default": "https://human-like.me",
|
|
93
|
+
"default": "https://plugin.human-like.me",
|
|
94
94
|
"env": "HUMAN_LIKE_MEM_BASE_URL"
|
|
95
95
|
},
|
|
96
96
|
"userId": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanlikememory/human-like-mem",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Long-term memory plugin for OpenClaw - AI Social Memory Integration",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"jest": "^29.7.0",
|
|
51
51
|
"prettier": "^3.2.0"
|
|
52
52
|
},
|
|
53
|
-
"publishConfig": {
|
|
54
|
-
"access": "public"
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
55
|
}
|
|
56
56
|
}
|