@wecom/wecom-openclaw-plugin 2026.3.31-beta.0 → 2026.4.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -251
- package/dist/cjs/index.js +45 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/src/channel.js +339 -0
- package/dist/cjs/src/channel.js.map +1 -0
- package/dist/cjs/src/const.js +137 -0
- package/dist/cjs/src/const.js.map +1 -0
- package/dist/cjs/src/dm-policy.js +89 -0
- package/dist/cjs/src/dm-policy.js.map +1 -0
- package/dist/cjs/src/group-policy.js +133 -0
- package/dist/cjs/src/group-policy.js.map +1 -0
- package/dist/cjs/src/mcp/interceptors/biz-error.js +70 -0
- package/dist/cjs/src/mcp/interceptors/biz-error.js.map +1 -0
- package/dist/cjs/src/mcp/interceptors/index.js +73 -0
- package/dist/cjs/src/mcp/interceptors/index.js.map +1 -0
- package/dist/cjs/src/mcp/interceptors/msg-media.js +159 -0
- package/dist/cjs/src/mcp/interceptors/msg-media.js.map +1 -0
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js +157 -0
- package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +1 -0
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js +93 -0
- package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +1 -0
- package/dist/cjs/src/mcp/schema.js +104 -0
- package/dist/cjs/src/mcp/schema.js.map +1 -0
- package/dist/cjs/src/mcp/tool.js +208 -0
- package/dist/cjs/src/mcp/tool.js.map +1 -0
- package/dist/cjs/src/mcp/transport.js +417 -0
- package/dist/cjs/src/mcp/transport.js.map +1 -0
- package/dist/cjs/src/media-handler.js +129 -0
- package/dist/cjs/src/media-handler.js.map +1 -0
- package/dist/cjs/src/media-uploader.js +284 -0
- package/dist/cjs/src/media-uploader.js.map +1 -0
- package/dist/cjs/src/message-parser.js +134 -0
- package/dist/cjs/src/message-parser.js.map +1 -0
- package/dist/cjs/src/message-sender.js +89 -0
- package/dist/cjs/src/message-sender.js.map +1 -0
- package/dist/cjs/src/monitor.js +991 -0
- package/dist/cjs/src/monitor.js.map +1 -0
- package/dist/cjs/src/onboarding.js +182 -0
- package/dist/cjs/src/onboarding.js.map +1 -0
- package/dist/cjs/src/openclaw-compat.js +400 -0
- package/dist/cjs/src/openclaw-compat.js.map +1 -0
- package/dist/cjs/src/reqid-store.js +88 -0
- package/dist/cjs/src/reqid-store.js.map +1 -0
- package/dist/cjs/src/runtime.js +16 -0
- package/dist/cjs/src/runtime.js.map +1 -0
- package/dist/cjs/src/state-dir-resolve.js +35 -0
- package/dist/cjs/src/state-dir-resolve.js.map +1 -0
- package/dist/cjs/src/state-manager.js +158 -0
- package/dist/cjs/src/state-manager.js.map +1 -0
- package/dist/cjs/src/template-card-parser.js +641 -0
- package/dist/cjs/src/template-card-parser.js.map +1 -0
- package/dist/cjs/src/timeout.js +42 -0
- package/dist/cjs/src/timeout.js.map +1 -0
- package/dist/cjs/src/utils.js +60 -0
- package/dist/cjs/src/utils.js.map +1 -0
- package/dist/cjs/src/version.js +26 -0
- package/dist/cjs/src/version.js.map +1 -0
- package/dist/esm/index.js +41 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/src/channel.js +337 -0
- package/dist/esm/src/channel.js.map +1 -0
- package/dist/{src → esm/src}/const.js +30 -77
- package/dist/esm/src/const.js.map +1 -0
- package/dist/{src → esm/src}/dm-policy.js +9 -6
- package/dist/esm/src/dm-policy.js.map +1 -0
- package/dist/{src → esm/src}/group-policy.js +7 -3
- package/dist/esm/src/group-policy.js.map +1 -0
- package/dist/{src → esm/src}/mcp/interceptors/biz-error.js +6 -2
- package/dist/esm/src/mcp/interceptors/biz-error.js.map +1 -0
- package/dist/{src → esm/src}/mcp/interceptors/index.js +10 -6
- package/dist/esm/src/mcp/interceptors/index.js.map +1 -0
- package/dist/{src → esm/src}/mcp/interceptors/msg-media.js +10 -6
- package/dist/esm/src/mcp/interceptors/msg-media.js.map +1 -0
- package/dist/{src → esm/src}/mcp/interceptors/smartpage-create.js +6 -2
- package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +1 -0
- package/dist/{src → esm/src}/mcp/interceptors/smartpage-export.js +6 -2
- package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +1 -0
- package/dist/{src → esm/src}/mcp/schema.js +4 -1
- package/dist/esm/src/mcp/schema.js.map +1 -0
- package/dist/{src → esm/src}/mcp/tool.js +8 -4
- package/dist/esm/src/mcp/tool.js.map +1 -0
- package/dist/{src → esm/src}/mcp/transport.js +16 -15
- package/dist/esm/src/mcp/transport.js.map +1 -0
- package/dist/{src → esm/src}/media-handler.js +10 -6
- package/dist/esm/src/media-handler.js.map +1 -0
- package/dist/{src → esm/src}/media-uploader.js +10 -64
- package/dist/esm/src/media-uploader.js.map +1 -0
- package/dist/{src → esm/src}/message-parser.js +4 -1
- package/dist/esm/src/message-parser.js.map +1 -0
- package/dist/{src → esm/src}/message-sender.js +11 -46
- package/dist/esm/src/message-sender.js.map +1 -0
- package/dist/{src → esm/src}/monitor.js +282 -151
- package/dist/esm/src/monitor.js.map +1 -0
- package/dist/{src → esm/src}/onboarding.js +26 -22
- package/dist/esm/src/onboarding.js.map +1 -0
- package/dist/{src → esm/src}/openclaw-compat.js +27 -143
- package/dist/esm/src/openclaw-compat.js.map +1 -0
- package/dist/{src → esm/src}/reqid-store.js +7 -4
- package/dist/esm/src/reqid-store.js.map +1 -0
- package/dist/{src → esm/src}/runtime.js +5 -2
- package/dist/esm/src/runtime.js.map +1 -0
- package/dist/esm/src/state-dir-resolve.js +13 -0
- package/dist/esm/src/state-dir-resolve.js.map +1 -0
- package/dist/{src → esm/src}/state-manager.js +16 -84
- package/dist/esm/src/state-manager.js.map +1 -0
- package/dist/{src → esm/src}/template-card-parser.js +8 -17
- package/dist/esm/src/template-card-parser.js.map +1 -0
- package/dist/{src → esm/src}/timeout.js +5 -2
- package/dist/esm/src/timeout.js.map +1 -0
- package/dist/{src → esm/src}/utils.js +10 -24
- package/dist/esm/src/utils.js.map +1 -0
- package/dist/esm/src/version.js +23 -0
- package/dist/esm/src/version.js.map +1 -0
- package/dist/esm/types/index.d.ts +9 -0
- package/dist/{src → esm/types/src}/const.d.ts +0 -46
- package/dist/{src → esm/types/src}/interface.d.ts +2 -0
- package/dist/{src → esm/types/src}/message-sender.d.ts +0 -18
- package/dist/{src → esm/types/src}/openclaw-compat.d.ts +0 -49
- package/dist/esm/types/src/state-dir-resolve.d.ts +2 -0
- package/dist/{src → esm/types/src}/utils.d.ts +0 -39
- package/dist/esm/types/src/version.d.ts +2 -0
- package/dist/index.d.ts +4 -2
- package/package.json +7 -15
- package/dist/index.js +0 -113
- package/dist/src/accounts.d.ts +0 -57
- package/dist/src/accounts.js +0 -222
- package/dist/src/agent/api-client.d.ts +0 -94
- package/dist/src/agent/api-client.js +0 -292
- package/dist/src/agent/handler.d.ts +0 -63
- package/dist/src/agent/handler.js +0 -532
- package/dist/src/agent/index.d.ts +0 -5
- package/dist/src/agent/index.js +0 -5
- package/dist/src/agent/webhook.d.ts +0 -23
- package/dist/src/agent/webhook.js +0 -226
- package/dist/src/agent/xml.d.ts +0 -21
- package/dist/src/agent/xml.js +0 -41
- package/dist/src/channel.js +0 -564
- package/dist/src/chat-queue.d.ts +0 -31
- package/dist/src/chat-queue.js +0 -45
- package/dist/src/http.d.ts +0 -26
- package/dist/src/http.js +0 -93
- package/dist/src/interface.js +0 -4
- package/dist/src/mcp/index.js +0 -6
- package/dist/src/mcp/interceptors/types.js +0 -7
- package/dist/src/shared/command-auth.d.ts +0 -23
- package/dist/src/shared/command-auth.js +0 -73
- package/dist/src/shared/xml-parser.d.ts +0 -46
- package/dist/src/shared/xml-parser.js +0 -210
- package/dist/src/target.d.ts +0 -35
- package/dist/src/target.js +0 -67
- package/dist/src/template-card-manager.d.ts +0 -55
- package/dist/src/template-card-manager.js +0 -205
- package/dist/src/types/account.d.ts +0 -29
- package/dist/src/types/account.js +0 -4
- package/dist/src/types/config.d.ts +0 -80
- package/dist/src/types/config.js +0 -7
- package/dist/src/types/constants.d.ts +0 -42
- package/dist/src/types/constants.js +0 -42
- package/dist/src/types/index.d.ts +0 -7
- package/dist/src/types/index.js +0 -5
- package/dist/src/types/message.d.ts +0 -238
- package/dist/src/types/message.js +0 -5
- package/dist/src/version.d.ts +0 -2
- package/dist/src/version.js +0 -24
- package/dist/src/webhook/command-auth.d.ts +0 -47
- package/dist/src/webhook/command-auth.js +0 -96
- package/dist/src/webhook/gateway.d.ts +0 -36
- package/dist/src/webhook/gateway.js +0 -227
- package/dist/src/webhook/handler.d.ts +0 -18
- package/dist/src/webhook/handler.js +0 -420
- package/dist/src/webhook/helpers.d.ts +0 -156
- package/dist/src/webhook/helpers.js +0 -833
- package/dist/src/webhook/http.d.ts +0 -26
- package/dist/src/webhook/http.js +0 -94
- package/dist/src/webhook/index.d.ts +0 -11
- package/dist/src/webhook/index.js +0 -14
- package/dist/src/webhook/media.d.ts +0 -29
- package/dist/src/webhook/media.js +0 -88
- package/dist/src/webhook/monitor.d.ts +0 -59
- package/dist/src/webhook/monitor.js +0 -1147
- package/dist/src/webhook/state.d.ts +0 -220
- package/dist/src/webhook/state.js +0 -491
- package/dist/src/webhook/target.d.ts +0 -41
- package/dist/src/webhook/target.js +0 -132
- package/dist/src/webhook/types.d.ts +0 -347
- package/dist/src/webhook/types.js +0 -33
- /package/dist/{src → esm/types/src}/channel.d.ts +0 -0
- /package/dist/{src → esm/types/src}/dm-policy.d.ts +0 -0
- /package/dist/{src → esm/types/src}/group-policy.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/index.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/interceptors/biz-error.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/interceptors/index.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/interceptors/msg-media.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/interceptors/smartpage-create.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/interceptors/smartpage-export.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/interceptors/types.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/schema.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/tool.d.ts +0 -0
- /package/dist/{src → esm/types/src}/mcp/transport.d.ts +0 -0
- /package/dist/{src → esm/types/src}/media-handler.d.ts +0 -0
- /package/dist/{src → esm/types/src}/media-uploader.d.ts +0 -0
- /package/dist/{src → esm/types/src}/message-parser.d.ts +0 -0
- /package/dist/{src → esm/types/src}/monitor.d.ts +0 -0
- /package/dist/{src → esm/types/src}/onboarding.d.ts +0 -0
- /package/dist/{src → esm/types/src}/reqid-store.d.ts +0 -0
- /package/dist/{src → esm/types/src}/runtime.d.ts +0 -0
- /package/dist/{src → esm/types/src}/state-manager.d.ts +0 -0
- /package/dist/{src → esm/types/src}/template-card-parser.d.ts +0 -0
- /package/dist/{src → esm/types/src}/timeout.d.ts +0 -0
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
**WeCom channel plugin for [OpenClaw](https://github.com/openclaw)** — by the Tencent WeCom team.
|
|
21
21
|
|
|
22
|
-
> A
|
|
22
|
+
> A bot plugin powered by WeCom AI Bot WebSocket persistent connections. Supports direct messages & group chats, streaming replies, and proactive messaging.
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -28,15 +28,13 @@
|
|
|
28
28
|
|
|
29
29
|
## ✨ Features
|
|
30
30
|
|
|
31
|
-
- 🔗
|
|
31
|
+
- 🔗 WebSocket persistent connection for stable communication
|
|
32
32
|
- 💬 Supports both direct messages (DM) and group chat
|
|
33
33
|
- 📤 Proactive messaging to specific users or groups
|
|
34
34
|
- 🖼️ Receives and processes image and file messages with automatic downloading
|
|
35
|
-
- ⏳ Streaming replies with "thinking" placeholder messages
|
|
36
|
-
- 🔐 Agent mode: AES-256-CBC encrypted XML callbacks with SHA1 signature verification
|
|
35
|
+
- ⏳ Streaming replies with "thinking" placeholder messages
|
|
37
36
|
- 📝 Markdown formatting support for replies
|
|
38
37
|
- 🔒 Built-in access control: DM Policy (pairing / open / allowlist / disabled) and Group Policy (open / allowlist / disabled)
|
|
39
|
-
- 👥 Multi-account support: run multiple WeCom accounts with independent bot/agent configs
|
|
40
38
|
- ⚡ Auto heartbeat keep-alive and reconnection (up to 100 reconnect attempts)
|
|
41
39
|
- 🧙 Interactive CLI setup wizard
|
|
42
40
|
|
|
@@ -91,16 +89,7 @@ openclaw config set channels.wecom.enabled true
|
|
|
91
89
|
openclaw gateway restart
|
|
92
90
|
```
|
|
93
91
|
|
|
94
|
-
###
|
|
95
|
-
|
|
96
|
-
The plugin supports two connection modes that can be used independently or together:
|
|
97
|
-
|
|
98
|
-
| Mode | Connection | Message Format | Use Case |
|
|
99
|
-
|------|-----------|---------------|----------|
|
|
100
|
-
| **Bot** (智能体) | WebSocket long-polling | JSON | Quick setup, streaming replies |
|
|
101
|
-
| **Agent** (自建应用) | HTTP webhook callbacks | XML | Enterprise apps, API-driven messaging |
|
|
102
|
-
|
|
103
|
-
### Bot Mode Configuration
|
|
92
|
+
### Configuration Reference
|
|
104
93
|
|
|
105
94
|
| Config Path | Description | Options | Default |
|
|
106
95
|
|---|---|---|---|
|
|
@@ -114,141 +103,6 @@ The plugin supports two connection modes that can be used independently or toget
|
|
|
114
103
|
| `channels.wecom.groupAllowFrom` | Group allowlist (group IDs) | — | `[]` |
|
|
115
104
|
| `channels.wecom.sendThinkingMessage` | Send "thinking" placeholder | `true` / `false` | `true` |
|
|
116
105
|
|
|
117
|
-
### Agent Mode Configuration
|
|
118
|
-
|
|
119
|
-
Agent mode uses HTTP webhook callbacks with XML encrypted messages. You need to configure the callback URL in the WeCom admin console under "API Receive" settings.
|
|
120
|
-
|
|
121
|
-
#### Prerequisites
|
|
122
|
-
|
|
123
|
-
1. Create a self-built app in [WeCom Admin Console](https://work.weixin.qq.com/wework_admin/frame#apps)
|
|
124
|
-
2. In the app settings, go to "API Receive" (API接收) and configure:
|
|
125
|
-
- **URL**: `https://<your-gateway-host>/plugins/wecom/agent` (or `/plugins/wecom/agent/<accountId>` for multi-account)
|
|
126
|
-
- **Token**: auto-generated or custom
|
|
127
|
-
- **EncodingAESKey**: auto-generated or custom
|
|
128
|
-
3. Note down the **CorpID**, **CorpSecret**, **Token**, and **EncodingAESKey**
|
|
129
|
-
|
|
130
|
-
#### Quick Setup
|
|
131
|
-
|
|
132
|
-
```shell
|
|
133
|
-
openclaw config set channels.wecom.agent.corpId <YOUR_CORP_ID>
|
|
134
|
-
openclaw config set channels.wecom.agent.corpSecret <YOUR_CORP_SECRET>
|
|
135
|
-
openclaw config set channels.wecom.agent.agentId <YOUR_AGENT_ID>
|
|
136
|
-
openclaw config set channels.wecom.agent.token <YOUR_CALLBACK_TOKEN>
|
|
137
|
-
openclaw config set channels.wecom.agent.encodingAESKey <YOUR_ENCODING_AES_KEY>
|
|
138
|
-
openclaw config set channels.wecom.enabled true
|
|
139
|
-
openclaw gateway restart
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
#### JSON Configuration
|
|
143
|
-
|
|
144
|
-
```json
|
|
145
|
-
{
|
|
146
|
-
"channels": {
|
|
147
|
-
"wecom": {
|
|
148
|
-
"enabled": true,
|
|
149
|
-
"agent": {
|
|
150
|
-
"corpId": "ww1234567890abcdef",
|
|
151
|
-
"corpSecret": "your-corp-secret",
|
|
152
|
-
"agentId": 1000002,
|
|
153
|
-
"token": "your-callback-token",
|
|
154
|
-
"encodingAESKey": "your-encoding-aes-key-43-chars"
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
#### Agent Config Reference
|
|
162
|
-
|
|
163
|
-
| Config Path | Description | Required |
|
|
164
|
-
|---|---|---|
|
|
165
|
-
| `channels.wecom.agent.corpId` | Enterprise Corp ID | Yes |
|
|
166
|
-
| `channels.wecom.agent.corpSecret` | App secret | Yes |
|
|
167
|
-
| `channels.wecom.agent.agentId` | App agent ID | No (needed for proactive messaging) |
|
|
168
|
-
| `channels.wecom.agent.token` | Callback verification token | Yes |
|
|
169
|
-
| `channels.wecom.agent.encodingAESKey` | Callback encryption key (43 chars) | Yes |
|
|
170
|
-
|
|
171
|
-
#### Webhook Paths
|
|
172
|
-
|
|
173
|
-
| Path | Description |
|
|
174
|
-
|---|---|
|
|
175
|
-
| `/plugins/wecom/agent` | Recommended path (single account) |
|
|
176
|
-
| `/plugins/wecom/agent/<accountId>` | Multi-account path |
|
|
177
|
-
| `/wecom/agent` | Legacy compatible path |
|
|
178
|
-
|
|
179
|
-
### Outbound Delivery (Bot WS → Agent HTTP Fallback)
|
|
180
|
-
|
|
181
|
-
The plugin uses a **Bot-first, Agent-fallback** strategy for outbound message delivery:
|
|
182
|
-
|
|
183
|
-
1. **Bot WebSocket available** → send via WS (supports markdown, streaming)
|
|
184
|
-
2. **Bot WS unavailable** → automatically fallback to **Agent HTTP API** (`cgi-bin/message/send`)
|
|
185
|
-
|
|
186
|
-
This means:
|
|
187
|
-
- **Agent-only accounts** (no Bot configured) can still send proactive messages, cron deliveries, and broadcasts
|
|
188
|
-
- **Target formats** like `party:1`, `tag:Ops`, `user:zhangsan` are fully supported in both paths
|
|
189
|
-
- No manual switching needed — the plugin handles fallback transparently
|
|
190
|
-
|
|
191
|
-
### Using Both Modes Together
|
|
192
|
-
|
|
193
|
-
Bot and Agent can run simultaneously on the same account. Bot handles WebSocket streaming; Agent handles HTTP webhook callbacks with API-driven replies.
|
|
194
|
-
|
|
195
|
-
```json
|
|
196
|
-
{
|
|
197
|
-
"channels": {
|
|
198
|
-
"wecom": {
|
|
199
|
-
"enabled": true,
|
|
200
|
-
"botId": "your-bot-id",
|
|
201
|
-
"secret": "your-bot-secret",
|
|
202
|
-
"agent": {
|
|
203
|
-
"corpId": "ww1234567890abcdef",
|
|
204
|
-
"corpSecret": "your-corp-secret",
|
|
205
|
-
"agentId": 1000002,
|
|
206
|
-
"token": "your-callback-token",
|
|
207
|
-
"encodingAESKey": "your-encoding-aes-key-43-chars"
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Multi-Account Configuration
|
|
215
|
-
|
|
216
|
-
Use `accounts` to configure multiple WeCom accounts, each with optional bot and/or agent sub-configs:
|
|
217
|
-
|
|
218
|
-
```json
|
|
219
|
-
{
|
|
220
|
-
"channels": {
|
|
221
|
-
"wecom": {
|
|
222
|
-
"enabled": true,
|
|
223
|
-
"accounts": {
|
|
224
|
-
"main": {
|
|
225
|
-
"bot": {
|
|
226
|
-
"botId": "bot-id-1",
|
|
227
|
-
"secret": "secret-1"
|
|
228
|
-
},
|
|
229
|
-
"agent": {
|
|
230
|
-
"corpId": "ww1234567890abcdef",
|
|
231
|
-
"corpSecret": "secret-a",
|
|
232
|
-
"agentId": 1000002,
|
|
233
|
-
"token": "token-a",
|
|
234
|
-
"encodingAESKey": "aes-key-a"
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
"support": {
|
|
238
|
-
"agent": {
|
|
239
|
-
"corpId": "ww1234567890abcdef",
|
|
240
|
-
"corpSecret": "secret-b",
|
|
241
|
-
"agentId": 1000003,
|
|
242
|
-
"token": "token-b",
|
|
243
|
-
"encodingAESKey": "aes-key-b"
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
106
|
---
|
|
253
107
|
|
|
254
108
|
## 🔒 Access Control
|
|
@@ -344,107 +198,6 @@ In addition to the group allowlist, you can restrict which members within a grou
|
|
|
344
198
|
|
|
345
199
|
---
|
|
346
200
|
|
|
347
|
-
## ⏰ Cronjob (Scheduled Tasks)
|
|
348
|
-
|
|
349
|
-
The plugin supports scheduled message delivery via OpenClaw's built-in Cron service. Cron jobs run through the **Agent outbound** channel, so Agent mode must be configured.
|
|
350
|
-
|
|
351
|
-
### Target Formats
|
|
352
|
-
|
|
353
|
-
The `delivery.to` field supports the following target formats:
|
|
354
|
-
|
|
355
|
-
| Format | Target | Example |
|
|
356
|
-
|--------|--------|---------|
|
|
357
|
-
| `party:<id>` | Department (all members) | `party:1` (root dept = all employees) |
|
|
358
|
-
| `dept:<id>` | Department (alias) | `dept:5` |
|
|
359
|
-
| `tag:<id>` | Tag group | `tag:Ops` |
|
|
360
|
-
| `user:<id>` | Specific user | `user:zhangsan` |
|
|
361
|
-
| `group:<id>` | External group chat | `group:wr123abc` |
|
|
362
|
-
| `chat:<id>` | Group chat (alias) | `chat:wc456def` |
|
|
363
|
-
| Pure number | Auto-detected as department | `1` → `party:1` |
|
|
364
|
-
| `wr...` / `wc...` | Auto-detected as group chat | `wr123` → `chatid` |
|
|
365
|
-
| Other string | Auto-detected as user | `zhangsan` → `touser` |
|
|
366
|
-
|
|
367
|
-
### Method 1: CLI (Recommended — takes effect immediately)
|
|
368
|
-
|
|
369
|
-
```shell
|
|
370
|
-
openclaw cron add \
|
|
371
|
-
--name "daily-report" \
|
|
372
|
-
--agent main \
|
|
373
|
-
--cron "0 9 * * 1-5" \
|
|
374
|
-
--message "Good morning! Here is your daily briefing." \
|
|
375
|
-
--deliver \
|
|
376
|
-
--delivery-channel wecom \
|
|
377
|
-
--delivery-to "party:1"
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
Common CLI commands:
|
|
381
|
-
|
|
382
|
-
```shell
|
|
383
|
-
openclaw cron list # List all cron jobs
|
|
384
|
-
openclaw cron enable <id> # Enable a job
|
|
385
|
-
openclaw cron disable <id> # Disable a job
|
|
386
|
-
openclaw cron remove <id> # Remove a job
|
|
387
|
-
openclaw cron run <id> # Manually trigger a job
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Method 2: Edit `jobs.json` (requires gateway restart)
|
|
391
|
-
|
|
392
|
-
File path: `~/.openclaw/cron/jobs.json`
|
|
393
|
-
|
|
394
|
-
```json
|
|
395
|
-
{
|
|
396
|
-
"version": 1,
|
|
397
|
-
"jobs": [
|
|
398
|
-
{
|
|
399
|
-
"id": "daily-report",
|
|
400
|
-
"name": "Daily Report",
|
|
401
|
-
"agentId": "main",
|
|
402
|
-
"enabled": true,
|
|
403
|
-
"schedule": { "kind": "cron", "expr": "0 9 * * 1-5", "tz": "Asia/Shanghai" },
|
|
404
|
-
"sessionTarget": "isolated",
|
|
405
|
-
"wakeMode": "now",
|
|
406
|
-
"payload": {
|
|
407
|
-
"kind": "agentTurn",
|
|
408
|
-
"message": "Generate today's briefing and send it.",
|
|
409
|
-
"deliver": true,
|
|
410
|
-
"channel": "wecom",
|
|
411
|
-
"to": "party:1"
|
|
412
|
-
},
|
|
413
|
-
"delivery": {
|
|
414
|
-
"mode": "announce",
|
|
415
|
-
"channel": "wecom",
|
|
416
|
-
"to": "party:1",
|
|
417
|
-
"accountId": "main"
|
|
418
|
-
},
|
|
419
|
-
"state": {}
|
|
420
|
-
}
|
|
421
|
-
]
|
|
422
|
-
}
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
After editing, restart the gateway:
|
|
426
|
-
|
|
427
|
-
```shell
|
|
428
|
-
openclaw gateway restart
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
### Method 3: Create via chat (takes effect immediately)
|
|
432
|
-
|
|
433
|
-
You can ask the AI agent directly in a WeCom conversation:
|
|
434
|
-
|
|
435
|
-
> "Create a scheduled task: send a daily briefing to the entire company at 9am every weekday"
|
|
436
|
-
|
|
437
|
-
The agent will call the Cron API to create the job — no restart needed.
|
|
438
|
-
|
|
439
|
-
### Notes
|
|
440
|
-
|
|
441
|
-
- Cron jobs use the **Agent outbound** path — Agent mode (`corpId` / `corpSecret` / `agentId`) must be configured.
|
|
442
|
-
- The server IP must be in the WeCom trusted IP allowlist, or configure `egressProxyUrl` for a fixed egress proxy.
|
|
443
|
-
- Jobs created via CLI or chat API take effect immediately. Manual edits to `jobs.json` require `openclaw gateway restart`.
|
|
444
|
-
- For multi-account setups, set `delivery.accountId` to the target account (e.g. `"main"`, `"support"`).
|
|
445
|
-
|
|
446
|
-
---
|
|
447
|
-
|
|
448
201
|
## 📦 Update
|
|
449
202
|
|
|
450
203
|
```shell
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var openclawCompat = require('./src/openclaw-compat.js');
|
|
6
|
+
var channel = require('./src/channel.js');
|
|
7
|
+
var tool = require('./src/mcp/tool.js');
|
|
8
|
+
require('@wecom/aibot-node-sdk');
|
|
9
|
+
var _const = require('./src/const.js');
|
|
10
|
+
require('./src/version.js');
|
|
11
|
+
var runtime = require('./src/runtime.js');
|
|
12
|
+
|
|
13
|
+
const plugin = {
|
|
14
|
+
id: "wecom-openclaw-plugin",
|
|
15
|
+
name: "企业微信",
|
|
16
|
+
description: "企业微信 OpenClaw 插件",
|
|
17
|
+
configSchema: openclawCompat.emptyPluginConfigSchema(),
|
|
18
|
+
register(api) {
|
|
19
|
+
runtime.setWeComRuntime(api.runtime);
|
|
20
|
+
api.registerChannel({ plugin: channel.wecomPlugin });
|
|
21
|
+
// 注册 wecom_mcp:通过 HTTP 直接调用企业微信 MCP Server
|
|
22
|
+
api.registerTool(tool.createWeComMcpTool(), { name: "wecom_mcp" });
|
|
23
|
+
// ── Gateway 启动时自动确保 tools.alsoAllow 包含 wecom_mcp ──────────
|
|
24
|
+
// 在 gateway_start 阶段检测并写入,保证插件安装/更新后首次启动即生效
|
|
25
|
+
// api.on("gateway_start", async () => {
|
|
26
|
+
// await ensureToolsAlsoAllow(api);
|
|
27
|
+
// });
|
|
28
|
+
// 注入媒体发送指令和文件大小限制提示词(仅对企业微信 channel 生效)
|
|
29
|
+
api.on("before_prompt_build", (_event, ctx) => {
|
|
30
|
+
// 只在企业微信 channel 的会话中注入,避免影响其他 channel 插件
|
|
31
|
+
if (ctx?.channelId !== _const.CHANNEL_ID) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
appendSystemContext: [
|
|
36
|
+
"重要:涉及发送图片/视频/语音/文件给用户时,请务必使用 `MEDIA:` 指令。详见 wecom-send-media 这个 skill(技能)。",
|
|
37
|
+
"重要:当需要向用户发送结构化卡片消息(如通知、投票、按钮选择等)时,请在回复中直接输出 JSON 代码块(```json ... ```),其中 card_type 字段标明卡片类型。详见 wecom-send-template-card 技能。"
|
|
38
|
+
].join("\n"),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
exports.default = plugin;
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../index.ts"],"sourcesContent":["import type { OpenClawPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { emptyPluginConfigSchema } from \"./src/openclaw-compat.js\";\nimport { wecomPlugin } from \"./src/channel.js\";\nimport { createWeComMcpTool } from \"./src/mcp\";\nimport { setWeComRuntime } from \"./src/runtime.js\";\nimport { CHANNEL_ID } from \"./src/const.js\";\n\n// ============================================================================\n// 需要自动注入 tools.alsoAllow 的工具名列表\n// ============================================================================\n\nconst REQUIRED_ALSO_ALLOW_TOOLS = [\"wecom_mcp\"] as const;\n\n/**\n * 确保 tools.alsoAllow 中包含插件所需的工具名。\n *\n * 逻辑:\n * 1. 读取当前配置\n * 2. 检查 tools.alsoAllow 中是否已包含所需工具\n * 3. 如有缺失,合并写入配置文件\n *\n * 幂等操作——重复调用不会产生副作用。\n * 若 tools.allow 已显式设置(与 alsoAllow 互斥),则跳过注入并打印提示。\n */\nasync function ensureToolsAlsoAllow(api: OpenClawPluginApi): Promise<void> {\n try {\n const cfg = api.runtime.config.loadConfig();\n const tools = cfg.tools ?? {};\n\n // 如果用户显式配置了 tools.allow(全量白名单),则 alsoAllow 与之互斥,\n // 此时不应自动追加 alsoAllow,避免产生校验冲突\n if (tools.allow && tools.allow.length > 0) {\n const missing = REQUIRED_ALSO_ALLOW_TOOLS.filter(\n (t) => !tools.allow!.includes(t),\n );\n if (missing.length > 0) {\n console.warn(\n `[wecom] tools.allow 已显式设置,无法自动注入 alsoAllow。` +\n `请手动将 ${JSON.stringify(missing)} 加入 tools.allow。`,\n );\n }\n return;\n }\n\n const existing = tools.alsoAllow ?? [];\n const missing = REQUIRED_ALSO_ALLOW_TOOLS.filter(\n (t) => !existing.includes(t),\n );\n\n if (missing.length === 0) {\n // 所有工具已在白名单中,无需操作\n return;\n }\n\n const merged = [...existing, ...missing];\n const nextConfig = {\n ...cfg,\n tools: {\n ...tools,\n alsoAllow: merged,\n },\n };\n\n await api.runtime.config.writeConfigFile(nextConfig);\n console.log(\n `[wecom] 已自动将 ${JSON.stringify(missing)} 加入 tools.alsoAllow`,\n );\n } catch (err) {\n // 配置写入失败不应阻断 Gateway 启动\n console.error(\n `[wecom] 自动注入 tools.alsoAllow 失败:`,\n err instanceof Error ? err.message : String(err),\n );\n }\n}\n\nconst plugin = {\n id: \"wecom-openclaw-plugin\",\n name: \"企业微信\",\n description: \"企业微信 OpenClaw 插件\",\n configSchema: emptyPluginConfigSchema(),\n register(api: OpenClawPluginApi) {\n\n setWeComRuntime(api.runtime);\n api.registerChannel({ plugin: wecomPlugin });\n\n // 注册 wecom_mcp:通过 HTTP 直接调用企业微信 MCP Server\n api.registerTool(createWeComMcpTool(), { name: \"wecom_mcp\" });\n\n // ── Gateway 启动时自动确保 tools.alsoAllow 包含 wecom_mcp ──────────\n // 在 gateway_start 阶段检测并写入,保证插件安装/更新后首次启动即生效\n // api.on(\"gateway_start\", async () => {\n // await ensureToolsAlsoAllow(api);\n // });\n\n // 注入媒体发送指令和文件大小限制提示词(仅对企业微信 channel 生效)\n api.on(\"before_prompt_build\", (_event, ctx) => {\n // 只在企业微信 channel 的会话中注入,避免影响其他 channel 插件\n if (ctx?.channelId !== CHANNEL_ID) {\n return;\n }\n return {\n appendSystemContext: [\n \"重要:涉及发送图片/视频/语音/文件给用户时,请务必使用 `MEDIA:` 指令。详见 wecom-send-media 这个 skill(技能)。\",\n \"重要:当需要向用户发送结构化卡片消息(如通知、投票、按钮选择等)时,请在回复中直接输出 JSON 代码块(```json ... ```),其中 card_type 字段标明卡片类型。详见 wecom-send-template-card 技能。\"\n ].join(\"\\n\"),\n };\n });\n },\n};\n\nexport default plugin;\n"],"names":["emptyPluginConfigSchema","setWeComRuntime","wecomPlugin","createWeComMcpTool","CHANNEL_ID"],"mappings":";;;;;;;;;;;;AA4EA,MAAM,MAAM,GAAG;AACb,IAAA,EAAE,EAAE,uBAAuB;AAC3B,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,WAAW,EAAE,kBAAkB;IAC/B,YAAY,EAAEA,sCAAuB,EAAE;AACvC,IAAA,QAAQ,CAAC,GAAsB,EAAA;AAE7B,QAAAC,uBAAe,CAAC,GAAG,CAAC,OAAO,CAAC;QAC5B,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,EAAEC,mBAAW,EAAE,CAAC;;AAG5C,QAAA,GAAG,CAAC,YAAY,CAACC,uBAAkB,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;;;;;;;QAS7D,GAAG,CAAC,EAAE,CAAC,qBAAqB,EAAE,CAAC,MAAM,EAAE,GAAG,KAAI;;AAE5C,YAAA,IAAI,GAAG,EAAE,SAAS,KAAKC,iBAAU,EAAE;gBACjC;YACF;YACA,OAAO;AACL,gBAAA,mBAAmB,EAAE;oBACnB,6EAA6E;oBAC7E;iBACD,CAAC,IAAI,CAAC,IAAI,CAAC;aACb;AACH,QAAA,CAAC,CAAC;IACJ,CAAC;;;;;"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var openclawCompat = require('./openclaw-compat.js');
|
|
4
|
+
var runtime = require('./runtime.js');
|
|
5
|
+
var monitor = require('./monitor.js');
|
|
6
|
+
var stateManager = require('./state-manager.js');
|
|
7
|
+
var onboarding = require('./onboarding.js');
|
|
8
|
+
var utils = require('./utils.js');
|
|
9
|
+
var _const = require('./const.js');
|
|
10
|
+
var mediaUploader = require('./media-uploader.js');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 使用 SDK 的 sendMessage 主动发送企业微信消息
|
|
14
|
+
* 无需依赖 reqId,直接向指定会话推送消息
|
|
15
|
+
*/
|
|
16
|
+
async function sendWeComMessage({ to, content, accountId, }) {
|
|
17
|
+
const resolvedAccountId = accountId ?? openclawCompat.DEFAULT_ACCOUNT_ID;
|
|
18
|
+
// 从 to 中提取 chatId(格式是 "${CHANNEL_ID}:chatId" 或直接是 chatId)
|
|
19
|
+
const channelPrefix = new RegExp(`^${_const.CHANNEL_ID}:`, "i");
|
|
20
|
+
const chatId = to.replace(channelPrefix, "");
|
|
21
|
+
// 获取 WSClient 实例
|
|
22
|
+
const wsClient = stateManager.getWeComWebSocket(resolvedAccountId);
|
|
23
|
+
if (!wsClient) {
|
|
24
|
+
throw new Error(`WSClient not connected for account ${resolvedAccountId}`);
|
|
25
|
+
}
|
|
26
|
+
// 使用 SDK 的 sendMessage 主动发送 markdown 消息
|
|
27
|
+
const result = await wsClient.sendMessage(chatId, {
|
|
28
|
+
msgtype: 'markdown',
|
|
29
|
+
markdown: { content },
|
|
30
|
+
});
|
|
31
|
+
const messageId = result?.headers?.req_id ?? `wecom-${Date.now()}`;
|
|
32
|
+
return {
|
|
33
|
+
channel: _const.CHANNEL_ID,
|
|
34
|
+
messageId,
|
|
35
|
+
chatId,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// 企业微信频道元数据
|
|
39
|
+
const meta = {
|
|
40
|
+
id: _const.CHANNEL_ID,
|
|
41
|
+
label: "企业微信",
|
|
42
|
+
selectionLabel: "企业微信 (WeCom)",
|
|
43
|
+
detailLabel: "企业微信智能机器人",
|
|
44
|
+
docsPath: `/channels/${_const.CHANNEL_ID}`,
|
|
45
|
+
docsLabel: _const.CHANNEL_ID,
|
|
46
|
+
blurb: "企业微信智能机器人接入插件",
|
|
47
|
+
systemImage: "message.fill",
|
|
48
|
+
};
|
|
49
|
+
const wecomPlugin = {
|
|
50
|
+
id: _const.CHANNEL_ID,
|
|
51
|
+
meta: {
|
|
52
|
+
...meta,
|
|
53
|
+
quickstartAllowFrom: true,
|
|
54
|
+
},
|
|
55
|
+
pairing: {
|
|
56
|
+
idLabel: "wecomUserId",
|
|
57
|
+
normalizeAllowEntry: (entry) => entry.replace(new RegExp(`^(${_const.CHANNEL_ID}|user):`, "i"), "").trim(),
|
|
58
|
+
notifyApproval: async ({ cfg, id }) => {
|
|
59
|
+
// sendWeComMessage({
|
|
60
|
+
// to: id,
|
|
61
|
+
// content: " pairing approved",
|
|
62
|
+
// accountId: cfg.accountId,
|
|
63
|
+
// });
|
|
64
|
+
// Pairing approved for user
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
setupWizard: onboarding.wecomSetupWizard,
|
|
68
|
+
setup: onboarding.wecomSetupAdapter,
|
|
69
|
+
capabilities: {
|
|
70
|
+
chatTypes: ["direct", "group"],
|
|
71
|
+
reactions: false,
|
|
72
|
+
threads: false,
|
|
73
|
+
media: true,
|
|
74
|
+
nativeCommands: false,
|
|
75
|
+
blockStreaming: true,
|
|
76
|
+
},
|
|
77
|
+
reload: { configPrefixes: [`channels.${_const.CHANNEL_ID}`] },
|
|
78
|
+
config: {
|
|
79
|
+
// 列出所有账户 ID(最小实现只支持默认账户)
|
|
80
|
+
listAccountIds: () => [openclawCompat.DEFAULT_ACCOUNT_ID],
|
|
81
|
+
// 解析账户配置
|
|
82
|
+
resolveAccount: (cfg) => utils.resolveWeComAccount(cfg),
|
|
83
|
+
// 获取默认账户 ID
|
|
84
|
+
defaultAccountId: () => openclawCompat.DEFAULT_ACCOUNT_ID,
|
|
85
|
+
// 设置账户启用状态
|
|
86
|
+
setAccountEnabled: ({ cfg, enabled }) => {
|
|
87
|
+
const wecomConfig = (cfg.channels?.[_const.CHANNEL_ID] ?? {});
|
|
88
|
+
return {
|
|
89
|
+
...cfg,
|
|
90
|
+
channels: {
|
|
91
|
+
...cfg.channels,
|
|
92
|
+
[_const.CHANNEL_ID]: {
|
|
93
|
+
...wecomConfig,
|
|
94
|
+
enabled,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
// 删除账户
|
|
100
|
+
deleteAccount: ({ cfg }) => {
|
|
101
|
+
const wecomConfig = (cfg.channels?.[_const.CHANNEL_ID] ?? {});
|
|
102
|
+
const { botId, secret, ...rest } = wecomConfig;
|
|
103
|
+
return {
|
|
104
|
+
...cfg,
|
|
105
|
+
channels: {
|
|
106
|
+
...cfg.channels,
|
|
107
|
+
[_const.CHANNEL_ID]: rest,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
// 检查是否已配置
|
|
112
|
+
isConfigured: (account) => Boolean(account.botId?.trim() && account.secret?.trim()),
|
|
113
|
+
// 描述账户信息
|
|
114
|
+
describeAccount: (account) => ({
|
|
115
|
+
accountId: account.accountId,
|
|
116
|
+
name: account.name,
|
|
117
|
+
enabled: account.enabled,
|
|
118
|
+
configured: Boolean(account.botId?.trim() && account.secret?.trim()),
|
|
119
|
+
botId: account.botId,
|
|
120
|
+
websocketUrl: account.websocketUrl,
|
|
121
|
+
}),
|
|
122
|
+
// 解析允许来源列表
|
|
123
|
+
resolveAllowFrom: ({ cfg }) => {
|
|
124
|
+
const account = utils.resolveWeComAccount(cfg);
|
|
125
|
+
return (account.config.allowFrom ?? []).map((entry) => String(entry));
|
|
126
|
+
},
|
|
127
|
+
// 格式化允许来源列表
|
|
128
|
+
formatAllowFrom: ({ allowFrom }) => allowFrom
|
|
129
|
+
.map((entry) => String(entry).trim())
|
|
130
|
+
.filter(Boolean),
|
|
131
|
+
},
|
|
132
|
+
security: {
|
|
133
|
+
resolveDmPolicy: ({ account }) => {
|
|
134
|
+
const basePath = `channels.${_const.CHANNEL_ID}.`;
|
|
135
|
+
return {
|
|
136
|
+
policy: account.config.dmPolicy ?? "open",
|
|
137
|
+
allowFrom: account.config.allowFrom ?? [],
|
|
138
|
+
policyPath: `${basePath}dmPolicy`,
|
|
139
|
+
allowFromPath: basePath,
|
|
140
|
+
approveHint: openclawCompat.formatPairingApproveHint(_const.CHANNEL_ID),
|
|
141
|
+
normalizeEntry: (raw) => raw.replace(new RegExp(`^${_const.CHANNEL_ID}:`, "i"), "").trim(),
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
collectWarnings: ({ account, cfg }) => {
|
|
145
|
+
const warnings = [];
|
|
146
|
+
// DM 策略警告
|
|
147
|
+
const dmPolicy = account.config.dmPolicy ?? "open";
|
|
148
|
+
if (dmPolicy === "open") {
|
|
149
|
+
const hasWildcard = (account.config.allowFrom ?? []).some((entry) => String(entry).trim() === "*");
|
|
150
|
+
if (!hasWildcard) {
|
|
151
|
+
warnings.push(`- 企业微信私信:dmPolicy="open" 但 allowFrom 未包含 "*"。任何人都可以发消息,但允许列表为空可能导致意外行为。建议设置 channels.${_const.CHANNEL_ID}.allowFrom=["*"] 或使用 dmPolicy="pairing"。`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// 群组策略警告
|
|
155
|
+
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
|
156
|
+
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "open";
|
|
157
|
+
// const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({
|
|
158
|
+
// providerConfigPresent: true,
|
|
159
|
+
// groupPolicy: account.config.groupPolicy,
|
|
160
|
+
// defaultGroupPolicy,
|
|
161
|
+
// });
|
|
162
|
+
if (groupPolicy === "open") {
|
|
163
|
+
warnings.push(`- 企业微信群组:groupPolicy="open" 允许所有群组中的成员触发。设置 channels.${_const.CHANNEL_ID}.groupPolicy="allowlist" + channels.${_const.CHANNEL_ID}.groupAllowFrom 来限制群组。`);
|
|
164
|
+
}
|
|
165
|
+
return warnings;
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
messaging: {
|
|
169
|
+
normalizeTarget: (target) => {
|
|
170
|
+
const trimmed = target.trim();
|
|
171
|
+
if (!trimmed)
|
|
172
|
+
return undefined;
|
|
173
|
+
return trimmed;
|
|
174
|
+
},
|
|
175
|
+
targetResolver: {
|
|
176
|
+
looksLikeId: (id) => {
|
|
177
|
+
const trimmed = id?.trim();
|
|
178
|
+
return Boolean(trimmed);
|
|
179
|
+
},
|
|
180
|
+
hint: "<userId|groupId>",
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
directory: {
|
|
184
|
+
self: async () => null,
|
|
185
|
+
listPeers: async () => [],
|
|
186
|
+
listGroups: async () => [],
|
|
187
|
+
},
|
|
188
|
+
outbound: {
|
|
189
|
+
deliveryMode: "gateway",
|
|
190
|
+
chunker: (text, limit) => runtime.getWeComRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
191
|
+
textChunkLimit: _const.TEXT_CHUNK_LIMIT,
|
|
192
|
+
sendText: async ({ to, text, accountId }) => {
|
|
193
|
+
return sendWeComMessage({ to, content: text, accountId: accountId ?? undefined });
|
|
194
|
+
},
|
|
195
|
+
sendMedia: async ({ to, text, mediaUrl, mediaLocalRoots, accountId }) => {
|
|
196
|
+
const resolvedAccountId = accountId ?? openclawCompat.DEFAULT_ACCOUNT_ID;
|
|
197
|
+
const channelPrefix = new RegExp(`^${_const.CHANNEL_ID}:`, "i");
|
|
198
|
+
const chatId = to.replace(channelPrefix, "");
|
|
199
|
+
// 获取 WSClient 实例
|
|
200
|
+
const wsClient = stateManager.getWeComWebSocket(resolvedAccountId);
|
|
201
|
+
if (!wsClient) {
|
|
202
|
+
throw new Error(`WSClient not connected for account ${resolvedAccountId}`);
|
|
203
|
+
}
|
|
204
|
+
// 如果没有 mediaUrl,fallback 为纯文本
|
|
205
|
+
if (!mediaUrl) {
|
|
206
|
+
return sendWeComMessage({ to, content: text || "", accountId: resolvedAccountId });
|
|
207
|
+
}
|
|
208
|
+
const result = await mediaUploader.uploadAndSendMedia({
|
|
209
|
+
wsClient,
|
|
210
|
+
mediaUrl,
|
|
211
|
+
chatId,
|
|
212
|
+
mediaLocalRoots,
|
|
213
|
+
});
|
|
214
|
+
if (result.rejected) {
|
|
215
|
+
return sendWeComMessage({ to, content: `⚠️ ${result.rejectReason}`, accountId: resolvedAccountId });
|
|
216
|
+
}
|
|
217
|
+
if (!result.ok) {
|
|
218
|
+
// 上传/发送失败,fallback 为文本 + URL
|
|
219
|
+
const fallbackContent = text
|
|
220
|
+
? `${text}\n📎 ${mediaUrl}`
|
|
221
|
+
: `📎 ${mediaUrl}`;
|
|
222
|
+
return sendWeComMessage({ to, content: fallbackContent, accountId: resolvedAccountId });
|
|
223
|
+
}
|
|
224
|
+
// 如有伴随文本,额外发送一条 markdown
|
|
225
|
+
if (text) {
|
|
226
|
+
await sendWeComMessage({ to, content: text, accountId: resolvedAccountId });
|
|
227
|
+
}
|
|
228
|
+
// 如果有降级说明,额外发送提示
|
|
229
|
+
if (result.downgradeNote) {
|
|
230
|
+
await sendWeComMessage({ to, content: `ℹ️ ${result.downgradeNote}`, accountId: resolvedAccountId });
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
channel: _const.CHANNEL_ID,
|
|
234
|
+
messageId: result.messageId,
|
|
235
|
+
chatId,
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
status: {
|
|
240
|
+
defaultRuntime: {
|
|
241
|
+
accountId: openclawCompat.DEFAULT_ACCOUNT_ID,
|
|
242
|
+
running: false,
|
|
243
|
+
lastStartAt: null,
|
|
244
|
+
lastStopAt: null,
|
|
245
|
+
lastError: null,
|
|
246
|
+
},
|
|
247
|
+
collectStatusIssues: (accounts) => accounts.flatMap((entry) => {
|
|
248
|
+
const accountId = String(entry.accountId ?? openclawCompat.DEFAULT_ACCOUNT_ID);
|
|
249
|
+
const enabled = entry.enabled !== false;
|
|
250
|
+
const configured = entry.configured === true;
|
|
251
|
+
if (!enabled) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
const issues = [];
|
|
255
|
+
if (!configured) {
|
|
256
|
+
issues.push({
|
|
257
|
+
channel: _const.CHANNEL_ID,
|
|
258
|
+
accountId,
|
|
259
|
+
kind: "config",
|
|
260
|
+
message: "企业微信机器人 ID 或 Secret 未配置",
|
|
261
|
+
fix: "Run: openclaw channels add wecom --bot-id <id> --secret <secret>",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return issues;
|
|
265
|
+
}),
|
|
266
|
+
buildChannelSummary: ({ snapshot }) => ({
|
|
267
|
+
configured: snapshot.configured ?? false,
|
|
268
|
+
running: snapshot.running ?? false,
|
|
269
|
+
lastStartAt: snapshot.lastStartAt ?? null,
|
|
270
|
+
lastStopAt: snapshot.lastStopAt ?? null,
|
|
271
|
+
lastError: snapshot.lastError ?? null,
|
|
272
|
+
}),
|
|
273
|
+
probeAccount: async () => {
|
|
274
|
+
return { ok: true, status: 200 };
|
|
275
|
+
},
|
|
276
|
+
buildAccountSnapshot: ({ account, runtime }) => {
|
|
277
|
+
const configured = Boolean(account.botId?.trim() &&
|
|
278
|
+
account.secret?.trim());
|
|
279
|
+
return {
|
|
280
|
+
accountId: account.accountId,
|
|
281
|
+
name: account.name,
|
|
282
|
+
enabled: account.enabled,
|
|
283
|
+
configured,
|
|
284
|
+
running: runtime?.running ?? false,
|
|
285
|
+
lastStartAt: runtime?.lastStartAt ?? null,
|
|
286
|
+
lastStopAt: runtime?.lastStopAt ?? null,
|
|
287
|
+
lastError: runtime?.lastError ?? null,
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
gateway: {
|
|
292
|
+
startAccount: async (ctx) => {
|
|
293
|
+
const account = ctx.account;
|
|
294
|
+
// 启动 WebSocket 监听
|
|
295
|
+
return monitor.monitorWeComProvider({
|
|
296
|
+
account,
|
|
297
|
+
config: ctx.cfg,
|
|
298
|
+
runtime: ctx.runtime,
|
|
299
|
+
abortSignal: ctx.abortSignal,
|
|
300
|
+
setStatus: ctx.setStatus,
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
logoutAccount: async ({ cfg }) => {
|
|
304
|
+
const nextCfg = { ...cfg };
|
|
305
|
+
const wecomConfig = (cfg.channels?.[_const.CHANNEL_ID] ?? {});
|
|
306
|
+
const nextWecom = { ...wecomConfig };
|
|
307
|
+
let cleared = false;
|
|
308
|
+
let changed = false;
|
|
309
|
+
if (nextWecom.botId || nextWecom.secret) {
|
|
310
|
+
delete nextWecom.botId;
|
|
311
|
+
delete nextWecom.secret;
|
|
312
|
+
cleared = true;
|
|
313
|
+
changed = true;
|
|
314
|
+
}
|
|
315
|
+
if (changed) {
|
|
316
|
+
if (Object.keys(nextWecom).length > 0) {
|
|
317
|
+
nextCfg.channels = { ...nextCfg.channels, [_const.CHANNEL_ID]: nextWecom };
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
const nextChannels = { ...nextCfg.channels };
|
|
321
|
+
delete nextChannels[_const.CHANNEL_ID];
|
|
322
|
+
if (Object.keys(nextChannels).length > 0) {
|
|
323
|
+
nextCfg.channels = nextChannels;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
delete nextCfg.channels;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
await runtime.getWeComRuntime().config.writeConfigFile(nextCfg);
|
|
330
|
+
}
|
|
331
|
+
const resolved = utils.resolveWeComAccount(changed ? nextCfg : cfg);
|
|
332
|
+
const loggedOut = !resolved.botId && !resolved.secret;
|
|
333
|
+
return { cleared, envToken: false, loggedOut };
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
exports.wecomPlugin = wecomPlugin;
|
|
339
|
+
//# sourceMappingURL=channel.js.map
|