@pawastation/wechat-kf 0.1.2 → 0.2.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.
Files changed (55) hide show
  1. package/README.md +34 -28
  2. package/README.zh-CN.md +34 -28
  3. package/dist/index.d.ts +5 -15
  4. package/dist/index.js +5 -5
  5. package/dist/index.js.map +1 -1
  6. package/dist/src/accounts.d.ts +2 -1
  7. package/dist/src/accounts.js +61 -19
  8. package/dist/src/accounts.js.map +1 -1
  9. package/dist/src/api.d.ts +31 -2
  10. package/dist/src/api.js +41 -13
  11. package/dist/src/api.js.map +1 -1
  12. package/dist/src/bot.d.ts +10 -8
  13. package/dist/src/bot.js +231 -78
  14. package/dist/src/bot.js.map +1 -1
  15. package/dist/src/channel.d.ts +7 -106
  16. package/dist/src/channel.js +208 -71
  17. package/dist/src/channel.js.map +1 -1
  18. package/dist/src/config-schema.d.ts +0 -6
  19. package/dist/src/config-schema.js +2 -7
  20. package/dist/src/config-schema.js.map +1 -1
  21. package/dist/src/constants.d.ts +20 -0
  22. package/dist/src/constants.js +29 -0
  23. package/dist/src/constants.js.map +1 -1
  24. package/dist/src/crypto.js +7 -6
  25. package/dist/src/crypto.js.map +1 -1
  26. package/dist/src/monitor.d.ts +27 -14
  27. package/dist/src/monitor.js +67 -120
  28. package/dist/src/monitor.js.map +1 -1
  29. package/dist/src/outbound.d.ts +10 -44
  30. package/dist/src/outbound.js +277 -92
  31. package/dist/src/outbound.js.map +1 -1
  32. package/dist/src/reply-dispatcher.d.ts +2 -6
  33. package/dist/src/reply-dispatcher.js +131 -32
  34. package/dist/src/reply-dispatcher.js.map +1 -1
  35. package/dist/src/runtime.d.ts +1 -119
  36. package/dist/src/runtime.js +2 -1
  37. package/dist/src/runtime.js.map +1 -1
  38. package/dist/src/send-utils.d.ts +13 -0
  39. package/dist/src/send-utils.js +56 -4
  40. package/dist/src/send-utils.js.map +1 -1
  41. package/dist/src/token.js +7 -3
  42. package/dist/src/token.js.map +1 -1
  43. package/dist/src/types.d.ts +68 -6
  44. package/dist/src/webhook.d.ts +16 -16
  45. package/dist/src/webhook.js +92 -75
  46. package/dist/src/webhook.js.map +1 -1
  47. package/dist/src/wechat-kf-directives.d.ts +132 -9
  48. package/dist/src/wechat-kf-directives.js +535 -24
  49. package/dist/src/wechat-kf-directives.js.map +1 -1
  50. package/index.ts +22 -12
  51. package/openclaw.plugin.json +1 -3
  52. package/package.json +3 -2
  53. package/dist/src/chunk-utils.d.ts +0 -18
  54. package/dist/src/chunk-utils.js +0 -58
  55. package/dist/src/chunk-utils.js.map +0 -1
package/README.md CHANGED
@@ -12,29 +12,29 @@
12
12
 
13
13
  ## Features
14
14
 
15
- - **Inbound message handling** — receive text, image, voice, video, file, location, link, mini-program, channels, business card, and forwarded chat history from WeChat users (11+ message types)
15
+ - **Inbound message handling** — receive text, image, voice, video, file, location, link, mini-program, channels, channels shop product, channels shop order, note, business card, and forwarded chat history from WeChat users (14+ message types)
16
16
  - **Event handling** — processes enter_session, msg_send_fail, and servicer_status_change events
