@sunnoy/wecom 1.2.0 → 1.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 CHANGED
@@ -1,44 +1,109 @@
1
- # OpenClaw WeCom (Enterprise WeChat) AI Bot Plugin
1
+ # OpenClaw 企业微信 (WeCom) AI 机器人插件
2
2
 
3
- [English](https://github.com/sunnoy/openclaw-plugin-wecom/blob/main/README.md) | [简体中文](https://github.com/sunnoy/openclaw-plugin-wecom/blob/main/README_ZH.md)
3
+ `openclaw-plugin-wecom` 是一个专为 [OpenClaw](https://github.com/openclaw/openclaw) 框架开发的企业微信(WeCom)集成插件。它允许你将强大的 AI 能力无缝接入企业微信,支持 AI 机器人模式和自建应用模式,并具备多层消息投递回退机制。
4
4
 
5
- `openclaw-plugin-wecom` is an Enterprise WeChat (WeCom) integration plugin developed for the [OpenClaw](https://github.com/openclaw/openclaw) framework. It enables seamless AI capabilities in Enterprise WeChat with advanced features.
5
+ ## 核心特性
6
6
 
7
- ## Key Features
7
+ ### 消息模式支持
8
+ - **AI 机器人模式 (Bot Mode)**: 基于企业微信最新的 AI 机器人流式分片机制,实现流畅的打字机式回复体验。支持 JSON 格式的回调消息。
9
+ - **自建应用模式 (Agent Mode)**: 支持企业微信自建应用,可处理 XML 格式的回调消息,支持收发消息、上传下载媒体文件。
10
+ - **Webhook Bot 模式**: 支持通过 Webhook 发送消息到群聊,适用于群通知场景。
8
11
 
9
- - **Streaming Output**: Built on WeCom's latest AI bot streaming mechanism for smooth typewriter-style responses.
10
- - **Dynamic Agent Management**: Automatically creates isolated agents per direct message user or group chat, with independent workspaces and conversation contexts.
11
- - **Deep Group Chat Integration**: Supports group message parsing with @mention triggering.
12
- - **Rich Message Types**: Handles text, image, voice, mixed (text+image), file, location, and link messages.
13
- - **Inbound Image Decryption**: Automatically decrypts WeCom-encrypted images using AES-256-CBC for AI vision processing.
14
- - **Outbound Image Support**: Automatic base64 encoding and sending of local images (screenshots, generated images) via `msg_item` API.
15
- - **Message Debounce**: Rapid consecutive messages from the same user are merged into a single AI request.
16
- - **Admin Users**: Configurable admin list that bypasses command allowlist and dynamic agent routing.
17
- - **Command Allowlist**: Built-in commands (e.g., `/new`, `/status`) with configurable allowlist to restrict sensitive operations.
18
- - **Security & Authentication**: Full support for WeCom message encryption/decryption, URL verification, and sender validation.
19
- - **High-Performance Async Processing**: Asynchronous message architecture ensures responsive gateway even during long AI inference.
12
+ ### 智能消息投递
13
+ - **四层投递回退机制**: 确保消息可靠送达
14
+ 1. **流式通道**: 优先通过活跃流式通道发送
15
+ 2. **Response URL 回退**: 流式通道关闭后,使用企业微信 response_url 发送
16
+ 3. **Webhook Bot 回退**: 支持通过 Webhook 发送到指定群聊
17
+ 4. **Agent API 回退**: 通过自建应用 API 主动推送消息
18
+ - **消息防抖合并**: 同一用户在短时间内(2 秒内)连续发送的多条消息自动合并为一次 AI 请求。
19
+ - **内存自动清理**: 定期清理过期的流元数据和响应 URL,防止内存泄漏。
20
20
 
21
- ## Prerequisites
21
+ ### 动态 Agent 与隔离
22
+ - **动态 Agent 管理**: 默认按"每个私聊用户 / 每个群聊"自动创建独立 Agent。每个 Agent 拥有独立的工作区与对话上下文,实现更强的数据隔离。
23
+ - **群聊深度集成**: 支持群聊消息解析,可通过 @提及(At-mention)精准触发机器人响应。
24
+ - **管理员用户**: 可配置管理员列表,绕过指令白名单和动态 Agent 路由限制。
25
+ - **指令白名单**: 内置常用指令支持(如 `/new`、`/status`),并提供指令白名单配置功能。
22
26
 
23
- - [OpenClaw](https://github.com/openclaw/openclaw) installed (version 2026.1.30+)
24
- - Enterprise WeChat admin access to create intelligent robot applications
25
- - Server address accessible from Enterprise WeChat (HTTP/HTTPS)
27
+ ### 多媒体支持
28
+ - **丰富消息类型**: 支持文本、图片、语音、图文混排、文件、位置、链接等消息类型。
29
+ - **入站媒体处理**: 自动解密企业微信 AES-256-CBC 加密的图片,下载并保存语音、视频、文件等媒体供 AI 分析。
30
+ - **出站图片发送**: 支持通过 `msg_item` API 发送 base64 编码图片,单张最大 2MB,每条消息最多 10 张。
31
+ - **文件上传下载**: Agent 模式下支持上传临时媒体文件和下载用户发送的媒体文件。
26
32
 
27
- ## Installation
33
+ ### 安全与扩展
34
+ - **安全与认证**: 完整支持企业微信消息加解密、URL 验证及发送者身份校验。
35
+ - **高性能异步处理**: 采用异步消息处理架构,确保即使在长耗时 AI 推理过程中,企业微信网关也能保持高响应性。
36
+ - **模块化架构**: 清晰的代码组织结构,易于维护和扩展。
37
+
38
+ ## 前置要求
39
+
40
+ - 已安装 [OpenClaw](https://github.com/openclaw/openclaw) (版本 2026.1.30+)
41
+ - 企业微信管理后台权限,可创建智能机器人应用或自建应用
42
+ - 可从企业微信访问的服务器地址(HTTP/HTTPS)
43
+
44
+ ## 安装
28
45
 
29
46
  ```bash
30
47
  openclaw plugins install @sunnoy/wecom
31
48
  ```
32
49
 
33
- This command will automatically:
34
- - Download the plugin from npm
35
- - Install to `~/.openclaw/extensions/`
36
- - Update your OpenClaw configuration
37
- - Register the plugin
50
+ 此命令会自动:
51
+ - npm 下载插件
52
+ - 安装到 `~/.openclaw/extensions/` 目录
53
+ - 更新 OpenClaw 配置
54
+ - 注册插件
55
+
56
+ ### 运行测试
57
+
58
+ ```bash
59
+ npm test
60
+ ```
61
+
62
+ 运行单元测试(使用 Node.js 内置测试运行器)。
63
+
64
+ ### 运行真实 E2E 测试(远程 OpenClaw)
38
65
 
39
- ## Configuration
66
+ 本项目新增了真实联调 e2e 用例(`tests/e2e/remote-wecom.e2e.test.js`),会对真实 `/webhooks/wecom` 做加密请求、验证握手、发送消息并轮询 stream 直到结束。
40
67
 
41
- Add to your OpenClaw configuration file (`~/.openclaw/openclaw.json`):
68
+ 1. 使用你当前环境的 `ssh ali-ai` 一键执行(自动读取远程 `~/.openclaw/openclaw.json`,并建立本地隧道):
69
+
70
+ ```bash
71
+ npm run test:e2e:ali-ai
72
+ ```
73
+
74
+ 2. 或者手动指定环境变量执行:
75
+
76
+ ```bash
77
+ E2E_WECOM_BASE_URL=http://127.0.0.1:28789 \
78
+ E2E_WECOM_TOKEN=xxx \
79
+ E2E_WECOM_ENCODING_AES_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
80
+ E2E_WECOM_WEBHOOK_PATH=/webhooks/wecom \
81
+ npm run test:e2e
82
+ ```
83
+
84
+ 可选变量:
85
+ - `E2E_WECOM_TEST_USER`(默认 `wecom-e2e-user`)
86
+ - `E2E_WECOM_TEST_COMMAND`(默认 `/status`)
87
+ - `E2E_WECOM_POLL_INTERVAL_MS`(默认 `1200`)
88
+ - `E2E_WECOM_STREAM_TIMEOUT_MS`(默认 `90000`)
89
+ - `E2E_WECOM_ENABLE_BROWSER_CASE`(默认 `1`,设置 `0` 可跳过浏览器场景)
90
+ - `E2E_WECOM_BROWSER_TIMEOUT_MS`(默认 `180000`)
91
+ - `E2E_WECOM_BROWSER_REQUIRE_IMAGE`(默认 `0`,设置 `1` 强制断言 `msg_item` 图片出站)
92
+ - `E2E_WECOM_BROWSER_PROMPT`(浏览器场景自定义提示词)
93
+ - `E2E_WECOM_BROWSER_BING_PDF_PROMPT`(Bing + 保存 PDF 场景提示词)
94
+ - `E2E_WECOM_ENABLE_BROWSER_BING_PDF_CASE`(默认 `1`)
95
+ - `E2E_BROWSER_PREPARE_MODE`(`check`/`install`/`off`,默认 `check`)
96
+ - `E2E_BROWSER_REQUIRE_READY`(默认 `0`,设置 `1` 时浏览器环境不满足则中止)
97
+ - `E2E_COLLECT_BROWSER_PDF`(默认 `1`,执行后自动收集远程 sandbox 中的 PDF)
98
+ - `E2E_PDF_OUTPUT_DIR`(默认 `tests/e2e/artifacts`)
99
+
100
+ > 说明:`test:e2e:ali-ai` 会消耗远程实例的真实 LLM token,并覆盖多种真实入站/出站场景(含浏览器相关场景)。
101
+ > 说明:执行 `test:e2e:ali-ai` 会先做 browser sandbox 准备检查(`prepare-browser-sandbox.sh`),测试后会尝试抓取 PDF 产物(`collect-browser-pdf.sh`)供用户下载。
102
+ > 说明:当 browser sandbox 未就绪(缺浏览器二进制或缺 `browser` skill)时,Bing+PDF case 会自动跳过,并在准备检查输出中标记 `STATUS=MISSING`。
103
+
104
+ ## 配置
105
+
106
+ 在 OpenClaw 配置文件(`~/.openclaw/openclaw.json`)中添加:
42
107
 
43
108
  ```json
44
109
  {
@@ -52,53 +117,173 @@ Add to your OpenClaw configuration file (`~/.openclaw/openclaw.json`):
52
117
  "channels": {
53
118
  "wecom": {
54
119
  "enabled": true,
55
- "token": "Your Token",
56
- "encodingAesKey": "Your EncodingAESKey",
57
- "adminUsers": ["admin-userid"],
120
+ "token": "你的 Bot Token",
121
+ "encodingAesKey": "你的 Bot EncodingAESKey",
122
+ "adminUsers": ["管理员userid"],
58
123
  "commands": {
59
124
  "enabled": true,
60
125
  "allowlist": ["/new", "/status", "/help", "/compact"]
126
+ },
127
+ "agent": {
128
+ "corpId": "企业 CorpID",
129
+ "corpSecret": "应用 Secret",
130
+ "agentId": 1000002,
131
+ "token": "回调 Token (Agent 模式)",
132
+ "encodingAesKey": "回调 EncodingAESKey (Agent 模式)"
133
+ },
134
+ "webhooks": {
135
+ "ops-group": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx",
136
+ "dev-group": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=yyy"
61
137
  }
62
138
  }
63
139
  }
64
140
  }
65
141
  ```
66
142
 
67
- ### Configuration Options
143
+ ### 配置说明
68
144
 
69
- | Option | Type | Required | Description |
70
- |--------|------|----------|-------------|
71
- | `plugins.entries.wecom.enabled` | boolean | Yes | Enable the plugin |
72
- | `channels.wecom.token` | string | Yes | WeCom bot Token |
73
- | `channels.wecom.encodingAesKey` | string | Yes | WeCom message encryption key (43 chars) |
74
- | `channels.wecom.adminUsers` | array | No | Admin user IDs (bypass command allowlist and dynamic routing) |
75
- | `channels.wecom.commands.allowlist` | array | No | Command allowlist |
145
+ #### 基础配置
76
146
 
77
- ## Enterprise WeChat Configuration
147
+ | 配置项 | 类型 | 必填 | 说明 |
148
+ |--------|------|------|------|
149
+ | `plugins.entries.wecom.enabled` | boolean | 是 | 启用插件 |
150
+ | `channels.wecom.token` | string | 是* | 企业微信机器人 Token (*Bot 模式必填) |
151
+ | `channels.wecom.encodingAesKey` | string | 是* | 消息加密密钥(43 位)(*Bot 模式必填) |
152
+ | `channels.wecom.adminUsers` | array | 否 | 管理员用户 ID 列表(绕过指令白名单和动态路由) |
153
+ | `channels.wecom.commands.enabled` | boolean | 否 | 是否启用指令白名单过滤(默认 true) |
154
+ | `channels.wecom.commands.allowlist` | array | 否 | 允许的指令白名单 |
78
155
 
79
- 1. Log in to [Enterprise WeChat Admin Console](https://work.weixin.qq.com/)
80
- 2. Navigate to "Application Management" > "Applications" > "Create Application" > Select "Intelligent Robot"
81
- 3. Configure "Receive Messages":
82
- - **URL**: `https://your-domain.com/webhooks/wecom`
83
- - **Token**: Match `channels.wecom.token`
84
- - **EncodingAESKey**: Match `channels.wecom.encodingAesKey`
85
- 4. Save and enable message receiving
156
+ #### 动态 Agent 配置
157
+
158
+ 配置按人/按群隔离的 Agent 管理:
159
+
160
+ | 配置项 | 类型 | 必填 | 说明 |
161
+ |--------|------|------|------|
162
+ | `channels.wecom.dynamicAgents.enabled` | boolean | | 是否启用动态 Agent(默认 true) |
163
+ | `channels.wecom.dm.createAgentOnFirstMessage` | boolean | 否 | 私聊时为每个用户创建独立 Agent(默认 true) |
164
+ | `channels.wecom.groupChat.enabled` | boolean | 否 | 是否启用群聊处理(默认 true) |
165
+ | `channels.wecom.groupChat.requireMention` | boolean | 否 | 群聊是否必须 @ 提及才响应(默认 true) |
166
+
167
+ #### 工作区模板配置 (可选)
86
168
 
87
- ## Supported Message Types
169
+ 配置工作区模板目录,为动态创建的 Agent 工作区预置初始化文件:
88
170
 
89
- | Type | Direction | Description |
90
- |------|-----------|-------------|
91
- | Text | Inbound/Outbound | Plain text messages |
92
- | Image | Inbound/Outbound | Encrypted images (inbound are auto-decrypted); outbound via `msg_item` base64 |
93
- | Voice | Inbound | Auto-transcribed by WeCom, processed as text (DM only) |
94
- | Mixed | Inbound | Text + image combination messages |
95
- | File | Inbound | File attachments (downloaded and passed to AI for analysis) |
96
- | Location | Inbound | Location shares (converted to text description) |
97
- | Link | Inbound | Shared links (title, description, URL extracted as text) |
171
+ | 配置项 | 类型 | 必填 | 说明 |
172
+ |--------|------|------|------|
173
+ | `channels.wecom.workspaceTemplate` | string | | 模板目录路径,支持 AGENTS.md、BOOTSTRAP.md 等 bootstrap 文件 |
98
174
 
99
- ## Admin Users
175
+ 当动态 Agent 首次创建时,会自动从模板目录复制 bootstrap 文件到对应的工作区。详细说明请参考[动态 Agent 路由](#动态-agent-路由)章节。
100
176
 
101
- Admin users bypass the command allowlist and skip dynamic agent routing (routed to the main agent directly).
177
+ #### Agent 模式配置 (可选)
178
+
179
+ 配置自建应用以实现更强大的消息收发能力:
180
+
181
+ | 配置项 | 类型 | 必填 | 说明 |
182
+ |--------|------|------|------|
183
+ | `channels.wecom.agent.corpId` | string | 是 | 企业 CorpID |
184
+ | `channels.wecom.agent.corpSecret` | string | 是 | 应用 Secret |
185
+ | `channels.wecom.agent.agentId` | number | 是 | 应用 Agent ID |
186
+ | `channels.wecom.agent.token` | string | 是 | 回调 Token (用于验证签名) |
187
+ | `channels.wecom.agent.encodingAesKey` | string | 是 | 回调 EncodingAESKey (43 位) |
188
+
189
+ #### Webhook 配置 (可选)
190
+
191
+ 配置 Webhook Bot 用于群通知:
192
+
193
+ | 配置项 | 类型 | 必填 | 说明 |
194
+ |--------|------|------|------|
195
+ | `channels.wecom.webhooks` | object | 否 | Webhook URL 映射 (key: 名称, value: URL) |
196
+
197
+ ## 企业微信后台配置
198
+
199
+ ### 方式一:创建 AI 机器人 (Bot 模式)
200
+
201
+ AI 机器人模式适用于简单的问答场景,支持流式输出。
202
+
203
+ > 📖 **官方文档**:[企业微信 AI 机器人开发指南](https://developer.work.weixin.qq.com/document/path/101039)
204
+
205
+ **创建步骤:**
206
+
207
+ 1. 登录[企业微信管理后台](https://work.weixin.qq.com/)
208
+ 2. 进入「应用管理」→「应用」→ 下拉找到「智能机器人」→ 点击「创建应用」
209
+ 3. **关键步骤**:在创建页面底部,选择 **「API 模式创建」**,而非「标准模式创建」
210
+ > ⚠️ **必须选择 API 模式**。标准模式下回调消息为 XML 格式,API 模式为 JSON 格式,本插件的 Bot 模式仅支持 JSON。
211
+ 4. 填写机器人名称、头像等基本信息,点击「创建」
212
+ 5. 创建完成后,进入机器人详情页:
213
+ - 复制 `Token`(用于验证消息签名)
214
+ - 复制 `EncodingAESKey`(43位字符,用于消息加解密)
215
+ 6. 点击「接收消息」区域的「设置」:
216
+ - **URL**: `https://your-domain.com/webhooks/wecom`
217
+ - **Token**: 填入上一步复制的 Token
218
+ - **EncodingAESKey**: 填入上一步复制的 EncodingAESKey
219
+ 7. 保存配置并启用消息接收
220
+
221
+ ### 方式二:创建自建应用 (Agent 模式)
222
+
223
+ 自建应用模式提供更完整的消息收发能力,支持 XML 回调、主动推送、媒体文件处理。
224
+
225
+ > 📖 **官方文档**:[企业微信自建应用开发指南](https://developer.work.weixin.qq.com/document/path/90226)、[接收消息服务器配置](https://developer.work.weixin.qq.com/document/path/90238)
226
+
227
+ **创建步骤:**
228
+
229
+ 1. 登录[企业微信管理后台](https://work.weixin.qq.com/)
230
+ 2. 进入「应用管理」→「应用」→ 点击「创建应用」
231
+ 3. 填写应用信息:
232
+ - 应用名称:如 "AI 助手"
233
+ - 应用头像:上传应用图标
234
+ - 可见成员:选择可使用该应用的成员
235
+ 4. 点击「创建应用」,记录以下信息:
236
+ - `AgentId`:应用 ID(数字)
237
+ - `Secret`:应用凭证(点击「查看」获取)
238
+ 5. 在「接收消息」区域点击「设置 API 接收」:
239
+ - **URL**: `https://your-domain.com/webhooks/app`
240
+ - **Token**: 点击「随机生成」获取
241
+ - **EncodingAESKey**: 点击「随机生成」获取(43位字符)
242
+ - 点击「保存」时,企业微信会发送验证请求到上述 URL 进行域名校验
243
+ > ⚠️ **注意**:保存前请确保服务已部署并可访问,否则校验会失败。如果遇到「回调 URL 校验失败」,请检查:
244
+ > - 服务器是否可以从公网访问
245
+ > - URL 路径是否正确(`/webhooks/app`)
246
+ > - Token 和 EncodingAESKey 是否已正确配置到插件
247
+ > - 防火墙是否放行了企业微信服务器 IP 段
248
+ 6. 获取企业 CorpID:
249
+ - 进入「我的企业」页面
250
+ - 复制页面底部的「企业ID」
251
+ 7. 配置应用可见范围(确保需要使用 AI 助手的成员在可见范围内)
252
+
253
+ ### 方式三:配置群机器人 (Webhook 模式)
254
+
255
+ Webhook Bot 用于向群聊发送通知消息。
256
+
257
+ > 📖 **官方文档**:[企业微信群机器人开发指南](https://developer.work.weixin.qq.com/document/path/99110)
258
+
259
+ **创建步骤:**
260
+
261
+ 1. 在手机或电脑端打开目标群聊
262
+ 2. 点击群聊右上角「···」→「群机器人」→「添加机器人」
263
+ 3. 选择「新建机器人」,填写机器人名称
264
+ 4. 复制 Webhook 地址(格式:`https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`)
265
+ 5. 将 Webhook 地址配置到 `openclaw.json` 的 `webhooks` 中
266
+
267
+ **注意事项:**
268
+ - Webhook Bot 仅支持发送消息,不支持接收消息
269
+ - 每个群聊可添加多个机器人
270
+ - Webhook 地址请妥善保管,避免泄露
271
+
272
+ ## 支持的消息类型
273
+
274
+ | 类型 | 方向 | 说明 |
275
+ |------|------|------|
276
+ | 文本 (text) | 收/发 | 纯文本消息 |
277
+ | 图片 (image) | 收/发 | 入站图片自动解密;出站通过 `msg_item` base64 发送 |
278
+ | 语音 (voice) | 收 | 企业微信自动转文字后处理(仅限私聊) |
279
+ | 图文混排 (mixed) | 收 | 文本 + 图片混合消息 |
280
+ | 文件 (file) | 收 | 文件附件(下载后传给 AI 分析) |
281
+ | 位置 (location) | 收 | 位置分享(转换为文本描述) |
282
+ | 链接 (link) | 收 | 分享链接(提取标题、描述、URL 为文本) |
283
+
284
+ ## 管理员用户
285
+
286
+ 管理员用户可以绕过指令白名单限制,并跳过动态 Agent 路由(直接路由到主 Agent)。
102
287
 
103
288
  ```json
104
289
  {
@@ -110,24 +295,26 @@ Admin users bypass the command allowlist and skip dynamic agent routing (routed
110
295
  }
111
296
  ```
112
297
 
113
- Admin user IDs are case-insensitive and matched against the WeCom `userid` field.
298
+ 管理员用户 ID 不区分大小写,匹配企业微信的 `userid` 字段。
114
299
 
115
- ## Dynamic Agent Routing
300
+ ## 动态 Agent 路由
116
301
 
117
- The plugin implements per-user/per-group agent isolation:
302
+ 本插件实现"按人/按群隔离"的 Agent 管理:
118
303
 
119
- ### How It Works
304
+ ### 工作原理
120
305
 
121
- 1. When a WeCom message arrives, the plugin generates a deterministic `agentId`:
122
- - **Direct Messages**: `wecom-dm-<userId>`
123
- - **Group Chats**: `wecom-group-<chatId>`
124
- 2. OpenClaw automatically creates/reuses the corresponding agent workspace
125
- 3. Each user/group has independent conversation history and context
126
- 4. **Admin users** skip dynamic routing and use the main agent directly
306
+ 1. 企业微信消息到达后,插件生成确定性的 `agentId`:
307
+ - **单账号私聊**: `wecom-dm-<userId>`
308
+ - **单账号群聊**: `wecom-group-<chatId>`
309
+ - **多账号私聊**: `wecom-<accountId>-dm-<userId>`
310
+ - **多账号群聊**: `wecom-<accountId>-group-<chatId>`
311
+ 2. OpenClaw 自动创建/复用对应的 Agent 工作区
312
+ 3. 每个用户/群聊拥有独立的对话历史和上下文
313
+ 4. **管理员用户**跳过动态路由,直接使用主 Agent
127
314
 
128
- ### Advanced Configuration
315
+ ### 高级配置
129
316
 
130
- Configure under `channels.wecom`:
317
+ 配置在 `channels.wecom` 下:
131
318
 
132
319
  ```json
133
320
  {
@@ -148,16 +335,16 @@ Configure under `channels.wecom`:
148
335
  }
149
336
  ```
150
337
 
151
- | Option | Type | Default | Description |
152
- |--------|------|---------|-------------|
153
- | `dynamicAgents.enabled` | boolean | `true` | Enable dynamic agents |
154
- | `dm.createAgentOnFirstMessage` | boolean | `true` | Use dynamic agents for DMs |
155
- | `groupChat.enabled` | boolean | `true` | Enable group chat processing |
156
- | `groupChat.requireMention` | boolean | `true` | Require @mention in groups |
338
+ | 配置项 | 类型 | 默认值 | 说明 |
339
+ |--------|------|--------|------|
340
+ | `dynamicAgents.enabled` | boolean | `true` | 是否启用动态 Agent |
341
+ | `dm.createAgentOnFirstMessage` | boolean | `true` | 私聊使用动态 Agent |
342
+ | `groupChat.enabled` | boolean | `true` | 启用群聊处理 |
343
+ | `groupChat.requireMention` | boolean | `true` | 群聊必须 @ 提及才响应 |
157
344
 
158
- ### Disable Dynamic Agents
345
+ ### 禁用动态 Agent
159
346
 
160
- To route all messages to the default agent:
347
+ 如果需要所有消息进入默认 Agent:
161
348
 
162
349
  ```json
163
350
  {
@@ -169,9 +356,113 @@ To route all messages to the default agent:
169
356
  }
170
357
  ```
171
358
 
172
- ## Command Allowlist
359
+ ### 多账号配置(Multi-Bot)
360
+
361
+ 支持在一个 OpenClaw 实例中接入多个企业微信机器人,每个机器人独立配置 Token、Agent 凭证、Webhook 等,互不干扰。
362
+
363
+ > 💡 **典型场景**:一个企业微信里创建多个 AI 机器人(如「客服助手」「技术支持」),各自对应不同的 Agent 和会话空间。
364
+
365
+ **配置方式:** 将 `channels.wecom` 下的值改为字典结构,每个 key 是账号 ID(如 `bot1`、`bot2`),value 包含该账号的完整配置:
366
+
367
+ ```json
368
+ {
369
+ "channels": {
370
+ "wecom": {
371
+ "bot1": {
372
+ "token": "Bot1 的 Token",
373
+ "encodingAesKey": "Bot1 的 EncodingAESKey",
374
+ "adminUsers": ["admin1"],
375
+ "agent": {
376
+ "corpId": "企业 CorpID",
377
+ "corpSecret": "Bot1 应用 Secret",
378
+ "agentId": 1000001,
379
+ "token": "Bot1 回调 Token",
380
+ "encodingAesKey": "Bot1 回调 EncodingAESKey"
381
+ },
382
+ "webhooks": {
383
+ "ops-group": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
384
+ }
385
+ },
386
+ "bot2": {
387
+ "token": "Bot2 的 Token",
388
+ "encodingAesKey": "Bot2 的 EncodingAESKey",
389
+ "agent": {
390
+ "corpId": "企业 CorpID",
391
+ "corpSecret": "Bot2 应用 Secret",
392
+ "agentId": 1000002
393
+ }
394
+ }
395
+ }
396
+ }
397
+ }
398
+ ```
399
+
400
+ **说明:**
401
+
402
+ | 项目 | 说明 |
403
+ |------|------|
404
+ | 账号 ID | 字典的 key,如 `bot1`、`bot2`,仅支持小写字母、数字、`-`、`_` |
405
+ | 完全兼容 | 旧的单账号配置(`token` 直接写在 `wecom` 下)自动识别为 `default` 账号,无需修改 |
406
+ | Webhook 路径 | 自动按账号分配:`/webhooks/wecom/bot1`、`/webhooks/wecom/bot2` |
407
+ | Agent 回调路径 | 自动按账号分配:`/webhooks/app/bot1`、`/webhooks/app/bot2` |
408
+ | 动态 Agent ID | 按账号隔离:`wecom-bot1-dm-{userId}`、`wecom-bot2-group-{chatId}` |
409
+ | 冲突检测 | 启动时自动检测重复的 Token 或 Agent ID,避免消息路由错乱 |
173
410
 
174
- Prevent regular users from executing sensitive Gateway management commands through WeCom messages.
411
+ > ⚠️ **注意**:多账号模式下,每个账号的 Webhook URL 需要在企业微信后台分别配置对应的路径(如 `/webhooks/wecom/bot1`)。
412
+
413
+ ### 工作区模板
414
+
415
+ 可以为动态创建的 Agent 工作区预置初始化文件。当新 Agent 首次创建时,会自动从模板目录复制 bootstrap 文件。
416
+
417
+ ```json
418
+ {
419
+ "channels": {
420
+ "wecom": {
421
+ "workspaceTemplate": "/path/to/template-dir"
422
+ }
423
+ }
424
+ }
425
+ ```
426
+
427
+ **支持的模板文件:**
428
+ - `AGENTS.md` - Agent 列表配置
429
+ - `BOOTSTRAP.md` - 初始化引导文档
430
+ - `CLAUDE.md` - Claude Code 指令集
431
+ - 其他自定义文件
432
+
433
+ 模板目录中的文件会复制到动态 Agent 的工作区(`~/.openclaw/workspace-<agentId>/`),仅当目标文件不存在时才会复制。
434
+
435
+ ## 支持的目标格式
436
+
437
+ 插件支持多种目标格式,用于消息路由和 Webhook 发送:
438
+
439
+ | 格式 | 示例 | 说明 |
440
+ |------|------|------|
441
+ | `webhook:<name>` | `webhook:ops-group` | 发送到配置的 Webhook 群 |
442
+ | `wecom:<userId>` | `wecom:zhangsan` | 企业微信用户 ID |
443
+ | `party:<id>` | `party:2` | 部门 ID(数字) |
444
+ | `tag:<name>` | `tag:Developers` | 标签名称 |
445
+ | `group:<chatId>` | `group:wr123456` | 群聊 ID |
446
+ | `chatId` | `wr123456` | 以 `wr` 或 `wc` 开头的群聊 ID |
447
+
448
+ ### 使用示例
449
+
450
+ 通过 OpenClaw 向企业微信发送消息时,可以使用上述格式指定目标:
451
+
452
+ ```bash
453
+ # 发送给指定用户
454
+ openclaw send "wecom:zhangsan" "Hello!"
455
+
456
+ # 发送到 Webhook 群
457
+ openclaw send "webhook:dev-group" "部署成功!"
458
+
459
+ # 发送给部门
460
+ openclaw send "party:2" "全体员工通知"
461
+ ```
462
+
463
+ ## 指令白名单
464
+
465
+ 为防止普通用户通过企业微信消息执行敏感的 Gateway 管理指令,本插件支持**指令白名单**机制。
175
466
 
176
467
  ```json
177
468
  {
@@ -186,78 +477,166 @@ Prevent regular users from executing sensitive Gateway management commands throu
186
477
  }
187
478
  ```
188
479
 
189
- ### Recommended Allowlist Commands
480
+ ### 推荐白名单指令
481
+
482
+ | 指令 | 说明 | 安全级别 |
483
+ |------|------|----------|
484
+ | `/new` | 重置当前对话,开启全新会话 | 用户级 |
485
+ | `/compact` | 压缩当前会话上下文 | 用户级 |
486
+ | `/help` | 查看帮助信息 | 用户级 |
487
+ | `/status` | 查看当前 Agent 状态 | 用户级 |
488
+
489
+ > **安全提示**:不要将 `/gateway`、`/plugins` 等管理指令添加到白名单,避免普通用户获得 Gateway 实例的管理权限。配置在 `adminUsers` 中的管理员不受此限制。
490
+
491
+ ## 消息防抖合并
492
+
493
+ 当用户在短时间内(2 秒内)连续发送多条消息时,插件会自动将它们合并为一次 AI 请求。这样可以避免同一用户触发多个并发的 LLM 调用,提供更连贯的回复。
494
+
495
+ - 第一条消息的流式通道接收 AI 回复
496
+ - 后续被合并的消息会显示已合并的提示
497
+ - 指令消息(以 `/` 开头)不参与防抖,会立即处理
498
+
499
+ ## 常见问题 (FAQ)
500
+
501
+ ### Q: 回调报错 `Unexpected token '<', "..." is not valid JSON` 怎么办?
502
+
503
+ **A:** 这是企业微信机器人**创建模式**选错导致的。企业微信提供两种机器人创建方式:
504
+
505
+ - **标准模式**:回调消息为 **XML 格式**,本插件不支持
506
+ - **API 模式**:回调消息为 **JSON 格式**,本插件所需
507
+
508
+ **解决方法**:删除当前机器人,重新创建时在页面底部选择 **"API 模式创建"**。
509
+
510
+ ### Q: 入站图片是怎么处理的?
511
+
512
+ **A:** 企业微信使用 AES-256-CBC 加密用户发送的图片。插件会自动:
513
+ 1. 从企业微信的 URL 下载加密图片
514
+ 2. 使用配置的 `encodingAesKey` 解密
515
+ 3. 保存到本地并传给 AI 进行视觉分析
516
+
517
+ 图文混排消息也完全支持——文本和图片会一起提取并发送给 AI。
518
+
519
+ ### Q: 出站图片发送是如何工作的?
190
520
 
191
- | Command | Description | Safety Level |
192
- |---------|-------------|--------------|
193
- | `/new` | Reset conversation, start new session | User-level |
194
- | `/compact` | Compress current session context | User-level |
195
- | `/help` | Show help information | User-level |
196
- | `/status` | Show Agent status | User-level |
521
+ **A:** 插件会自动处理 OpenClaw 生成的图片(如浏览器截图):
197
522
 
198
- > **Security Note**: Do not add `/gateway`, `/plugins`, or other management commands to the allowlist to prevent regular users from gaining Gateway instance admin privileges. Admin users configured in `adminUsers` bypass this restriction.
523
+ - **本地图片**(来自 `~/.openclaw/media/`)会自动进行 base64 编码,通过企业微信 `msg_item` API 发送
524
+ - **图片限制**:单张图片最大 2MB,支持 JPG 和 PNG 格式,每条消息最多 10 张图片
525
+ - **无需配置**:开箱即用,配合浏览器截图等工具自动生效
526
+ - 图片会在 AI 完成回复后显示(流式输出不支持增量发送图片)
199
527
 
200
- ## Message Debounce
528
+ 如果图片处理失败(超出大小限制、格式不支持等),文本回复仍会正常发送,错误信息会记录在日志中。
201
529
 
202
- When a user sends multiple messages in rapid succession (within 2 seconds), the plugin automatically merges them into a single AI request. This prevents multiple concurrent LLM calls for the same user and provides a more coherent response.
530
+ ### Q: 机器人支持语音消息吗?
203
531
 
204
- - The first message's stream receives the AI response
205
- - Subsequent merged messages show a notice that they have been combined
206
- - Commands (messages starting with `/`) bypass debounce and are processed immediately
532
+ **A:** 支持!私聊中的语音消息会被企业微信自动转录为文字并作为文本处理,无需额外配置。
207
533
 
208
- ## FAQ
534
+ ### Q: 机器人支持文件消息吗?
209
535
 
210
- ### Q: How does inbound image handling work?
536
+ **A:** 支持。用户发送的文件会被下载并作为附件传给 AI。AI 可以分析文件内容(如读取 PDF 或解析代码文件)。MIME 类型根据文件扩展名自动检测。
211
537
 
212
- **A:** WeCom encrypts images sent by users with AES-256-CBC. The plugin automatically:
213
- 1. Downloads the encrypted image from WeCom's URL
214
- 2. Decrypts it using the configured `encodingAesKey`
215
- 3. Saves it locally and passes it to the AI for vision analysis
538
+ ### Q: 如何配置自建应用 (Agent) 模式?
216
539
 
217
- Mixed messages (text + images) are fully supported — text and images are extracted and sent together.
540
+ **A:** Agent 模式提供更强大的消息收发能力,包括主动推送消息和接收 XML 格式回调。
218
541
 
219
- ### Q: How does outbound image sending work?
542
+ **配置步骤:**
220
543
 
221
- **A:** The plugin automatically handles images generated by OpenClaw (such as browser screenshots):
544
+ 1. 在企业微信管理后台创建"自建应用"
545
+ 2. 获取应用凭证:
546
+ - `corpId`: 企业 ID(在"我的企业"页面)
547
+ - `agentId`: 应用 ID
548
+ - `corpSecret`: 应用 Secret
549
+ 3. 设置接收消息:
550
+ - 获取 `token` 和 `encodingAesKey`(随机生成)
551
+ - 回调 URL: `https://your-domain.com/webhooks/app`
222
552
 
223
- - **Local images** (from `~/.openclaw/media/`) are automatically encoded to base64 and sent via WeCom's `msg_item` API
224
- - **Image constraints**: Max 2MB per image, supports JPG and PNG formats, up to 10 images per message
225
- - **No configuration needed**: Works out of the box with tools like browser screenshot
226
- - Images appear when the AI completes its response (streaming doesn't support incremental image sending)
553
+ 4. `openclaw.json` 中添加 Agent 配置:
554
+ ```json
555
+ {
556
+ "channels": {
557
+ "wecom": {
558
+ "agent": {
559
+ "corpId": "wwxxxxxxxxxxxxxxxx",
560
+ "corpSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
561
+ "agentId": 1000002,
562
+ "token": "your_callback_token",
563
+ "encodingAesKey": "your_43_char_encoding_aes_key"
564
+ }
565
+ }
566
+ }
567
+ }
568
+ ```
227
569
 
228
- If an image fails to process (size limit, invalid format), the text response will still be delivered and an error will be logged.
570
+ **Agent 模式与 Bot 模式的区别:**
229
571
 
230
- ### Q: Does the bot support voice messages?
572
+ | 特性 | Bot 模式 | Agent 模式 |
573
+ |------|----------|------------|
574
+ | 创建方式 | 智能机器人 | 自建应用 |
575
+ | 回调格式 | JSON | XML |
576
+ | 主动推送 | 不支持 | 支持 |
577
+ | 媒体下载 | 不支持 | 支持 |
578
+ | 文件消息 | 不支持 | 支持 |
231
579
 
232
- **A:** Yes! Voice messages in direct chats are automatically transcribed by WeCom and processed as text. No additional configuration needed.
580
+ ### Q: 如何使用 Webhook Bot 发送群通知?
233
581
 
234
- ### Q: Does the bot support file messages?
582
+ **A:** Webhook Bot 适用于向群聊发送通知消息。
235
583
 
236
- **A:** Yes. Files sent by users are downloaded and passed to the AI as attachments. The AI can analyze file contents (e.g., reading a PDF or parsing a code file). MIME types are auto-detected from the file extension.
584
+ **配置步骤:**
237
585
 
238
- ### Q: How to configure auth token for public-facing OpenClaw with WeCom callbacks?
586
+ 1. 在企业微信群聊中添加"群机器人"
587
+ 2. 复制 Webhook URL(包含 key 参数)
588
+ 3. 在配置中添加 webhook 映射:
589
+ ```json
590
+ {
591
+ "channels": {
592
+ "wecom": {
593
+ "webhooks": {
594
+ "ops-group": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx",
595
+ "dev-group": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=yyy"
596
+ }
597
+ }
598
+ }
599
+ }
600
+ ```
601
+
602
+ 4. 使用 `webhook:` 前缀作为目标:
603
+ - 文本:`webhook:ops-group`
604
+ - 支持 Markdown、图片、文件等多种消息类型
605
+
606
+ ### Q: 四层消息投递回退是如何工作的?
239
607
 
240
- **A:** WeCom bot **does not need** OpenClaw's Gateway Auth Token.
608
+ **A:** 插件采用四层回退机制确保消息可靠送达:
241
609
 
242
- - **Gateway Auth Token** (`gateway.auth.token`) is used for:
243
- - WebUI access authentication
244
- - WebSocket connection authentication
245
- - CLI remote connection authentication
610
+ | 层级 | 条件 | 说明 |
611
+ |------|------|------|
612
+ | **Layer 1** | 活跃流式通道 | 正常对话时,消息通过流式通道实时推送 |
613
+ | **Layer 2** | response_url | 流式通道关闭后 1 小时内,可通过 response_url 发送 |
614
+ | **Layer 3a** | Webhook Bot | 目标以 `webhook:` 开头时,使用 Webhook 发送 |
615
+ | **Layer 3b** | Agent API | 配置了 Agent 时,通过自建应用 API 主动推送 |
246
616
 
247
- - **WeCom Webhook** (`/webhooks/wecom`) authentication:
248
- - Uses WeCom's own signature verification (Token + EncodingAESKey)
249
- - Does not require Gateway Auth Token
250
- - OpenClaw plugin system automatically handles webhook routing
617
+ 当上一层级不可用时,自动回退到下一层级。这种设计确保了即使在流式通道关闭的情况下,AI 生成的异步消息(如定时任务、子 Agent 输出)仍能送达。
251
618
 
252
- **Deployment suggestions:**
253
- 1. If using a reverse proxy (e.g., Nginx), configure authentication exemption for `/webhooks/wecom` path
254
- 2. Or expose the webhook endpoint on a separate port without Gateway Auth
619
+ ### Q: OpenClaw 开放公网需要 auth token,企业微信回调如何配置?
255
620
 
256
- ### Q: How to fix EncodingAESKey length validation failure?
621
+ - **Gateway Auth Token** (`gateway.auth.token`) 主要用于:
622
+ - WebUI 访问认证
623
+ - WebSocket 连接认证
624
+ - CLI 远程连接认证
257
625
 
258
- **A:** Common causes and solutions:
626
+ - **企业微信 Webhook** (`/webhooks/wecom`) 的认证机制:
627
+ - 使用企业微信自己的签名验证(Token + EncodingAESKey)
628
+ - 不需要 Gateway Auth Token
629
+ - OpenClaw 插件系统会自动处理 webhook 路由
259
630
 
260
- 1. **Check configuration key name**: Ensure correct key name `encodingAesKey` (case-sensitive)
631
+ **部署建议:**
632
+ 1. 如果使用反向代理(如 Nginx),可以为 `/webhooks/wecom` 路径配置豁免认证
633
+ 2. 或者将 webhook 端点暴露在独立端口,不经过 Gateway Auth
634
+
635
+ ### Q: EncodingAESKey 长度验证失败怎么办?
636
+
637
+ **A:** 常见原因和解决方法:
638
+
639
+ 1. **检查配置键名**:确保使用正确的键名 `encodingAesKey`(注意大小写)
261
640
  ```json
262
641
  {
263
642
  "channels": {
@@ -268,36 +647,341 @@ If an image fails to process (size limit, invalid format), the text response wil
268
647
  }
269
648
  ```
270
649
 
271
- 2. **Check key length**: EncodingAESKey must be exactly 43 characters
650
+ 2. **检查密钥长度**:EncodingAESKey 必须是 43 位字符
272
651
  ```bash
273
- # Check length
274
- echo -n "your-key" | wc -c
652
+ # 检查长度
653
+ echo -n "你的密钥" | wc -c
275
654
  ```
276
655
 
277
- 3. **Check for extra spaces/newlines**: Ensure no leading/trailing whitespace in the key string
656
+ 3. **检查是否有多余空格/换行**:确保密钥字符串前后没有空格或换行符
657
+
658
+ ### Q: 日志报错 reply delivery failed ... 60020 not allow to access from your ip 怎么办?
659
+
660
+ **A:** 这是企业微信对「自建应用 API 主动发送消息」的安全限制。错误码 60020 表示:当前服务器出口公网 IP 未加入企业微信应用的可信 IP 白名单。
278
661
 
279
- ## Project Structure
662
+ **典型日志示例:**
663
+
664
+ ```bash
665
+ [wecom] [agent-inbound] reply delivery failed {"error":"agent send text failed: 60020 not allow to access from your ip, ... from ip: xx.xx.xx.xx"}
666
+ ```
667
+
668
+
669
+
670
+ **原因说明**
671
+
672
+ 当插件使用 Agent API 回退(或 Agent 模式主动推送)发送消息时,会调用企业微信开放接口(如 qyapi.weixin.qq.com)。
673
+ 如果企业微信后台为该应用启用了 企业可信IP / 接口可信IP 校验,而当前服务器出口公网 IP 不在白名单内,企业微信会拒绝请求并返回 60020。
674
+
675
+ **解决方法**
676
+
677
+ 1. 登录企业微信管理后台
678
+
679
+ 2. 进入对应的 自建应用 详情页
680
+
681
+ 3. 找到 企业可信IP 配置项
682
+
683
+ 4. 将服务器公网出口 IP 加入白名单
684
+ - 建议以错误日志中的 from ip 为准(你的服务器公网ip)
685
+
686
+ 5. 保存配置后重试发送消息
687
+
688
+ ## 项目结构
280
689
 
281
690
  ```
282
691
  openclaw-plugin-wecom/
283
- ├── index.js # Plugin entry point
284
- ├── webhook.js # WeCom HTTP communication handler
285
- ├── dynamic-agent.js # Dynamic agent routing logic
286
- ├── stream-manager.js # Streaming response manager
287
- ├── image-processor.js # Image encoding/validation for msg_item
288
- ├── crypto.js # WeCom encryption algorithms (message + media)
289
- ├── logger.js # Logging module
290
- ├── utils.js # Utility functions (TTL cache, deduplication)
291
- ├── package.json # npm package config
292
- └── openclaw.plugin.json # OpenClaw plugin manifest
692
+ ├── index.js # 插件入口
693
+ ├── package.json # npm 包配置
694
+ ├── openclaw.plugin.json # OpenClaw 插件清单
695
+ ├── crypto.js # 企业微信加密算法(消息 + 媒体)
696
+ ├── logger.js # 日志模块
697
+ ├── utils.js # 工具函数(TTL 缓存、消息去重)
698
+ ├── stream-manager.js # 流式回复管理
699
+ ├── image-processor.js # 图片编码/校验(msg_item)
700
+ ├── webhook.js # 企业微信 Bot 模式 HTTP 通信处理
701
+ ├── dynamic-agent.js # 动态 Agent 分配逻辑
702
+ ├── wecom/ # 核心模块目录
703
+ │ ├── channel-plugin.js # 主频道插件逻辑
704
+ │ ├── http-handler.js # HTTP 请求处理器
705
+ │ ├── agent-api.js # Agent API 客户端(AccessToken 缓存、消息发送)
706
+ │ ├── agent-inbound.js # Agent 模式入站处理器(XML 回调)
707
+ │ ├── webhook-bot.js # Webhook Bot 客户端
708
+ │ ├── inbound-processor.js # 入站消息处理器
709
+ │ ├── xml-parser.js # XML 解析器(Agent 模式)
710
+ │ ├── target.js # 目标解析器(支持多种目标格式)
711
+ │ ├── commands.js # 命令处理
712
+ │ ├── constants.js # 常量定义
713
+ │ ├── state.js # 状态管理
714
+ │ ├── stream-utils.js # 流式处理工具
715
+ │ ├── response-url.js # response_url 处理
716
+ │ ├── allow-from.js # 权限控制
717
+ │ ├── media.js # 媒体文件处理
718
+ │ ├── webhook-targets.js # Webhook 目标管理
719
+ │ └── workspace-template.js # 工作区模板
720
+ ├── tests/ # 测试目录
721
+ │ ├── e2e/
722
+ │ │ ├── remote-wecom.e2e.test.js # 真实远程 E2E(加密请求 + stream 轮询)
723
+ │ │ └── run-ali-ai.sh # ssh ali-ai 一键联调脚本
724
+ │ │ ├── prepare-browser-sandbox.sh # browser sandbox 环境检查/准备
725
+ │ │ └── collect-browser-pdf.sh # 收集并下载 PDF 测试产物
726
+ │ ├── outbound.test.js # 出站投递回退逻辑测试
727
+ │ ├── target.test.js # 目标解析器测试
728
+ │ └── xml-parser.test.js # XML 解析器测试
729
+ ├── README.md # 本文档
730
+ ├── CONTRIBUTING.md # 贡献指南
731
+ └── LICENSE # 开源协议
732
+ ```
733
+
734
+ ## 贡献规范
735
+
736
+ 我们非常欢迎开发者参与贡献!如果你发现了 Bug 或有更好的功能建议,请提交 Issue 或 Pull Request。
737
+
738
+ 详见 [CONTRIBUTING.md](./CONTRIBUTING.md)
739
+
740
+ ## 开源协议
741
+
742
+ 本项目采用 [ISC License](./LICENSE) 协议。
743
+
744
+ ## 配置示例参考
745
+
746
+ 以下是一个生产环境的脱敏配置示例,供参考:
747
+
748
+ ```json
749
+ {
750
+ "meta": {
751
+ "lastTouchedVersion": "2026.2.25",
752
+ "lastTouchedAt": "2026-02-28T03:14:11.564Z"
753
+ },
754
+ "wizard": {
755
+ "lastRunAt": "2026-02-26T09:29:04.028Z",
756
+ "lastRunVersion": "2026.2.25",
757
+ "lastRunCommand": "onboard",
758
+ "lastRunMode": "local"
759
+ },
760
+ "logging": {
761
+ "level": "info",
762
+ "consoleLevel": "debug",
763
+ "consoleStyle": "pretty"
764
+ },
765
+ "models": {
766
+ "mode": "merge",
767
+ "providers": {
768
+ "bailian": {
769
+ "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
770
+ "apiKey": "sk-xxxxxxxxxxxxxxxxxxxxxx",
771
+ "api": "openai-completions",
772
+ "models": [
773
+ { "id": "qwen3.5-plus", "name": "qwen3.5-plus", "reasoning": false, "input": ["text", "image"], "contextWindow": 1000000, "maxTokens": 65536 },
774
+ { "id": "MiniMax-M2.5", "name": "MiniMax-M2.5", "reasoning": false, "input": ["text"], "contextWindow": 1000000, "maxTokens": 65536 },
775
+ { "id": "glm-5", "name": "glm-5", "reasoning": false, "input": ["text"], "contextWindow": 202752, "maxTokens": 16384 },
776
+ { "id": "glm-4.7", "name": "glm-4.7", "reasoning": false, "input": ["text"], "contextWindow": 202752, "maxTokens": 16384 },
777
+ { "id": "kimi-k2.5", "name": "kimi-k2.5", "reasoning": false, "input": ["text", "image"], "contextWindow": 262144, "maxTokens": 32768 }
778
+ ]
779
+ }
780
+ }
781
+ },
782
+ "agents": {
783
+ "defaults": {
784
+ "model": { "primary": "bailian/kimi-k2.5" },
785
+ "models": {
786
+ "bailian/qwen3.5-plus": {},
787
+ "bailian/MiniMax-M2.5": {},
788
+ "bailian/glm-5": {},
789
+ "bailian/glm-4.7": {},
790
+ "bailian/kimi-k2.5": {}
791
+ },
792
+ "workspace": "/path/to/workspace",
793
+ "userTimezone": "Asia/Shanghai",
794
+ "timeFormat": "24",
795
+ "compaction": {
796
+ "mode": "safeguard",
797
+ "reserveTokensFloor": 20000,
798
+ "memoryFlush": {
799
+ "enabled": true,
800
+ "softThresholdTokens": 4000
801
+ }
802
+ },
803
+ "thinkingDefault": "medium",
804
+ "verboseDefault": "on",
805
+ "heartbeat": {
806
+ "every": "10m",
807
+ "target": "last",
808
+ "directPolicy": "allow"
809
+ },
810
+ "sandbox": {
811
+ "mode": "all",
812
+ "workspaceAccess": "rw",
813
+ "scope": "agent",
814
+ "docker": {
815
+ "image": "your-registry.com/openclaw-agent:v2026.x.x",
816
+ "readOnlyRoot": false,
817
+ "network": "bridge",
818
+ "extraHosts": [
819
+ "your-domain.internal:xxx.xxx.xxx.xxx"
820
+ ],
821
+ "binds": [
822
+ "/path/to/skills:/workspace/skills:ro"
823
+ ],
824
+ "dangerouslyAllowReservedContainerTargets": true,
825
+ "dangerouslyAllowExternalBindSources": true
826
+ },
827
+ "prune": {
828
+ "idleHours": 87600,
829
+ "maxAgeDays": 3650
830
+ }
831
+ }
832
+ },
833
+ "list": [
834
+ { "id": "main" },
835
+ { "id": "wecom-dm-xxxxxx" }
836
+ ]
837
+ },
838
+ "commands": {
839
+ "native": "auto",
840
+ "nativeSkills": "auto",
841
+ "restart": true,
842
+ "ownerDisplay": "raw"
843
+ },
844
+ "session": {
845
+ "dmScope": "per-channel-peer"
846
+ },
847
+ "hooks": {
848
+ "internal": {
849
+ "enabled": true,
850
+ "entries": {
851
+ "boot-md": { "enabled": true },
852
+ "command-logger": { "enabled": true },
853
+ "session-memory": { "enabled": true },
854
+ "bootstrap-extra-files": { "enabled": true }
855
+ }
856
+ }
857
+ },
858
+ "channels": {
859
+ "wecom": {
860
+ "enabled": true,
861
+ "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
862
+ "encodingAesKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
863
+ "commands": {
864
+ "enabled": true,
865
+ "allowlist": ["/help", "/commands", "/status", "/context", "/whoami", "/new", "/compact", "/stop", "/reset", "/usage", "/think", "/thinking", "/t", "/verbose", "/v", "/reasoning", "/reason", "/model", "/models", "/skill"]
866
+ },
867
+ "dynamicAgents": { "enabled": true },
868
+ "dm": { "createAgentOnFirstMessage": true },
869
+ "groupChat": { "enabled": true, "requireMention": true },
870
+ "adminUsers": ["admin_userid"],
871
+ "workspaceTemplate": "/path/to/workspace-template",
872
+ "agent": {
873
+ "corpId": "wwxxxxxxxxxxxxxxxx",
874
+ "corpSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
875
+ "agentId": 1000002,
876
+ "token": "xxxxxxxxxxxxxxx",
877
+ "encodingAesKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
878
+ }
879
+ }
880
+ },
881
+ "gateway": {
882
+ "port": 18789,
883
+ "mode": "local",
884
+ "bind": "lan",
885
+ "controlUi": {
886
+ "dangerouslyAllowHostHeaderOriginFallback": true,
887
+ "allowInsecureAuth": true
888
+ },
889
+ "auth": {
890
+ "mode": "token",
891
+ "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
892
+ },
893
+ "tailscale": {
894
+ "mode": "off",
895
+ "resetOnExit": false
896
+ }
897
+ },
898
+ "skills": {
899
+ "allowBundled": ["_none_"],
900
+ "load": {
901
+ "extraDirs": ["/path/to/skills"],
902
+ "watch": true,
903
+ "watchDebounceMs": 250
904
+ },
905
+ "install": { "nodeManager": "npm" }
906
+ },
907
+ "plugins": {
908
+ "allow": ["wecom"],
909
+ "entries": { "wecom": { "enabled": true } }
910
+ }
911
+ }
293
912
  ```
294
913
 
295
- ## Contributing
914
+ ## 自定义 Skills 配合沙箱使用实践
915
+
916
+ OpenClaw 支持自定义 Skills 并通过沙箱(Docker)隔离执行,以下是生产环境的实践配置:
296
917
 
297
- We welcome contributions! Please submit Issues or Pull Requests for bugs or feature suggestions.
298
918
 
299
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
919
+ ### 沙箱配置关键点
300
920
 
301
- ## License
921
+ ```json
922
+ {
923
+ "agents": {
924
+ "defaults": {
925
+ "sandbox": {
926
+ "mode": "all",
927
+ "workspaceAccess": "rw",
928
+ "scope": "agent",
929
+ "docker": {
930
+ "image": "your-registry.com/openclaw-agent:v2026.x.x",
931
+ "readOnlyRoot": false,
932
+ "network": "bridge",
933
+ "extraHosts": [
934
+ "your-domain.internal:xxx.xxx.xxx.xxx"
935
+ ],
936
+ "binds": [
937
+ "/path/to/skills:/workspace/skills:ro"
938
+ ],
939
+ "dangerouslyAllowReservedContainerTargets": true,
940
+ "dangerouslyAllowExternalBindSources": true
941
+ },
942
+ "prune": {
943
+ "idleHours": 87600,
944
+ "maxAgeDays": 3650
945
+ }
946
+ }
947
+ }
948
+ },
949
+ "skills": {
950
+ "allowBundled": ["_none_"],
951
+ "load": {
952
+ "extraDirs": ["/path/to/skills"],
953
+ "watch": true,
954
+ "watchDebounceMs": 250
955
+ }
956
+ }
957
+ }
958
+ ```
302
959
 
303
- This project is licensed under the [ISC License](./LICENSE).
960
+ ### 配置说明
961
+
962
+ | 配置项 | 说明 |
963
+ |--------|------|
964
+ | `sandbox.mode` | 沙箱模式:`all` 所有操作都走沙箱 |
965
+ | `sandbox.workspaceAccess` | 工作区访问权限:`rw` 读写 |
966
+ | `sandbox.scope` | 沙箱作用域:`agent` 每个 Agent 独立沙箱 |
967
+ | `sandbox.docker.image` | 沙箱使用的 Docker 镜像 |
968
+ | `sandbox.docker.readOnlyRoot` | 是否只读根文件系统 |
969
+ | `sandbox.docker.network` | 网络模式:`bridge` 桥接网络 |
970
+ | `sandbox.docker.binds` | 挂载目录:将宿主机 skills 目录映射到沙箱内 `/workspace/skills`(只读) |
971
+ | `sandbox.docker.extraHosts` | 添加额外 hosts,解决内网服务域名解析 |
972
+ | `sandbox.docker.dangerouslyAllowReservedContainerTargets` | 允许容器访问保留目标 |
973
+ | `sandbox.docker.dangerouslyAllowExternalBindSources` | 允许外部绑定源 |
974
+ | `sandbox.prune.idleHours` | 空闲容器清理时间(小时) |
975
+ | `sandbox.prune.maxAgeDays` | 容器最大存活天数 |
976
+ | `skills.allowBundled` | 允许的内置 skills(`["_none_"]` 表示禁用所有内置) |
977
+ | `skills.load.extraDirs` | 自定义 skills 加载目录 |
978
+ | `skills.load.watch` | 启用热加载,修改 skill 无需重启 |
979
+ | `skills.load.watchDebounceMs` | 热加载防抖时间(毫秒) |
980
+
981
+ ### 使用流程
982
+
983
+ 1. 在宿主机创建自定义 skill 目录
984
+ 2. 配置 `binds` 将目录映射到沙箱
985
+ 3. 在 `skills.load.extraDirs` 指定加载路径
986
+ 4. Agent 在沙箱中可通过 `/workspace/skills` 访问自定义 skills
987
+ 5. 使用 `/skill` 命令查看和管理 skills