@@ -4,22 +4,23 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44
55## Project Overview
66
7- This is an ** OpenClaw Channel Plugin** for WeCom (企业微信 / WeChat Work). It enables AI bot integration with enterprise WeChat through a dual -mode architecture.
7+ This is an ** OpenClaw Channel Plugin** for WeCom (企业微信 / WeChat Work). It enables AI bot integration with enterprise WeChat through a multi -mode architecture.
88
9- - ** Package** : ` @mocrane /wecom `
9+ - ** Package** : ` @partme.ai /wecom `
1010- ** Type** : ES Module (NodeNext)
1111- ** Entry** : ` index.ts `
1212
1313## Architecture
1414
15- ### Dual -Mode Design (Bot + Agent)
15+ ### Multi -Mode Design (WebSocket + Webhook Bot + Agent)
1616
17- The plugin implements a unique dual-mode architecture :
17+ The plugin implements three connection modes :
1818
1919| Mode | Purpose | Webhook Path | Capabilities |
2020| ------| ---------| --------------| --------------|
21- | ** Bot** (智能体) | Real-time streaming chat | ` /wecom ` , ` /wecom/bot ` | Streaming responses, low latency, text/image only |
22- | ** Agent** (自建应用) | Fallback & broadcast | ` /wecom/agent ` | File sending, broadcasts, long tasks (>6min) |
21+ | ** WebSocket** (Bot 长连接) | Real-time streaming chat | N/A (WS) | Streaming responses, low latency |
22+ | ** Webhook** (Bot URL 回调) | HTTP callback for restricted networks | ` /wecom ` , ` /wecom/bot ` , ` /plugins/wecom/bot ` | Streaming via ` response_url ` , 6min window, Agent fallback |
23+ | ** Agent** (自建应用) | Fallback & broadcast | ` /wecom/agent ` , ` /plugins/wecom/agent ` | File sending, broadcasts, long tasks (>6min) |
2324
2425** Key Design Principle** : Bot is preferred for conversations; Agent is used as fallback when Bot cannot deliver (files, timeouts) or for proactive broadcasts.
2526
@@ -29,32 +30,67 @@ The plugin implements a unique dual-mode architecture:
2930index.ts # Plugin entry - registers channel and HTTP handlers
3031src/
3132 channel.ts # ChannelPlugin implementation, lifecycle management
32- monitor.ts # Core webhook handler, message flow, stream state
33+ monitor.ts # WebSocket message processing + backward-compat webhook handler
3334 runtime.ts # Runtime state singleton
3435 http.ts # HTTP client with undici + proxy support
3536 crypto.ts # AES-CBC encryption/decryption for webhooks
3637 media.ts # Media file download/decryption
3738 outbound.ts # Outbound message adapter
3839 target.ts # Target resolution (user/party/tag/chat)
3940 dynamic-agent.ts # Dynamic agent routing (per-user/per-group isolation)
41+ gateway-monitor.ts # Account lifecycle: dispatch WS / webhook gateway / agent registration
42+ ws-adapter.ts # WebSocket client adapter (@wecom/aibot-node-sdk)
43+
44+ # ── Webhook mode (integrated from @wecom/wecom-openclaw-plugin) ──
45+ webhook/
46+ index.ts # Re-exports: handler, gateway, state, helpers, types
47+ handler.ts # HTTP GET/POST handler with multi-account signature matching
48+ gateway.ts # Lifecycle: start/stop webhook targets, prune timer
49+ monitor.ts # startAgentForStream() — message processing, Agent dispatch, deliver
50+ state.ts # StreamStore + ActiveReplyStore + WebhookMonitorState (singleton)
51+ helpers.ts # buildInboundBody, processInboundMessage, buildFallbackPrompt, MIME detect
52+ types.ts # WebhookInboundMessage, StreamState, PendingInbound, WecomWebhookTarget
53+ target.ts # Path-indexed target registry (register/unregister/resolve)
54+ http.ts # undici fetch wrapper with ProxyAgent
55+ media.ts # AES-256-CBC media decryption (decryptWecomMediaWithMeta)
56+ command-auth.ts # DM policy + command authorization
57+ video-frame.ts # ffmpeg first-frame extraction for video messages
58+
4059 agent/
4160 api-client.ts # WeCom API client with AccessToken caching
4261 handler.ts # XML webhook handler for Agent mode
62+ webhook.ts # Agent HTTP handler (GET echostr verify, POST XML decrypt)
4363 config/
4464 schema.ts # Zod schemas for configuration
65+ accounts.ts # Account resolution, mode detection, conflict checking
66+ network.ts # Proxy resolution chain
67+ routing.ts # Fail-closed routing policy
4568 monitor/
46- state.ts # StreamStore and ActiveReplyStore with TTL pruning
47- types/constants.ts # API endpoints and limits
69+ state.ts # StreamStore and ActiveReplyStore (WebSocket mode)
70+ types.ts # StreamState, PendingInbound types
71+ mcp/ # wecom_mcp tool: JSON-RPC over Streamable HTTP
72+ crypto/ # AES-256-CBC, SHA1 signature, XML encrypt/decrypt
73+ media/ # Media uploader, constants
74+ shared/ # XML parser, command auth utilities
75+ types/ # TypeScript types: config, account, message, constants
76+ compat/ # SDK version compatibility shim
4877```
4978
5079### Stream State Management
5180
52- The plugin uses a sophisticated stream state system ( ` src/monitor/state.ts ` ) :
81+ The plugin uses sophisticated stream state systems for both modes :
5382
83+ ** WebSocket mode** (` src/monitor/state.ts ` ):
5484- ** StreamStore** : Manages message streams with 6-minute timeout window
5585- ** ActiveReplyStore** : Tracks ` response_url ` for proactive pushes
5686- ** Pending Queue** : Debounces rapid messages (500ms default)
5787- ** Message Deduplication** : Uses ` msgid ` to prevent duplicate processing
88+ - Exports ` getSessionChatInfo() ` for MCP tool context (preserves original-case chatId)
89+
90+ ** Webhook mode** (` src/webhook/state.ts ` ):
91+ - Separate singleton (` WebhookMonitorState ` ) with same StreamStore + ActiveReplyStore pattern
92+ - Additional ` conversationState ` /` batchKey ` /` ackStream ` queue semantics for multi-message merge
93+ - Used by ` webhook/gateway.ts ` and ` webhook/monitor.ts `
5894
5995### Token Management
6096
@@ -67,23 +103,23 @@ Agent mode uses automatic AccessToken caching (`src/agent/api-client.ts`):
67103
68104### Testing
69105
70- This project uses ** Vitest** . Tests extend from a base config at ` ../../vitest.config.ts ` :
106+ This project uses ** Vitest** :
71107
72108``` bash
73109# Run all tests
74- npx vitest --config vitest.config.ts
110+ npx vitest --config vitest.config.ts run
75111
76112# Run specific test file
77- npx vitest --config vitest.config.ts src/crypto.test.ts
113+ npx vitest --config vitest.config.ts run src/crypto.test.ts
78114
79115# Run tests matching pattern
80- npx vitest --config vitest.config.ts --testNamePattern= " should encrypt"
116+ npx vitest --config vitest.config.ts run -t " should encrypt"
81117
82118# Watch mode
83119npx vitest --config vitest.config.ts --watch
84120```
85121
86- Test files are located alongside source files with ` .test.ts ` suffix:
122+ Test files are located alongside source files with ` .test.ts ` suffix (16 test files total) :
87123- ` src/crypto.test.ts `
88124- ` src/monitor.integration.test.ts `
89125- ` src/monitor/state.queue.test.ts `
@@ -97,7 +133,7 @@ npx tsc --noEmit
97133
98134### Build
99135
100- The plugin is loaded directly as TypeScript by OpenClaw. No build step is required for development, but type checking is recommended.
136+ The plugin is loaded directly as TypeScript by OpenClaw. No build step is required for development, but type checking is recommended. For distribution, use ` npm pack ` .
101137
102138## Configuration Schema
103139
@@ -107,6 +143,11 @@ Configuration is validated via Zod (`src/config/schema.ts`):
107143{
108144 enabled : boolean ,
109145 bot : {
146+ connectionMode : ' websocket' | ' webhook' ,
147+ // WebSocket mode:
148+ botId : string ,
149+ secret : string ,
150+ // Webhook mode:
110151 token : string , // Bot webhook token
111152 encodingAESKey : string , // AES encryption key
112153 receiveId : string ? , // Optional receive ID
@@ -123,11 +164,14 @@ Configuration is validated via Zod (`src/config/schema.ts`):
123164 welcomeText : string ? ,
124165 dm : { policy , allowFrom }
125166 },
167+ accounts : { // Multi-account (matrix mode)
168+ main : { bot : {... }, agent : {... } }
169+ },
126170 network : {
127171 egressProxyUrl : string ? // For dynamic IP scenarios
128172 },
129173 media : {
130- maxBytes: number ? // Default 25MB
174+ maxBytes: number ? // Default 20MB
131175 },
132176 dynamicAgents : {
133177 enabled: boolean ? // Enable per-user/per-group agents
@@ -160,23 +204,23 @@ Dynamic agents are automatically added to `agents.list` in the config file.
160204
161205### Webhook Security
162206
163- - **Signature Verification**: HMAC-SHA256 with token
164- - **Encryption**: AES-CBC with PKCS#7 padding (32-byte blocks)
165- - **Paths**: ` / wecom ` (legacy), ` / wecom / bot ` , ` / wecom / agent `
207+ - **Signature Verification**: SHA1(token, timestamp, nonce, encrypt) via ` @ wecom / aibot - node - sdk ` WecomCrypto
208+ - **Encryption**: AES-256- CBC with PKCS#7 padding (32-byte blocks)
209+ - **Paths**: ` / wecom ` (legacy), ` / wecom / bot ` (bot) , ` / wecom / agent ` (agent), ` / plugins / wecom / bot /* `, `/plugins/wecom/agent/* `
166210
167211### Timeout Handling
168212
169- Bot mode has a 6-minute window (360s) for streaming responses. The plugin:
213+ Bot webhook mode has a 6-minute window (360s) for streaming responses. The plugin:
170214- Tracks deadline: `createdAt + 6 * 60 * 1000`
171215- Switches to Agent fallback at `deadline - 30s` margin
172216- Sends DM via Agent for remaining content
173217
174218### Media Handling
175219
176- - **Inbound**: Decrypts WeCom encrypted media URLs
220+ - **Inbound**: Decrypts WeCom encrypted media URLs (AES-256-CBC)
177221- **Outbound Images**: Base64 encoded via `msg_item` in stream
178222- **Outbound Files**: Requires Agent mode, sent via `media/upload` + `message/send`
179- - **Max Size**: 25MB default (configurable via ` channels .wecom .media .maxBytes ` )
223+ - **Max Size**: 20MB default (configurable via `channels.wecom.media.maxBytes`)
180224
181225### Proxy Support
182226
@@ -186,9 +230,13 @@ For servers with dynamic IPs (common error: `60020 not allow to access from your
186230openclaw config set channels.wecom.network.egressProxyUrl "http://proxy.company.local:3128"
187231```
188232
233+ ### message tool denial
234+
235+ `buildCfgForDispatch()` in `webhook/helpers.ts` adds `"message"` to `tools.deny` to prevent Agent from bypassing Bot delivery via the message tool.
236+
189237## Testing Notes
190238
191- - Tests use Vitest with ` .. / .. / vitest . config . ts ` as base
239+ - Tests use Vitest with co-located test files
192240- Integration tests mock WeCom API responses
193241- Crypto tests verify AES encryption round-trips
194242- Monitor tests cover stream state transitions and queue behavior
@@ -197,7 +245,7 @@ openclaw config set channels.wecom.network.egressProxyUrl "http://proxy.company.
197245
198246### Adding a New Message Type Handler
199247
200- 1. Update ` buildInboundBody ()` in ` src / monitor .ts ` to parse the message
248+ 1. Update `buildInboundBody()` in `src/webhook/helpers.ts` or `src/ monitor.ts` to parse the message
2012492. Add type definitions in `src/types/message.ts`
2022503. Update `processInboundMessage()` if media handling is needed
203251
@@ -225,14 +273,17 @@ streamStore.updateStream(streamId, (state) => {
225273
226274## Dependencies
227275
276+ - `@wecom/aibot-node-sdk`: Official WeCom Bot WebSocket SDK + crypto
228277- `undici`: HTTP client with proxy support
229278- `fast-xml-parser`: XML parsing for Agent callbacks
279+ - `file-type`: MIME type detection from file buffers
230280- `zod`: Configuration validation
231281- `openclaw`: Peer dependency (>=2026.2.24)
232282
233283## WeCom API Endpoints Used
234284
235285- `GET_TOKEN`: `https://qyapi.weixin.qq.com/cgi-bin/gettoken`
236286- `SEND_MESSAGE`: `https://qyapi.weixin.qq.com/cgi-bin/message/send`
287+ - `SEND_APPCHAT`: `https://qyapi.weixin.qq.com/cgi-bin/appchat/send`
237288- `UPLOAD_MEDIA`: `https://qyapi.weixin.qq.com/cgi-bin/media/upload`
238289- `DOWNLOAD_MEDIA`: `https://qyapi.weixin.qq.com/cgi-bin/media/get`
0 commit comments