17
- - **Rich outbound messaging** — send text, image, voice, video, file, and link messages back to users
18
- - **Media upload & download** — automatically downloads inbound media and uploads outbound media via the WeCom temporary media API; supports HTTP URL download for outbound media
17
+ - **Rich outbound messaging** — send text, image, voice, video, file, link, location, mini-program, menu, business card, and channel article messages back to users
18
+ - **Media upload & download** — automatically downloads inbound media and uploads outbound media via the WeCom temporary media API; supports all URL formats (HTTP, file://, local paths) for outbound media via framework loadWebMedia
19
19
  - **Markdown to Unicode formatting** — converts markdown bold/italic/headings/lists to Unicode Mathematical Alphanumeric symbols for styled plain-text display in WeChat
20
20
  - **AES-256-CBC encryption** — full WeChat callback encryption/decryption with SHA-1 signature verification and PKCS#7 padding validation
21
- - **Webhook + polling fallback** — HTTP webhook server for real-time callbacks, with automatic 30-second polling fallback for reliability; hardened with body size limits, method validation, and error responses
21
+ - **Webhook + polling fallback** — webhook handler registered on framework's shared gateway for real-time callbacks, with automatic 30-second polling fallback for reliability
22
22
  - **Dynamic KF account discovery** — KF account IDs (open_kfid) are automatically discovered from webhook callbacks with enable/disable/delete lifecycle management
23
23
  - **Cursor-based incremental sync** — persists sync cursors per KF account with atomic file writes for crash safety
24
24
  - **Access token auto-caching** — tokens cached in memory with hashed keys, automatic refresh 5 minutes before expiry, and auto-retry on token expiry
25
25
  - **Multi-KF-account isolation** — each KF account gets its own session, cursor, and routing context with per-kfId processing mutex
26
- - **DM policy control** — configurable access control: `open` or `allowlist` with security adapter (resolveDmPolicy, collectWarnings). `pairing` mode is not yet implemented.
26
+ - **DM policy control** — configurable access control: `open`, `allowlist`, or `pairing` with security adapter (resolveDmPolicy, collectWarnings)
27
27
  - **Text chunking** — automatically splits long replies to respect WeChat's 2000-character message size limit, with chunker declaration for framework integration
28
28
  - **Session limit awareness** — detects and gracefully handles WeChat's 48-hour reply window and 5-message-per-window limits
29
29
  - **Race condition safety** — per-kfId mutex and msgid deduplication prevent duplicate message processing
30
30
  - **Human-like reply delays** — configurable typing delay simulation for natural conversation pacing
31
- - **Graceful shutdown** — responds to abort signals with pre-check guards, cleanly stopping the webhook server and polling
31
+ - **Graceful shutdown** — responds to abort signals with pre-check guards, cleanly stopping polling loops
32
32
 
33
33
  ## Prerequisites
34
34
 
35
35
  1. A **WeCom account** (企业微信) with admin privileges — [Register here](https://work.weixin.qq.com/)
36
36
  2. At least one **Customer Service account** (客服账号) created in WeCom's WeChat Customer Service module
37
- 3. A **publicly accessible URL** for receiving callbacks — you can use [ngrok](https://ngrok.com/), [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/), or a server with a public IP
37
+ 3. A **publicly accessible URL** for receiving callbacks — you can use [Tailscale Funnel](https://docs.openclaw.ai/gateway/tailscale#tailscale) (recommended, built-in to OpenClaw Gateway), [ngrok](https://ngrok.com/), [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/), or a server with a public IP
38
38
  4. **OpenClaw Gateway** installed and running (`openclaw gateway start`)
39
39
 
40
40
  ## Installation
@@ -94,9 +94,8 @@ channels:
94
94
  appSecret: "your-app-secret-here" # App Secret (self-built app or WeChat KF secret)
95
95
  token: "your-callback-token" # Callback Token
96
96
  encodingAESKey: "your-43-char-key" # Callback EncodingAESKey (43 characters)
97
- webhookPort: 9999 # Local port for webhook server (default: 9999)
98
97
  webhookPath: "/wechat-kf" # URL path for webhook (default: /wechat-kf)
99
- dmPolicy: "open" # Access control: open | allowlist (pairing: not yet implemented)
98
+ dmPolicy: "open" # Access control: open | allowlist | pairing | disabled
100
99
  # allowFrom: # Only used with dmPolicy: allowlist
101
100
  # - "external_userid_1"
102
101
  # - "external_userid_2"
@@ -111,9 +110,8 @@ channels:
111
110
  | `appSecret` | string | **Yes** | — | Self-built app secret or WeChat KF secret |
112
111
  | `token` | string | **Yes** | — | Webhook callback token |
113
112
  | `encodingAESKey` | string | **Yes** | — | 43-char AES key for message encryption |
114
- | `webhookPort` | integer | No | `9999` | Port for the HTTP webhook server |
115
113
  | `webhookPath` | string | No | `/wechat-kf` | URL path for webhook callbacks |
116
- | `dmPolicy` | string | No | `"open"` | `open` / `allowlist` (`pairing` not yet implemented) |
114
+ | `dmPolicy` | string | No | `"open"` | `open` / `allowlist` / `pairing` / `disabled` |
117
115
  | `allowFrom` | string[] | No | `[]` | Allowed external_userids (when dmPolicy is `allowlist`) |
118
116
 
119
117
  ## Verification
@@ -122,13 +120,17 @@ channels:
122
120
  ```bash
123
121
  openclaw gateway start
124
122
  ```
125
- 2. Expose the webhook port (if not on a public server):
123
+ 2. Expose the gateway to the public internet (if not on a public server). Option A — Tailscale Funnel (built-in):
126
124
  ```bash
127
- ngrok http 9999
125
+ openclaw gateway --tailscale funnel --auth password
128
126
  ```
129
- 3. Copy the HTTPS URL (e.g. `https://xxxx.ngrok-free.app`) and set the callback URL in WeCom:
127
+ Option B ngrok:
128
+ ```bash
129
+ ngrok http <gateway-port>
130
+ ```
131
+ 3. Copy the HTTPS URL (e.g. `https://your-machine.tail1234.ts.net` or `https://xxxx.ngrok-free.app`) and set the callback URL in WeCom:
130
132
  ```
131
- https://xxxx.ngrok-free.app/wechat-kf
133
+ https://<your-public-host>/wechat-kf
132
134
  ```
133
135
  4. WeCom sends a GET verification request — the plugin decrypts the `echostr` and responds automatically
134
136
  5. Send a test message from WeChat (via the KF link) and confirm the agent responds
@@ -160,15 +162,18 @@ The agent can use the `message` tool to send messages:
160
162
  | Video | Downloaded as MP4, saved as media attachment |
161
163
  | File | Downloaded, saved as media attachment |
162
164
  | Location | Converted to text: `[Location: name address]` |
163
- | Link | Converted to text: `[Link: title url]` |
164
- | Mini Program | Converted to text with title and appid |
165
+ | Link | Converted to text: `[Link: title url]` (with desc, pic_url) |
166
+ | Mini Program | Converted to text with title, appid, and pagepath |
165
167
  | Channels (Video Account) | Converted to text with type, nickname, title |
168
+ | Channels Shop Product | Converted to text with product info |
169
+ | Channels Shop Order | Converted to text with order info |
170
+ | Note | Converted to text with note content |
166
171
  | Business Card | Converted to text with userid |
167
172
  | Forwarded Messages | Parsed and expanded into readable text |
168
173
 
169
174
  ### Supported outbound message types
170
175
 
171
- Text, image, voice, video, file, and link messages. Local files are automatically uploaded to WeChat's temporary media storage before sending.
176
+ Text, image, voice, video, file, link, location, mini-program, menu, business card, channel article, and raw JSON messages (`[[wechat_raw:...]]`). Rich message types are sent via `[[wechat_*:...]]` text directives. Media from any source (local files, HTTP URLs, file:// URIs) is loaded via the framework's loadWebMedia and uploaded to WeChat's temporary media storage before sending.
172
177
 
173
178
  ## Architecture
174
179
 
@@ -201,9 +206,9 @@ WeCom Server (Tencent)
201
206
  | +-----------+-----------+
202
207
  | v
203
208
  | send-utils.ts
204
- | formatText, detectMediaType
205
- | uploadAndSendMedia
206
- | downloadMediaFromUrl
209
+ | formatText, mediaKindToWechatType
210
+ | detectMediaType, uploadAndSendMedia
211
+ | resolveThumbMediaId
207
212
  | v
208
213
  +--- send_msg API <--- api.ts
209
214
  (JSON)
@@ -213,21 +218,22 @@ WeCom Server (Tencent)
213
218
 
214
219
  | Module | Role |
215
220
  | --------------------- | ------------------------------------------------------------------------------------------------- |
216
- | `webhook.ts` | HTTP server — GET verification, POST event handling, size/method guards |
221
+ | `webhook.ts` | HTTP handler (framework gateway) — GET verification, POST event handling, size/method guards |
217
222
  | `crypto.ts` | AES-256-CBC encrypt/decrypt, SHA-1 signature, full PKCS#7 validation |
218
223
  | `token.ts` | Access token cache with hashed key and auto-refresh |
219
- | `api.ts` | WeCom API client (sync_msg, send_msg, media upload/download) with token auto-retry |
224
+ | `api.ts` | WeCom API client (sync_msg, send_msg, sendRawMessage, media upload/download) with token auto-retry |
220
225
  | `accounts.ts` | Dynamic KF account discovery, resolution, enable/disable/delete lifecycle |
221
226
  | `bot.ts` | Message sync with mutex + dedup, DM policy check, event handling, agent dispatch |
222
- | `monitor.ts` | Webhook + polling lifecycle management with AbortSignal guards |
227
+ | `monitor.ts` | Shared context manager (setSharedContext/getSharedContext/waitForSharedContext/clearSharedContext) |
223
228
  | `reply-dispatcher.ts` | Plugin-internal streaming reply delivery with chunking, formatting, delays |
224
229
  | `outbound.ts` | Framework-driven outbound adapter with chunker declaration |
225
- | `send-utils.ts` | Shared outbound utilities (formatText, detectMediaType, uploadAndSendMedia, downloadMediaFromUrl) |
226
- | `chunk-utils.ts` | Text chunking with natural boundary splitting (newline, whitespace, hard-cut) |
230
+ | `send-utils.ts` | Shared outbound utilities (formatText, mediaKindToWechatType, detectMediaType, uploadAndSendMedia, resolveThumbMediaId) |
231
+ | `wechat-kf-directives.ts` | `[[wechat_*:...]]` directive parser for rich message types in agent replies |
227
232
  | `constants.ts` | Shared constants (WECHAT_TEXT_CHUNK_LIMIT, timeouts, error codes) |
228
233
  | `fs-utils.ts` | Atomic file operations (temp file + rename) |
229
234
  | `unicode-format.ts` | Markdown to Unicode Mathematical styled text |
230
235
  | `channel.ts` | ChannelPlugin interface with security adapter (resolveDmPolicy, collectWarnings) |
236
+ | `config-schema.ts` | JSON Schema for wechat-kf channel config validation |
231
237
  | `runtime.ts` | OpenClaw runtime reference holder |
232
238
 
233
239
  ### State persistence
@@ -243,7 +249,7 @@ WeCom Server (Tencent)
243
249
  - **5 messages per window** — you can send at most 5 replies before the user sends another message. The plugin detects this limit and logs accordingly.
244
250
  - **Voice format** — inbound voice messages are AMR format; transcription depends on the OpenClaw agent's media processing capabilities.
245
251
  - **Temporary media only** — uploaded media uses WeChat's temporary media API (3-day expiry). Permanent media upload is not implemented.
246
- - **Single webhook endpoint** — all KF accounts share the same webhook port and path. This is by design (WeCom sends all callbacks to one URL per enterprise).
252
+ - **Single webhook endpoint** — all KF accounts share the same webhook path. This is by design (WeCom sends all callbacks to one URL per enterprise).
247
253
  - **No group chat** — WeChat KF is direct messaging only. The plugin only supports `direct` chat type.
248
254
  - **IP whitelist drift** — if your server's public IP changes, API calls will fail silently. Monitor your IP or use a static IP.
249
255
 
@@ -259,7 +265,7 @@ pnpm run build
259
265
  # Type check
260
266
  pnpm run typecheck
261
267
 
262
- # Run tests (363 tests across 16 files)
268
+ # Run tests (~600 tests across 17 files)
263
269
  pnpm test
264
270
 
265
271
  # Watch mode
package/README.zh-CN.md CHANGED
@@ -12,29 +12,29 @@
12
12
 
13
13
  ## 功能特性
14
14
 
15
- - **入站消息处理** — 接收文本、图片、语音、视频、文件、位置、链接、小程序、视频号、名片、合并转发消息等 11+ 种消息类型
15
+ - **入站消息处理** — 接收文本、图片、语音、视频、文件、位置、链接、小程序、视频号、视频号商品、视频号订单、笔记、名片、合并转发消息等 14+ 种消息类型
16
16
  - **事件处理** — 处理 enter_session(用户进入会话)、msg_send_fail(消息发送失败)、servicer_status_change(接待人员状态变更)事件
17
- - **丰富的出站消息** — 发送文本、图片、语音、视频、文件和链接消息
18
- - **媒体上传与下载** — 自动下载入站媒体(图片、语音、视频、文件),通过企业微信临时素材 API 上传出站媒体;支持 HTTP URL 下载
17
+ - **丰富的出站消息** — 发送文本、图片、语音、视频、文件、链接、位置、小程序、菜单、名片和视频号文章消息
18
+ - **媒体上传与下载** — 自动下载入站媒体(图片、语音、视频、文件),通过企业微信临时素材 API 上传出站媒体;通过框架 loadWebMedia 支持所有 URL 格式(HTTP、file://、本地路径)
19
19
  - **Markdown 转 Unicode 格式化** — 将 Markdown 粗体/斜体/标题/列表转换为 Unicode 数学字母符号,在微信中实现富文本效果
20
20
  - **AES-256-CBC 加密** — 完整的微信回调加密/解密,包含 SHA-1 签名验证和 PKCS#7 填充校验
21
- - **Webhook + 轮询兜底** — HTTP webhook 服务器接收实时回调,同时提供 30 秒轮询兜底机制保证可靠性;内置请求体大小限制、方法校验和错误响应
21
+ - **Webhook + 轮询兜底** — webhook 处理器注册在框架共享网关上接收实时回调,同时提供 30 秒轮询兜底机制保证可靠性
22
22
  - **动态客服账号发现** — 客服账号 ID(open_kfid)从 webhook 回调中自动发现,支持启用/禁用/删除生命周期管理
23
23
  - **基于游标的增量同步** — 每个客服账号独立持久化同步游标,使用原子文件写入保证崩溃安全
24
24
  - **Access Token 自动缓存** — Token 在内存中以哈希键缓存,过期前 5 分钟自动刷新,Token 过期时自动重试
25
25
  - **多客服账号隔离** — 每个客服账号拥有独立的会话、游标和路由上下文,通过 per-kfId 互斥锁隔离处理
26
- - **DM 策略控制** — 可配置的访问控制模式:`open`(开放)、`allowlist`(白名单),包含安全适配器。`pairing`(配对)模式尚未实现。
26
+ - **DM 策略控制** — 可配置的访问控制模式:`open`(开放)、`allowlist`(白名单)或 `pairing`(配对),包含安全适配器
27
27
  - **文本分块** — 自动按微信 2000 字符消息限制拆分长回复,并声明 chunker 供框架集成
28
28
  - **会话限制感知** — 检测并优雅处理微信 48 小时回复窗口和 5 条消息限制
29
29
  - **竞态条件安全** — per-kfId 互斥锁和 msgid 去重,防止消息重复处理
30
30
  - **仿真回复延迟** — 可配置的打字延迟模拟,营造自然的对话节奏
31
- - **优雅关停** — 响应中止信号,带前置检查守卫,干净地停止 webhook 服务器和轮询
31
+ - **优雅关停** — 响应中止信号,带前置检查守卫,干净地停止轮询循环
32
32
 
33
33
  ## 前提条件
34
34
 
35
35
  1. 一个**企业微信账号**,且拥有管理员权限 — [注册地址](https://work.weixin.qq.com/)
36
36
  2. 至少一个**客服账号**(在企业微信的「微信客服」模块中创建)
37
- 3. 一个**公网可访问的 URL**,用于接收回调 — 可使用 [ngrok](https://ngrok.com/)、[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 或有公网 IP 的服务器
37
+ 3. 一个**公网可访问的 URL**,用于接收回调 — 可使用 [Tailscale Funnel](https://docs.openclaw.ai/gateway/tailscale#tailscale)(推荐,OpenClaw Gateway 内置支持)、[ngrok](https://ngrok.com/)、[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 或有公网 IP 的服务器
38
38
  4. 已安装并运行 **OpenClaw Gateway**(`openclaw gateway start`)
39
39
 
40
40
  微信客服 API 有**两种接入方式**,请根据实际情况选择:
@@ -204,9 +204,8 @@ channels:
204
204
  appSecret: "your-app-secret-here" # 应用密钥(自建应用 Secret 或微信客服 Secret)
205
205
  token: "your-callback-token" # 回调 Token
206
206
  encodingAESKey: "your-43-char-key" # 回调 EncodingAESKey(43 位字符)
207
- webhookPort: 9999 # Webhook 服务端口(默认:9999)
208
207
  webhookPath: "/wechat-kf" # Webhook URL 路径(默认:/wechat-kf)
209
- dmPolicy: "open" # 访问控制:open | allowlistpairing 尚未实现)
208
+ dmPolicy: "open" # 访问控制:open | allowlist | pairing | disabled
210
209
  # allowFrom: # 仅在 dmPolicy 为 allowlist 时使用
211
210
  # - "external_userid_1"
212
211
  # - "external_userid_2"
@@ -221,9 +220,8 @@ channels:
221
220
  | `appSecret` | string | **是** | — | 自建应用密钥或微信客服 Secret |
222
221
  | `token` | string | **是** | — | Webhook 回调 Token |
223
222
  | `encodingAESKey` | string | **是** | — | 43 位 AES 加密密钥 |
224
- | `webhookPort` | integer | 否 | `9999` | Webhook HTTP 服务端口 |
225
223
  | `webhookPath` | string | 否 | `/wechat-kf` | Webhook 回调 URL 路径 |
226
- | `dmPolicy` | string | 否 | `"open"` | `open`(开放)/ `allowlist`(白名单)。`pairing` 尚未实现 |
224
+ | `dmPolicy` | string | 否 | `"open"` | `open`(开放)/ `allowlist`(白名单)/ `pairing`(配对)/ `disabled`(禁用) |
227
225
  | `allowFrom` | string[] | 否 | `[]` | 允许的 external_userid 列表(dmPolicy 为 `allowlist` 时使用) |
228
226
 
229
227
  ## 验证
@@ -232,13 +230,17 @@ channels:
232
230
  ```bash
233
231
  openclaw gateway start
234
232
  ```
235
- 2. 暴露 webhook 端口(如果不在公网服务器上):
233
+ 2. Gateway 暴露到公网(如果不在公网服务器上)。方式 A — Tailscale Funnel(内置支持):
236
234
  ```bash
237
- ngrok http 9999
235
+ openclaw gateway --tailscale funnel --auth password
238
236
  ```
239
- 3. 复制 HTTPS URL(如 `https://xxxx.ngrok-free.app`),在企业微信中设置回调地址:
237
+ 方式 B ngrok
238
+ ```bash
239
+ ngrok http <gateway-port>
240
+ ```
241
+ 3. 复制 HTTPS URL(如 `https://your-machine.tail1234.ts.net` 或 `https://xxxx.ngrok-free.app`),在企业微信中设置回调地址:
240
242
  ```
241
- https://xxxx.ngrok-free.app/wechat-kf
243
+ https://<your-public-host>/wechat-kf
242
244
  ```
243
245
  4. 企业微信发送 GET 验证请求 — 插件自动解密 `echostr` 并响应
244
246
  5. 从微信中通过客服链接发送测试消息,确认 Agent 正常回复
@@ -270,15 +272,18 @@ Agent 可以使用 `message` 工具发送消息:
270
272
  | 视频 | 下载为 MP4 格式,保存为媒体附件 |
271
273
  | 文件 | 下载保存为媒体附件 |
272
274
  | 位置 | 转换为文本:`[位置: 名称 地址]` |
273
- | 链接 | 转换为文本:`[链接: 标题 URL]` |
274
- | 小程序 | 转换为文本,包含标题和 appid |
275
+ | 链接 | 转换为文本:`[链接: 标题 URL]`(含 desc、pic_url)|
276
+ | 小程序 | 转换为文本,包含标题、appid 和 pagepath |
275
277
  | 视频号 | 转换为文本,包含类型、昵称、标题 |
278
+ | 视频号商品 | 转换为文本,包含商品信息 |
279
+ | 视频号订单 | 转换为文本,包含订单信息 |
280
+ | 笔记 | 转换为文本,包含笔记内容 |
276
281
  | 名片 | 转换为文本,包含 userid |
277
282
  | 合并转发消息 | 解析并展开为可读文本 |
278
283
 
279
284
  ### 支持的出站消息类型
280
285
 
281
- 文本、图片、语音、视频、文件和链接消息。本地文件在发送前会自动上传到微信临时素材存储。
286
+ 文本、图片、语音、视频、文件、链接、位置、小程序、菜单、名片、视频号文章和原始 JSON 消息(`[[wechat_raw:...]]`)。富消息类型通过 `[[wechat_*:...]]` 文本指令发送。所有来源的媒体(本地文件、HTTP URL、file:// URI)通过框架 loadWebMedia 加载后自动上传到微信临时素材存储。
282
287
 
283
288
  ## 架构
284
289
 
@@ -311,9 +316,9 @@ Agent 可以使用 `message` 工具发送消息:
311
316
  | +-----------+-----------+
312
317
  | v
313
318
  | send-utils.ts
314
- | formatText, detectMediaType
315
- | uploadAndSendMedia
316
- | downloadMediaFromUrl
319
+ | formatText, mediaKindToWechatType
320
+ | detectMediaType, uploadAndSendMedia
321
+ | resolveThumbMediaId
317
322
  | v
318
323
  +--- send_msg API <-- api.ts
319
324
  (JSON)
@@ -323,21 +328,22 @@ Agent 可以使用 `message` 工具发送消息:
323
328
 
324
329
  | 模块 | 职责 |
325
330
  | --------------------- | ------------------------------------------------------------------------------------- |
326
- | `webhook.ts` | HTTP 服务器 GET 验证、POST 事件处理、大小/方法守卫 |
331
+ | `webhook.ts` | HTTP 处理器(框架网关)— GET 验证、POST 事件处理、大小/方法守卫 |
327
332
  | `crypto.ts` | AES-256-CBC 加密/解密、SHA-1 签名验证、PKCS#7 填充校验 |
328
333
  | `token.ts` | Access Token 缓存,哈希键存储,自动刷新 |
329
- | `api.ts` | 企业微信 API 客户端(sync_msg、send_msg、媒体上传/下载),Token 过期自动重试 |
334
+ | `api.ts` | 企业微信 API 客户端(sync_msg、send_msg、sendRawMessage、媒体上传/下载),Token 过期自动重试 |
330
335
  | `accounts.ts` | 动态客服账号发现、解析、启用/禁用/删除生命周期 |
331
336
  | `bot.ts` | 消息同步(互斥锁 + 去重)、DM 策略检查、事件处理、Agent 分发 |
332
- | `monitor.ts` | Webhook + 轮询生命周期管理,AbortSignal 守卫 |
337
+ | `monitor.ts` | 共享上下文管理器(setSharedContext/getSharedContext/waitForSharedContext/clearSharedContext)|
333
338
  | `reply-dispatcher.ts` | 插件内部流式回复投递,包含分块、格式化、延迟 |
334
339
  | `outbound.ts` | 框架驱动的出站适配器,声明 chunker |
335
- | `send-utils.ts` | 共享出站工具(formatText、detectMediaType、uploadAndSendMedia、downloadMediaFromUrl) |
336
- | `chunk-utils.ts` | 文本分块,支持自然边界拆分(换行、空格、硬截断) |
340
+ | `send-utils.ts` | 共享出站工具(formatText、mediaKindToWechatType、detectMediaType、uploadAndSendMedia、resolveThumbMediaId) |
341
+ | `wechat-kf-directives.ts` | `[[wechat_*:...]]` 指令解析器,用于 agent 回复中的富文本消息类型 |
337
342
  | `constants.ts` | 共享常量(WECHAT_TEXT_CHUNK_LIMIT、超时、错误码) |
338
343
  | `fs-utils.ts` | 原子文件操作(临时文件 + 重命名) |
339
344
  | `unicode-format.ts` | Markdown 转 Unicode 数学字母符号格式化 |
340
345
  | `channel.ts` | ChannelPlugin 接口,包含安全适配器(resolveDmPolicy、collectWarnings) |
346
+ | `config-schema.ts` | wechat-kf 渠道配置的 JSON Schema 校验 |
341
347
  | `runtime.ts` | OpenClaw 运行时引用持有 |
342
348
 
343
349
  ### 状态持久化
@@ -353,7 +359,7 @@ Agent 可以使用 `message` 工具发送消息:
353
359
  - **5 条消息限制** — 在用户发送下一条消息前,最多只能发送 5 条回复。插件检测此限制并相应记录日志。
354
360
  - **语音格式** — 入站语音消息为 AMR 格式;转录取决于 OpenClaw Agent 的媒体处理能力。
355
361
  - **仅临时素材** — 上传的媒体使用微信临时素材 API(3 天有效期)。未实现永久素材上传。
356
- - **单一 webhook 端点** — 所有客服账号共享同一个 webhook 端口和路径。这是设计如此(企业微信为每个企业发送所有回调到同一个 URL)。
362
+ - **单一 webhook 端点** — 所有客服账号共享同一个 webhook 路径。这是设计如此(企业微信为每个企业发送所有回调到同一个 URL)。
357
363
  - **不支持群聊** — 微信客服仅支持一对一会话。插件仅支持 `direct` 聊天类型。
358
364
  - **IP 白名单漂移** — 如果服务器公网 IP 变化,API 调用将静默失败。请监控 IP 或使用静态 IP。
359
365
 
@@ -369,7 +375,7 @@ pnpm run build
369
375
  # 类型检查
370
376
  pnpm run typecheck
371
377
 
372
- # 运行测试(16 个文件,363 个测试)
378
+ # 运行测试(17 个文件,约 600 个测试)
373
379
  pnpm test
374
380
 
375
381
  # 监听模式
package/dist/index.d.ts CHANGED
@@ -1,27 +1,17 @@
1
1
  /**
2
2
  * WeChat KF (微信客服) OpenClaw Channel Plugin
3
3
  */
4
- import { wechatKfPlugin } from "./src/channel.js";
5
- import { type PluginRuntime } from "./src/runtime.js";
6
- export { sendTextMessage, syncMessages } from "./src/api.js";
4
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
5
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
6
+ export { sendBusinessCardMessage, sendCaLinkMessage, sendLocationMessage, sendMiniprogramMessage, sendMsgMenuMessage, sendTextMessage, syncMessages, } from "./src/api.js";
7
7
  export { wechatKfPlugin } from "./src/channel.js";
8
8
  export { computeSignature, decrypt, encrypt, verifySignature } from "./src/crypto.js";
9
9
  export { getAccessToken } from "./src/token.js";
10
- type OpenClawPluginApi = {
11
- runtime: PluginRuntime;
12
- registerChannel: (opts: {
13
- plugin: typeof wechatKfPlugin;
14
- }) => void;
15
- };
16
10
  declare const plugin: {
17
11
  id: string;
18
12
  name: string;
19
13
  description: string;
20
- configSchema: {
21
- type: string;
22
- additionalProperties: boolean;
23
- properties: {};
24
- };
25
- register(api: OpenClawPluginApi): void;
14
+ configSchema: ReturnType<typeof emptyPluginConfigSchema>;
15
+ register: (api: OpenClawPluginApi) => void;
26
16
  };
27
17
  export default plugin;
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * WeChat KF (微信客服) OpenClaw Channel Plugin
3
3
  */
4
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
4
5
  import { wechatKfPlugin } from "./src/channel.js";
5
6
  import { setRuntime } from "./src/runtime.js";
6
- export { sendTextMessage, syncMessages } from "./src/api.js";
7
+ import { handleWechatKfWebhook } from "./src/webhook.js";
8
+ export { sendBusinessCardMessage, sendCaLinkMessage, sendLocationMessage, sendMiniprogramMessage, sendMsgMenuMessage, sendTextMessage, syncMessages, } from "./src/api.js";
7
9
  export { wechatKfPlugin } from "./src/channel.js";
8
10
  export { computeSignature, decrypt, encrypt, verifySignature } from "./src/crypto.js";
9
11
  export { getAccessToken } from "./src/token.js";
@@ -11,13 +13,11 @@ const plugin = {
11
13
  id: "wechat-kf",
12
14
  name: "WeChat KF",
13
15
  description: "WeChat Customer Service (企业微信客服) channel plugin",
14
- // Plugin-level config schema (not channel-level).
15
- // Channel config is handled via openclaw.plugin.json configSchema
16
- // and the runtime schema in channel.ts → configSchema.
17
- configSchema: { type: "object", additionalProperties: false, properties: {} },
16
+ configSchema: emptyPluginConfigSchema(),
18
17
  register(api) {
19
18
  setRuntime(api.runtime);
20
19
  api.registerChannel({ plugin: wechatKfPlugin });
20
+ api.registerHttpHandler(handleWechatKfWebhook);
21
21
  },
22
22
  };
23
23
  export default plugin;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAsB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAOhD,MAAM,MAAM,GAAG;IACb,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,iDAAiD;IAC9D,kDAAkD;IAClD,kEAAkE;IAClE,uDAAuD;IACvD,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE;IAC7E,QAAQ,CAAC,GAAsB;QAC7B,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxB,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;IAClD,CAAC;CACF,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,eAAe,EACf,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,MAAM,GAMR;IACF,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,iDAAiD;IAC9D,YAAY,EAAE,uBAAuB,EAAE;IACvC,QAAQ,CAAC,GAAsB;QAC7B,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxB,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;QAChD,GAAG,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,CAAC;IACjD,CAAC;CACF,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -5,7 +5,8 @@
5
5
  * Each openKfId becomes an independent accountId (like Telegram chat groups).
6
6
  * Enterprise credentials (corpId, appSecret, token, encodingAESKey) are shared.
7
7
  */
8
- import type { OpenClawConfig, ResolvedWechatKfAccount, WechatKfConfig } from "./types.js";
8
+ import type { OpenClawConfig } from "openclaw/plugin-sdk";
9
+ import type { ResolvedWechatKfAccount, WechatKfConfig } from "./types.js";
9
10
  export declare function setStateDir(dir: string): void;
10
11
  export declare function getChannelConfig(cfg: OpenClawConfig): WechatKfConfig;
11
12
  /** Register a dynamically discovered kfid */
@@ -5,21 +5,56 @@
5
5
  * Each openKfId becomes an independent accountId (like Telegram chat groups).
6
6
  * Enterprise credentials (corpId, appSecret, token, encodingAESKey) are shared.
7
7
  */
8
+ import { readFileSync } from "node:fs";
8
9
  import { mkdir, readFile } from "node:fs/promises";
9
10
  import { join } from "node:path";
11
+ import { CHANNEL_ID, DEFAULT_WEBHOOK_PATH, DISABLED_KFIDS_FILE, defaultStateDir, formatError, KFIDS_FILE, logTag, } from "./constants.js";
10
12
  import { atomicWriteFile } from "./fs-utils.js";
11
- const DEFAULT_PORT = 9999;
12
- const DEFAULT_PATH = "/wechat-kf";
13
+ import { getSharedContext } from "./monitor.js";
13
14
  /** In-memory set of discovered kfids */
14
15
  const discoveredKfIds = new Set();
15
16
  /** In-memory set of disabled kfids (persisted to disk) */
16
17
  const disabledKfIds = new Set();
17
18
  let stateDir = null;
19
+ let kfIdsPreloaded = false;
20
+ /** Synchronously preload persisted kfIds so listAccountIds returns them before loadKfIds runs */
21
+ function preloadKfIdsSync() {
22
+ if (kfIdsPreloaded)
23
+ return;
24
+ kfIdsPreloaded = true;
25
+ const dir = stateDir ?? defaultStateDir();
26
+ try {
27
+ const data = readFileSync(join(dir, KFIDS_FILE), "utf8");
28
+ const ids = JSON.parse(data);
29
+ if (Array.isArray(ids)) {
30
+ for (const id of ids)
31
+ discoveredKfIds.add(id);
32
+ }
33
+ }
34
+ catch (err) {
35
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
36
+ console.warn(`${logTag()} failed to preload kfids: ${formatError(err)}`);
37
+ }
38
+ }
39
+ try {
40
+ const data = readFileSync(join(dir, DISABLED_KFIDS_FILE), "utf8");
41
+ const ids = JSON.parse(data);
42
+ if (Array.isArray(ids)) {
43
+ for (const id of ids)
44
+ disabledKfIds.add(id);
45
+ }
46
+ }
47
+ catch (err) {
48
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
49
+ console.warn(`${logTag()} failed to preload disabled kfids: ${formatError(err)}`);
50
+ }
51
+ }
52
+ }
18
53
  export function setStateDir(dir) {
19
54
  stateDir = dir;
20
55
  }
21
56
  export function getChannelConfig(cfg) {
22
- return (cfg.channels?.["wechat-kf"] ?? {});
57
+ return (cfg.channels?.[CHANNEL_ID] ?? {});
23
58
  }
24
59
  /** Register a dynamically discovered kfid */
25
60
  export async function registerKfId(kfId) {
@@ -101,27 +136,32 @@ function resolveKfId(kfId) {
101
136
  /** Load persisted kfids from state dir */
102
137
  export async function loadKfIds(dir) {
103
138
  stateDir = dir;
139
+ kfIdsPreloaded = true;
104
140
  try {
105
- const data = await readFile(join(dir, "wechat-kf-kfids.json"), "utf8");
141
+ const data = await readFile(join(dir, KFIDS_FILE), "utf8");
106
142
  const ids = JSON.parse(data);
107
143
  if (Array.isArray(ids)) {
108
144
  for (const id of ids)
109
145
  discoveredKfIds.add(id);
110
146
  }
111
147
  }
112
- catch {
113
- // No persisted state yet, that's fine
148
+ catch (err) {
149
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
150
+ console.warn(`${logTag()} failed to load kfids: ${formatError(err)}`);
151
+ }
114
152
  }
115
153
  try {
116
- const data = await readFile(join(dir, "wechat-kf-disabled-kfids.json"), "utf8");
154
+ const data = await readFile(join(dir, DISABLED_KFIDS_FILE), "utf8");
117
155
  const ids = JSON.parse(data);
118
156
  if (Array.isArray(ids)) {
119
157
  for (const id of ids)
120
158
  disabledKfIds.add(id);
121
159
  }
122
160
  }
123
- catch {
124
- // No persisted disabled state yet, that's fine
161
+ catch (err) {
162
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
163
+ console.warn(`${logTag()} failed to load disabled kfids: ${formatError(err)}`);
164
+ }
125
165
  }
126
166
  }
127
167
  /** Persist kfids to state dir */
@@ -130,10 +170,10 @@ async function persistKfIds() {
130
170
  return;
131
171
  try {
132
172
  await mkdir(stateDir, { recursive: true });
133
- await atomicWriteFile(join(stateDir, "wechat-kf-kfids.json"), JSON.stringify(Array.from(discoveredKfIds)));
173
+ await atomicWriteFile(join(stateDir, KFIDS_FILE), JSON.stringify(Array.from(discoveredKfIds)));
134
174
  }
135
- catch {
136
- // Best effort
175
+ catch (err) {
176
+ getSharedContext()?.botCtx.log?.warn(`${logTag()} failed to persist kfids: ${formatError(err)}`);
137
177
  }
138
178
  }
139
179
  /** Persist disabled kfids to state dir */
@@ -142,16 +182,18 @@ async function persistDisabledKfIds() {
142
182
  return;
143
183
  try {
144
184
  await mkdir(stateDir, { recursive: true });
145
- await atomicWriteFile(join(stateDir, "wechat-kf-disabled-kfids.json"), JSON.stringify(Array.from(disabledKfIds)));
185
+ await atomicWriteFile(join(stateDir, DISABLED_KFIDS_FILE), JSON.stringify(Array.from(disabledKfIds)));
146
186
  }
147
- catch {
148
- // Best effort
187
+ catch (err) {
188
+ getSharedContext()?.botCtx.log?.warn(`${logTag()} failed to persist disabled kfids: ${formatError(err)}`);
149
189
  }
150
190
  }
151
191
  export function listAccountIds(_cfg) {
152
- // Return discovered kfids as account ids, excluding disabled ones
192
+ // "default" is always first represents enterprise-level shared infrastructure.
193
+ // Real kfIds follow. When no kfIds are discovered yet, returns ["default"].
194
+ preloadKfIdsSync();
153
195
  const ids = getEnabledKfIds();
154
- return ids.length > 0 ? ids : ["default"];
196
+ return ["default", ...ids];
155
197
  }
156
198
  /**
157
199
  * Recover the original case-sensitive kfId from the normalized (lowercased) accountId.
@@ -177,6 +219,7 @@ export function _reset() {
177
219
  discoveredKfIds.clear();
178
220
  disabledKfIds.clear();
179
221
  stateDir = null;
222
+ kfIdsPreloaded = false;
180
223
  }
181
224
  export function resolveAccount(cfg, accountId) {
182
225
  const config = getChannelConfig(cfg);
@@ -197,8 +240,7 @@ export function resolveAccount(cfg, accountId) {
197
240
  token,
198
241
  encodingAESKey,
199
242
  openKfId: recoverOriginalKfId(id),
200
- webhookPort: config.webhookPort ?? DEFAULT_PORT,
201
- webhookPath: config.webhookPath ?? DEFAULT_PATH,
243
+ webhookPath: config.webhookPath ?? DEFAULT_WEBHOOK_PATH,
202
244
  config,
203
245
  };
204
246
  }
@@ -1 +1 @@
1
- {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../src/accounts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../src/accounts.ts"],"names":[],"mappings":"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"}