@creatoraris/openclaw-wecom 0.3.0 → 0.4.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 +73 -242
- package/index.js +69 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,300 +5,131 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@creatoraris/openclaw-wecom)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 特性
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
10
|
+
- 支持流式回复(分段发送,体验更流畅)
|
|
11
|
+
- 支持消息加密(符合企微安全规范)
|
|
12
|
+
- 支持单聊和群聊
|
|
13
|
+
- 支持图片消息(自动解密 WeCom 加密图片)
|
|
14
|
+
- 支持上下文重置(发送 `/reset` 或 `/重置`)
|
|
15
|
+
- 自动消息去重
|
|
16
|
+
- 作为 OpenClaw 插件运行,随 OpenClaw Gateway 自动启停
|
|
15
17
|
|
|
16
|
-
##
|
|
18
|
+
## 前置条件
|
|
17
19
|
|
|
18
20
|
- OpenClaw 已安装并运行
|
|
19
21
|
- Node.js >= 18.0.0
|
|
20
22
|
- 企业微信账号(个人也可注册企业版,1-9 人免费)
|
|
21
|
-
- 一台可部署 Webhook 服务的机器(或使用 ngrok
|
|
23
|
+
- 一台可部署 Webhook 服务的机器(或使用 ngrok / Tailscale 进行内网穿透)
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## 快速开始
|
|
24
26
|
|
|
25
|
-
###
|
|
26
|
-
|
|
27
|
-
### 步骤 1:安装插件(2 分钟)
|
|
27
|
+
### 步骤 1:安装插件
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
openclaw plugins install @creatoraris/openclaw-wecom
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
### 步骤 2
|
|
34
|
-
|
|
35
|
-
#### 2.1 注册企业微信
|
|
36
|
-
|
|
37
|
-
如果还没有企业微信账号:
|
|
38
|
-
|
|
39
|
-
1. 访问 [企业微信官网](https://work.weixin.qq.com/)
|
|
40
|
-
2. 点击「注册企业」
|
|
41
|
-
3. 填写信息(可以用个人身份注册)
|
|
42
|
-
4. 验证手机号
|
|
43
|
-
|
|
44
|
-
#### 2.2 创建智能助手机器人
|
|
33
|
+
### 步骤 2:创建企微智能助手机器人
|
|
45
34
|
|
|
46
35
|
1. 登录 [企业微信管理后台](https://work.weixin.qq.com/wework_admin/)
|
|
47
|
-
2.
|
|
36
|
+
2. 左侧菜单:「应用管理」 -> 「应用」
|
|
48
37
|
3. 找到「智能助手」,点击进入
|
|
49
|
-
4.
|
|
50
|
-
5.
|
|
51
|
-
6.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
1. 在智能助手详情页,点击「接收消息」标签
|
|
56
|
-
2. 设置回调 URL:
|
|
57
|
-
```
|
|
58
|
-
https://your-domain.com/wecom/callback
|
|
59
|
-
```
|
|
60
|
-
> 💡 本地开发可以用 [ngrok](https://ngrok.com/):`https://xxx.ngrok.io/wecom/callback`
|
|
61
|
-
|
|
62
|
-
3. 点击「生成 Token 和 EncodingAESKey」
|
|
63
|
-
4. **保存这三个值**(下一步要用):
|
|
64
|
-
- Token: `abc123...`
|
|
65
|
-
- EncodingAESKey: `xyz789...`
|
|
66
|
-
- Webhook URL: `https://your-domain.com/wecom/callback`
|
|
67
|
-
|
|
68
|
-
5. 点击「保存」
|
|
38
|
+
4. 点击「创建智能助手」,填写名称(如:OpenClaw AI 助手)
|
|
39
|
+
5. 在智能助手详情页,点击「接收消息」标签
|
|
40
|
+
6. 设置回调 URL:`https://your-domain.com/callback`
|
|
41
|
+
7. 点击「生成 Token 和 EncodingAESKey」
|
|
42
|
+
8. 记录 Token 和 EncodingAESKey(下一步配置要用)
|
|
43
|
+
9. 点击「保存」
|
|
69
44
|
|
|
70
|
-
|
|
45
|
+
> 本地开发可以用 ngrok:`ngrok http 8788`,将生成的 URL 填入回调
|
|
71
46
|
|
|
72
|
-
### 步骤 3
|
|
47
|
+
### 步骤 3:配置插件
|
|
73
48
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
编辑 `~/.openclaw/openclaw.json`:
|
|
49
|
+
编辑 `~/.openclaw/openclaw.json`,在 `plugins.entries` 中添加:
|
|
77
50
|
|
|
78
51
|
```json
|
|
79
52
|
{
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
53
|
+
"plugins": {
|
|
54
|
+
"entries": {
|
|
55
|
+
"openclaw-wecom": {
|
|
56
|
+
"enabled": true,
|
|
57
|
+
"config": {
|
|
58
|
+
"token": "你的 Token",
|
|
59
|
+
"encodingAESKey": "你的 EncodingAESKey",
|
|
60
|
+
"corpId": "你的企业 ID",
|
|
61
|
+
"port": 8788
|
|
62
|
+
}
|
|
63
|
+
}
|
|
87
64
|
}
|
|
88
65
|
}
|
|
89
66
|
}
|
|
90
67
|
```
|
|
91
68
|
|
|
92
|
-
|
|
69
|
+
企业 ID (corpId) 可在企业微信管理后台「我的企业」页面底部找到。
|
|
93
70
|
|
|
94
|
-
|
|
71
|
+
### 步骤 4:重启 OpenClaw
|
|
95
72
|
|
|
96
73
|
```bash
|
|
97
|
-
|
|
98
|
-
WECOM_ENCODING_AES_KEY=你的EncodingAESKey
|
|
99
|
-
OPENCLAW_API=http://localhost:18789/v1/chat/completions
|
|
100
|
-
OPENCLAW_TOKEN=你的OpenClaw_Gateway_Token
|
|
101
|
-
PORT=8788
|
|
74
|
+
systemctl --user restart openclaw-gateway
|
|
102
75
|
```
|
|
103
76
|
|
|
104
|
-
|
|
77
|
+
### 步骤 5:测试
|
|
105
78
|
|
|
106
|
-
|
|
79
|
+
在企业微信中找到你创建的智能助手机器人,发送消息,应该会收到 AI 回复。
|
|
107
80
|
|
|
108
|
-
|
|
81
|
+
## 配置说明
|
|
109
82
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
83
|
+
| 参数 | 必填 | 默认值 | 说明 |
|
|
84
|
+
|------|------|--------|------|
|
|
85
|
+
| `token` | 是 | - | 企微回调 Token |
|
|
86
|
+
| `encodingAESKey` | 是 | - | 企微回调 EncodingAESKey |
|
|
87
|
+
| `corpId` | 否 | `""` | 企业 ID,图片功能需要 |
|
|
88
|
+
| `corpSecret` | 否 | `""` | 应用 Secret(智能助手机器人通常不需要) |
|
|
89
|
+
| `port` | 否 | `8788` | 本地监听端口 |
|
|
114
90
|
|
|
115
|
-
|
|
116
|
-
Type=simple
|
|
117
|
-
User=your-user
|
|
118
|
-
WorkingDirectory=/path/to/openclaw-wecom-plugin
|
|
119
|
-
EnvironmentFile=/path/to/.env
|
|
120
|
-
ExecStart=/usr/bin/node index.js
|
|
121
|
-
Restart=always
|
|
122
|
-
RestartSec=10
|
|
91
|
+
## 内置命令
|
|
123
92
|
|
|
124
|
-
|
|
125
|
-
WantedBy=multi-user.target
|
|
126
|
-
```
|
|
93
|
+
在企微聊天窗口中发送以下命令:
|
|
127
94
|
|
|
128
|
-
|
|
95
|
+
| 命令 | 说明 |
|
|
96
|
+
|------|------|
|
|
97
|
+
| `/reset` | 重置当前对话上下文 |
|
|
98
|
+
| `/重置` | 同上(中文别名) |
|
|
129
99
|
|
|
130
|
-
|
|
131
|
-
sudo systemctl daemon-reload
|
|
132
|
-
sudo systemctl enable wecom-bridge
|
|
133
|
-
sudo systemctl start wecom-bridge
|
|
134
|
-
sudo systemctl status wecom-bridge
|
|
135
|
-
```
|
|
100
|
+
上下文被污染或需要开始新话题时,发送重置命令即可清除历史对话。
|
|
136
101
|
|
|
137
|
-
|
|
102
|
+
## 架构说明
|
|
138
103
|
|
|
139
|
-
```bash
|
|
140
|
-
pm2 start index.js --name wecom-bridge
|
|
141
|
-
pm2 save
|
|
142
|
-
pm2 startup
|
|
143
104
|
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
# 终端 1:启动 webhook 服务
|
|
149
|
-
node index.js
|
|
150
|
-
|
|
151
|
-
# 终端 2:启动 ngrok
|
|
152
|
-
ngrok http 8788
|
|
153
|
-
# 复制 ngrok 生成的 https URL,配置到企微回调中
|
|
105
|
+
企微客户端 -> 企微服务器 -> 本插件 (HTTP Server) -> OpenClaw Gateway -> AI 模型
|
|
106
|
+
|
|
|
107
|
+
加密/解密、去重、流式处理、图片解密
|
|
154
108
|
```
|
|
155
109
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
1. 在企业微信中找到你创建的机器人
|
|
159
|
-
2. 发送消息:`你好`
|
|
160
|
-
3. 应该会收到 AI 回复
|
|
161
|
-
|
|
162
|
-
🎉 恭喜!配置完成!
|
|
110
|
+
本插件作为 OpenClaw 的内置服务运行,随 OpenClaw Gateway 自动启停,无需单独部署。
|
|
163
111
|
|
|
164
|
-
##
|
|
112
|
+
## 安全性
|
|
165
113
|
|
|
166
|
-
|
|
114
|
+
- 使用 AES-256-CBC 加密通信
|
|
115
|
+
- 消息签名验证
|
|
116
|
+
- 自动消息去重(防止重放攻击)
|
|
117
|
+
- 敏感信息通过 OpenClaw 配置文件管理,不使用环境变量
|
|
167
118
|
|
|
168
|
-
|
|
169
|
-
{
|
|
170
|
-
"channels": {
|
|
171
|
-
"wecom": {
|
|
172
|
-
"enabled": true, // 是否启用
|
|
173
|
-
"webhookUrl": "https://...", // 回调 URL(必填)
|
|
174
|
-
"token": "...", // Token(必填)
|
|
175
|
-
"encodingAESKey": "...", // EncodingAESKey(必填)
|
|
176
|
-
"port": 8788, // 监听端口(可选,默认 8788)
|
|
177
|
-
"streamDelay": 2000 // 流式回复延迟(毫秒,可选)
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
```
|
|
119
|
+
## 故障排查
|
|
182
120
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
确保 OpenClaw Gateway 配置了正确的认证:
|
|
186
|
-
|
|
187
|
-
```json
|
|
188
|
-
{
|
|
189
|
-
"gateway": {
|
|
190
|
-
"auth": {
|
|
191
|
-
"mode": "token",
|
|
192
|
-
"token": "your-gateway-token"
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
```
|
|
121
|
+
查看日志:
|
|
197
122
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
### 问题:消息发送后没有回复
|
|
201
|
-
|
|
202
|
-
**检查清单:**
|
|
203
|
-
|
|
204
|
-
1. Webhook 服务器是否正常运行?
|
|
205
|
-
```bash
|
|
206
|
-
curl http://localhost:8788/health
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
2. 查看日志:
|
|
210
|
-
```bash
|
|
211
|
-
# systemd
|
|
212
|
-
sudo journalctl -u wecom-bridge -f
|
|
213
|
-
|
|
214
|
-
# pm2
|
|
215
|
-
pm2 logs wecom-bridge
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
3. 验证 OpenClaw Gateway 连接:
|
|
219
|
-
```bash
|
|
220
|
-
curl http://localhost:18789/v1/status \
|
|
221
|
-
-H "Authorization: Bearer your-token"
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### 问题:消息乱码
|
|
225
|
-
|
|
226
|
-
检查 `encodingAESKey` 是否正确配置。
|
|
227
|
-
|
|
228
|
-
### 问题:回复速度慢
|
|
229
|
-
|
|
230
|
-
流式回复会分段发送。可以调整 `streamDelay` 参数:
|
|
231
|
-
|
|
232
|
-
```json
|
|
233
|
-
{
|
|
234
|
-
"wecom": {
|
|
235
|
-
"streamDelay": 1000 // 减小延迟(毫秒)
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### 问题:Webhook 服务启动失败
|
|
241
|
-
|
|
242
|
-
1. 检查端口是否被占用:
|
|
243
|
-
```bash
|
|
244
|
-
lsof -i :8788
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
2. 检查环境变量是否正确:
|
|
248
|
-
```bash
|
|
249
|
-
echo $WECOM_TOKEN
|
|
250
|
-
echo $WECOM_ENCODING_AES_KEY
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## 🏗️ 架构说明
|
|
254
|
-
|
|
255
|
-
```
|
|
256
|
-
企微客户端 → 企微服务器 → Webhook 服务器 → OpenClaw Gateway → AI 模型
|
|
257
|
-
↓
|
|
258
|
-
加密/解密、去重、流式处理
|
|
123
|
+
```bash
|
|
124
|
+
journalctl --user -u openclaw-gateway -f
|
|
259
125
|
```
|
|
260
126
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
1. 用户在企微发送消息
|
|
264
|
-
2. 企微服务器加密后推送到 Webhook
|
|
265
|
-
3. Webhook 服务解密、验证、去重
|
|
266
|
-
4. 转发到 OpenClaw Gateway
|
|
267
|
-
5. AI 生成回复(流式)
|
|
268
|
-
6. Webhook 服务分段加密发送到企微
|
|
269
|
-
7. 用户收到 AI 回复
|
|
270
|
-
|
|
271
|
-
## 🔒 安全性
|
|
272
|
-
|
|
273
|
-
- ✅ 使用 AES-256-CBC 加密
|
|
274
|
-
- ✅ 消息签名验证
|
|
275
|
-
- ✅ 自动消息去重(防止重放攻击)
|
|
276
|
-
- ✅ 环境变量存储敏感信息
|
|
127
|
+
常见问题:
|
|
277
128
|
|
|
278
|
-
|
|
129
|
+
- **端口被占用**:检查 `lsof -i :8788`,修改配置中的 port 或停止冲突进程
|
|
130
|
+
- **消息无回复**:确认 OpenClaw Gateway 正常运行,检查日志中的错误信息
|
|
131
|
+
- **回调验证失败**:确认 token 和 encodingAESKey 与企微后台一致
|
|
279
132
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
1. Fork 本仓库
|
|
283
|
-
2. 创建特性分支:`git checkout -b feature/amazing-feature`
|
|
284
|
-
3. 提交更改:`git commit -m 'Add amazing feature'`
|
|
285
|
-
4. 推送分支:`git push origin feature/amazing-feature`
|
|
286
|
-
5. 提交 Pull Request
|
|
287
|
-
|
|
288
|
-
## 📄 License
|
|
133
|
+
## License
|
|
289
134
|
|
|
290
135
|
MIT License - 详见 [LICENSE](LICENSE) 文件
|
|
291
|
-
|
|
292
|
-
## 🙏 致谢
|
|
293
|
-
|
|
294
|
-
- [OpenClaw](https://openclaw.ai) - 强大的 AI 助手框架
|
|
295
|
-
- [企业微信](https://work.weixin.qq.com/) - 提供企业级通讯平台
|
|
296
|
-
|
|
297
|
-
## 📞 支持
|
|
298
|
-
|
|
299
|
-
- 提交 Issue:[GitHub Issues](https://github.com/CreatorAris/openclaw-wecom-plugin/issues)
|
|
300
|
-
- 查看文档:[OpenClaw 文档](https://docs.openclaw.ai)
|
|
301
|
-
|
|
302
|
-
---
|
|
303
|
-
|
|
304
|
-
**注意:** 本项目为 beta 版本,欢迎反馈和建议!
|
package/index.js
CHANGED
|
@@ -28,6 +28,11 @@ const plugin = {
|
|
|
28
28
|
const botCrypto = new WeComCrypto(token, encodingAESKey, corpId);
|
|
29
29
|
const log = api.logger;
|
|
30
30
|
|
|
31
|
+
// ── Sessions directory (for context reset) ──
|
|
32
|
+
const openclawDir = path.dirname(api.config?.agents?.defaults?.workspace || path.join(process.env.HOME, '.openclaw', 'workspace'));
|
|
33
|
+
const sessionsDir = path.join(openclawDir, 'agents', 'main', 'sessions');
|
|
34
|
+
const RESET_COMMANDS = ['/reset', '/重置'];
|
|
35
|
+
|
|
31
36
|
// ── Stream state management ──
|
|
32
37
|
const streams = new Map();
|
|
33
38
|
|
|
@@ -129,18 +134,41 @@ const plugin = {
|
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
|
|
137
|
+
function decryptImageIfNeeded(buf) {
|
|
138
|
+
// Check if already a known image format
|
|
139
|
+
if ((buf[0] === 0xff && buf[1] === 0xd8) || (buf[0] === 0x89 && buf[1] === 0x50)) return buf;
|
|
140
|
+
// Try AES-256-CBC decryption with WeCom encodingAESKey
|
|
141
|
+
if (buf.length % 16 !== 0) return buf;
|
|
142
|
+
try {
|
|
143
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', botCrypto.aesKey, botCrypto.iv);
|
|
144
|
+
decipher.setAutoPadding(false);
|
|
145
|
+
let decrypted = Buffer.concat([decipher.update(buf), decipher.final()]);
|
|
146
|
+
// Remove PKCS7 padding
|
|
147
|
+
const pad = decrypted[decrypted.length - 1];
|
|
148
|
+
if (pad > 0 && pad <= 32) decrypted = decrypted.subarray(0, decrypted.length - pad);
|
|
149
|
+
// Verify it's a real image now
|
|
150
|
+
if ((decrypted[0] === 0xff && decrypted[1] === 0xd8) || (decrypted[0] === 0x89 && decrypted[1] === 0x50)) {
|
|
151
|
+
log.info('[Image] decrypted successfully');
|
|
152
|
+
return decrypted;
|
|
153
|
+
}
|
|
154
|
+
return buf;
|
|
155
|
+
} catch { return buf; }
|
|
156
|
+
}
|
|
157
|
+
|
|
132
158
|
async function downloadImage(imageUrl) {
|
|
133
159
|
try {
|
|
134
160
|
log.info(`[Image] downloading ${imageUrl.slice(0, 100)}`);
|
|
135
161
|
const response = await fetch(imageUrl);
|
|
136
162
|
if (!response.ok) return null;
|
|
137
|
-
|
|
163
|
+
let buffer = Buffer.from(await response.arrayBuffer());
|
|
138
164
|
if (buffer.byteLength > 10 * 1024 * 1024) return null;
|
|
165
|
+
buffer = decryptImageIfNeeded(buffer);
|
|
139
166
|
await fs.mkdir(IMAGE_CACHE_DIR, { recursive: true });
|
|
140
|
-
|
|
167
|
+
// Detect actual format from magic bytes
|
|
168
|
+
const ext = (buffer[0] === 0x89 && buffer[1] === 0x50) ? '.png' : '.jpg';
|
|
141
169
|
const filename = `${Date.now()}-${crypto.randomBytes(4).toString('hex')}${ext}`;
|
|
142
170
|
const filepath = path.join(IMAGE_CACHE_DIR, filename);
|
|
143
|
-
await fs.writeFile(filepath,
|
|
171
|
+
await fs.writeFile(filepath, buffer);
|
|
144
172
|
log.info(`[Image] saved ${filename} (${(buffer.byteLength / 1024 / 1024).toFixed(2)}MB)`);
|
|
145
173
|
return filepath;
|
|
146
174
|
} catch (err) {
|
|
@@ -150,6 +178,7 @@ const plugin = {
|
|
|
150
178
|
}
|
|
151
179
|
|
|
152
180
|
async function resolveImage(imageObj) {
|
|
181
|
+
log.info(`[Image] raw fields: ${JSON.stringify(imageObj)}`);
|
|
153
182
|
const mediaId = imageObj?.media_id;
|
|
154
183
|
const imageUrl = imageObj?.url || imageObj?.pic_url;
|
|
155
184
|
let localPath = null;
|
|
@@ -405,6 +434,43 @@ const plugin = {
|
|
|
405
434
|
return;
|
|
406
435
|
}
|
|
407
436
|
|
|
437
|
+
// ── Context reset command ──
|
|
438
|
+
if (RESET_COMMANDS.includes(text.trim().toLowerCase())) {
|
|
439
|
+
const sessionId = chattype === 'group' ? `wecom_group_${chatid}` : `wecom_bot_${from?.userid}`;
|
|
440
|
+
const sessionKey = `agent:main:openresponses-user:${sessionId}`;
|
|
441
|
+
let resetMsg = '上下文已重置,开始新的对话。';
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const sessionsFile = path.join(sessionsDir, 'sessions.json');
|
|
445
|
+
const sessionsData = JSON.parse(await fs.readFile(sessionsFile, 'utf8'));
|
|
446
|
+
const session = sessionsData[sessionKey];
|
|
447
|
+
if (session?.sessionId) {
|
|
448
|
+
const sessionFile = path.join(sessionsDir, `${session.sessionId}.jsonl`);
|
|
449
|
+
await fs.rename(sessionFile, `${sessionFile}.reset.${Date.now()}`).catch(() => {});
|
|
450
|
+
delete sessionsData[sessionKey];
|
|
451
|
+
await fs.writeFile(sessionsFile, JSON.stringify(sessionsData, null, 2));
|
|
452
|
+
log.info(`[Reset] session ${sessionKey} cleared`);
|
|
453
|
+
} else {
|
|
454
|
+
log.info(`[Reset] no session found for ${sessionKey}`);
|
|
455
|
+
resetMsg = '当前没有活跃的对话上下文。';
|
|
456
|
+
}
|
|
457
|
+
} catch (err) {
|
|
458
|
+
log.error(`[Reset] error: ${err.message}`);
|
|
459
|
+
resetMsg = '重置失败,请稍后重试。';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const resetStreamId = makeStreamId();
|
|
463
|
+
streams.set(resetStreamId, { content: resetMsg, finished: true, createdAt: Date.now() });
|
|
464
|
+
const replyObj = {
|
|
465
|
+
msgtype: 'stream',
|
|
466
|
+
stream: { id: resetStreamId, finish: true, content: resetMsg },
|
|
467
|
+
};
|
|
468
|
+
const encrypted = encryptReply(replyObj, nonce);
|
|
469
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
470
|
+
res.end(encrypted);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
408
474
|
// Create stream state and start OpenClaw request
|
|
409
475
|
const streamId = makeStreamId();
|
|
410
476
|
const sessionId = chattype === 'group' ? `wecom_group_${chatid}` : `wecom_bot_${from?.userid}`;
|