-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat(channels): add WeCom intelligent robot channel #6224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
de769d5
f32f098
b2dbd5e
6100fd1
1f86d11
30076d4
067d74c
74563a1
3c14ef9
ccd4f33
4588045
5a2abaf
547f67f
46a2efc
6c66094
9ccf0c9
006023d
645406b
e6a0e3a
36ea9f9
3e923e1
bc95cb9
92d129c
7016ea5
5ad0c9c
643555d
5a9c9b0
7947aff
7dea3a7
9ba9cc9
8ab3343
05936c1
d9a9b84
6e25684
c3350a1
f49032b
40ca360
ebb6e63
08dbc93
f58a17d
d9bb953
f091c36
907c232
8bc756d
003c985
3ba8e95
f03c008
f1998d1
8a4b6bd
2bfe8b3
dcd4170
8f56b3f
81d6d1c
c988e15
951c1b5
bfaf08f
3dbcc91
0249240
60d191b
6e7fab1
7661af0
2380b64
e2da90a
0fa9f03
e0ae776
b650c4c
0fda2a8
4ada37e
7d634af
9b5a3cd
5f4b2ab
8d7c1b6
75bf5b9
71fc860
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # WeCom (Enterprise WeChat) | ||
|
|
||
| This guide covers setting up Qwen Code with a WeCom intelligent robot (企业微信智能机器人). | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - A WeCom organization account | ||
| - A WeCom intelligent robot created in API mode | ||
| - The robot's Bot ID and Secret | ||
|
|
||
| ## Creating the Robot | ||
|
|
||
| 1. Open the WeCom admin console and create an intelligent robot. | ||
| 2. Choose API mode. | ||
| 3. Copy the Bot ID and Secret. | ||
| 4. Add the robot to the direct chats or groups where it should be available. | ||
|
|
||
| The intelligent robot uses a WebSocket connection from Qwen Code to WeCom. You do not need a public callback URL, Token, EncodingAESKey, Corp ID, or Agent ID. | ||
|
|
||
| ## Configuration | ||
|
|
||
| Add the channel to `~/.qwen/settings.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "channels": { | ||
| "my-wecom": { | ||
| "type": "wecom", | ||
| "botId": "$WECOM_BOT_ID", | ||
| "secret": "$WECOM_SECRET", | ||
| "senderPolicy": "allowlist", | ||
| "allowedUsers": ["zhangsan"], | ||
| "sessionScope": "user", | ||
| "cwd": "/path/to/your/project", | ||
| "instructions": "You are a concise coding assistant responding via WeCom.", | ||
| "groupPolicy": "open", | ||
| "groups": { | ||
| "*": { "requireMention": true } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Set the credentials as environment variables: | ||
|
|
||
| ```bash | ||
| export WECOM_BOT_ID=<your-bot-id> | ||
| export WECOM_SECRET=<your-secret> | ||
| ``` | ||
|
|
||
| Or define them in the `env` section of `settings.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "env": { | ||
| "WECOM_BOT_ID": "your-bot-id", | ||
| "WECOM_SECRET": "your-secret" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Running | ||
|
|
||
| ```bash | ||
| qwen channel start my-wecom | ||
| ``` | ||
|
|
||
| Open WeCom and send a message to the intelligent robot. | ||
|
|
||
| ## Access Control | ||
|
|
||
| `senderPolicy` works the same way as other IM channels: | ||
|
|
||
| - `allowlist`: only users in `allowedUsers` can use the bot. This is the recommended enterprise default. | ||
| - `pairing`: users must pair before using the bot. | ||
| - `open`: anyone who can message the robot can use it. | ||
|
|
||
| For groups, set `groupPolicy` to `"allowlist"` or `"open"`. By default, group messages require a mention through `"requireMention": true`. | ||
|
|
||
| When the WeCom SDK includes explicit mention metadata, Qwen Code uses it for this gate. If no mention metadata is present, the channel treats delivered group messages as mentioned, matching WeCom intelligent robot API mode where group delivery is expected to be scoped by WeCom. | ||
|
|
||
| ## Images and Files | ||
|
|
||
| Users can send text, voice messages with transcription, images, mixed text plus images, files, and videos. Images are passed to the agent as image attachments. Files and videos are downloaded to temporary local paths so the agent can read them with file tools. | ||
|
|
||
| Assistant responses are sent as WeCom markdown. To send a local image generated by the agent, include one marker outside code blocks: | ||
|
|
||
| ```text | ||
| [IMAGE: /absolute/path/to/image.png] | ||
| ``` | ||
|
|
||
| For safety, local image paths must be inside the channel file directory under the system temporary directory, such as `/tmp/channel-files/...`. Generic file, video, and voice upload markers are ignored because model-produced file paths could otherwise upload arbitrary workspace files. | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Bot does not connect | ||
|
|
||
| - Verify the Bot ID and Secret. | ||
| - Make sure the robot is created in API mode. | ||
| - Check that the environment variables are available in the shell running `qwen channel start`. | ||
|
|
||
| ### Bot does not respond in groups | ||
|
|
||
| - Check `groupPolicy`. | ||
| - Mention the bot unless the group config sets `"requireMention": false`. | ||
| - Confirm the robot has been added to the group. | ||
|
|
||
| ### Self-built application credentials do not work | ||
|
|
||
| This channel is for WeCom intelligent robots. Self-built application callback credentials such as Corp ID, Agent ID, Token, and EncodingAESKey are not used by this channel. | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2146,25 +2146,33 @@ export abstract class ChannelBase { | |
| return `${GROUP_HISTORY_CONTEXT_MARKER}\n${formatted.join('\n')}\n\n${CURRENT_MESSAGE_MARKER}\n${promptText}`; | ||
| } | ||
|
|
||
| async handleInbound(envelope: Envelope): Promise<void> { | ||
| // 1. Group gate: policy + allowlist + mention gating | ||
| protected async preflightInbound(envelope: Envelope): Promise<boolean> { | ||
| const groupResult = this.groupGate.check(envelope); | ||
| if (!groupResult.allowed) { | ||
| if (groupResult.reason === 'mention_required') { | ||
| this.recordPendingGroupHistory(envelope); | ||
| } | ||
| return; // silently drop — no pairing, no reply | ||
| return false; | ||
| } | ||
|
|
||
| // 2. Sender gate: allowlist / pairing / open | ||
| const result = this.gate.check(envelope.senderId, envelope.senderName); | ||
| if (!result.allowed) { | ||
| if (result.pairingCode !== undefined) { | ||
| await this.onPairingRequired(envelope.chatId, result.pairingCode); | ||
| } | ||
| return; | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] // Suggested fix: always await — await on a non-Promise is a no-op
async handleInbound(envelope: Envelope): Promise<void> {
if (!(await this.preflightInbound(envelope))) return;
await this.processInbound(envelope);
}This is already what WeComAdapter does in its direct call path. The micro-cost of |
||
| async handleInbound(envelope: Envelope): Promise<void> { | ||
| if (!(await this.preflightInbound(envelope))) return; | ||
|
|
||
| await this.processInbound(envelope); | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The WeCom adapter correctly calls preflight then processInbound separately (to interleave attachment download), but there is no runtime enforcement of this invariant. A future channel author would naturally call Suggested fix: Add a private |
||
| protected async processInbound(envelope: Envelope): Promise<void> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] A future adapter that calls Fix: Add a JSDoc warning: /**
* Process an inbound message AFTER preflight gates have passed.
* WARNING: This method does NOT run group gating, sender allowlisting,
* or pairing checks. Callers MUST run preflightInbound() first unless
* the envelope was already preflighted (e.g., collect-buffer drain).
*/ |
||
| // 3. Slash command handling — before session/agent routing | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical — future maintainer] The WeCom adapter calls Suggested approaches (any of these):
|
||
| const parsed = this.parseCommand(envelope.text); | ||
| if (parsed) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,29 @@ | ||||||
| { | ||||||
| "name": "@qwen-code/channel-wecom", | ||||||
| "version": "0.19.5", | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] This new workspace is versioned as
Suggested change
— GPT-5 Codex via Qwen Code /review |
||||||
| "description": "WeCom channel adapter for Qwen Code", | ||||||
| "type": "module", | ||||||
| "main": "dist/index.js", | ||||||
| "types": "dist/index.d.ts", | ||||||
| "exports": { | ||||||
| ".": { | ||||||
| "types": "./dist/index.d.ts", | ||||||
| "default": "./dist/index.js" | ||||||
| } | ||||||
| }, | ||||||
| "files": [ | ||||||
| "dist" | ||||||
| ], | ||||||
| "scripts": { | ||||||
| "build": "tsc --build", | ||||||
| "test": "vitest run", | ||||||
| "test:ci": "vitest run" | ||||||
| }, | ||||||
| "dependencies": { | ||||||
| "@qwen-code/channel-base": "file:../base", | ||||||
| "@wecom/aibot-node-sdk": "^1.0.7" | ||||||
| }, | ||||||
| "devDependencies": { | ||||||
| "typescript": "^5.0.0" | ||||||
| } | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] This sentence is now inverted relative to the adapter behavior.
WeComAdaptersetsisMentionedtofalsefor group messages when no explicit mention metadata is present, and the tests pin that missing metadata is dropped by the default mention gate. As written, users will expect those messages to reach the bot when they will be ignored unless they disablerequireMention.— GPT-5 Codex via Qwen Code /review