* fix(channels): manage DingTalk interactive card config * test(cli): cover nested channel object validation * fix(channels): harden nested management metadata * fix(channels): isolate invalid management descriptors * fix(channels): isolate invalid management metadata from channel runtime * fix(channels): reject reserved unknown keys in management config upserts * fix(channels): harden channel management validation and editor checks Reject management descriptors that lack a fields array at registration so broken plugins are stripped to unmanageable instead of being advertised as manageable and failing every upsert with an unmapped TypeError. Reserve the top-level "type" field key and require enum fields to declare at least one option, both of which the settings store could never accept. Treat whitespace-only number drafts as empty in the channel editor, consistent with the module's other emptiness checks. Also give the SDK descriptor mirror test a runtime wire-shape walk over the built-in catalog, add the parser's timeout rejection boundary, and restore the exact built-in catalog membership assertion. * fix(channels): validate management field shapes and editor bounds (#8517) * fix(channels): align management validation layers and pin gate behavior (#8517) Read envResolvable by truthiness in the settings store so it matches the registration gate and the editor, instead of rejecting the advertised environment references of untyped plugins. Fail closed at registration on non-finite exclusiveMinimum values, empty object property lists, and async validateConfig functions, all of which would otherwise advertise a field or save path that can never succeed. Strip invalid management metadata over a prototype-preserving copy so class-instance plugins keep their createChannel implementation. Move the unchanged-value preservation exemption ahead of the object shape rejection so a stored non-record value (for example a hand-written null) no longer locks every unrelated management edit of that channel. Clamp DingTalk question-card timeouts at the maximum setTimeout delay, since Node treats larger delays as one millisecond and would expire cards instantly. Pin the previously untested load-bearing behaviors: per-key previous threading in the recursive validation, the preservation exemption's precedence over nested required enforcement, nested "type" properties, depth-2 nesting rules, and the nested-only constraints of the daemon descriptor wire contract. * fix(channels): close reserved-key preservation gaps and pin gate behavior (#8517) * test(cli): tolerate IPv6-less hosts in serve ::1 bind tests (#8517) The self-hosted CI containers can have no IPv6 loopback, where the two runQwenServe tests that bind ::1 fail with EADDRNOTAVAIL. Probe the interfaces once and skip only the IPv6-dependent binds there; every assertion still runs on IPv6-capable hosts. * fix(channels): align descriptor type contracts with runtime validation (#8517) The registry already rejects object fields without a non-empty properties array and enums without unique options, but the descriptor types still admitted both, so TS-authored plugins only learned about it when registration stripped their management surface. Make `properties` required, give enums a dedicated descriptor member with required `options`, and drop the never-honored `envResolvable` flag from number descriptors, in both channel-base and the SDK mirror, and export the descriptor sub-types through the webui barrels. Also map a throwing `validateConfig` to the usual invalid-config error and pin the store contracts that had no distinguishing tests: omitting a parent object drops the stored object without checking its nested required, writes replace nested values wholesale, unchanged stored scalars are still re-validated, and valid plugins register by original reference. * fix(channels): defuse validateConfig rejection leak and close descriptor gate gaps (#8517) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
8.8 KiB
DingTalk (Dingtalk)
This guide covers setting up a Qwen Code channel on DingTalk (钉钉).
Prerequisites
- A DingTalk organization account
- A DingTalk bot application with AppKey and AppSecret (see below)
Creating a Bot
- Go to the DingTalk Developer Portal
- Create a new application (or use an existing one)
- Under the application, enable the Robot capability
- In Robot settings, enable Stream Mode (机器人协议 → Stream 模式)
- Note the AppKey (Client ID) and AppSecret (Client Secret) from the application credentials page
Stream Mode
DingTalk Stream mode uses an outbound WebSocket connection — no public URL or server is needed. The bot connects to DingTalk's servers, which push messages through the WebSocket. This is the simplest deployment model.
Configuration
Add the channel to ~/.qwen/settings.json:
{
"channels": {
"my-dingtalk": {
"type": "dingtalk",
"clientId": "$DINGTALK_CLIENT_ID",
"clientSecret": "$DINGTALK_CLIENT_SECRET",
"useConnectionManager": true,
"senderPolicy": "open",
"sessionScope": "user",
"cwd": "/path/to/your/project",
"instructions": "You are a concise coding assistant responding via DingTalk.",
"groupPolicy": "open",
"atSender": true,
"groups": {
"*": { "requireMention": true }
}
}
}
}
Set the credentials as environment variables:
export DINGTALK_CLIENT_ID=<your-app-key>
export DINGTALK_CLIENT_SECRET=<your-app-secret>
Or define them in the env section of settings.json:
{
"env": {
"DINGTALK_CLIENT_ID": "your-app-key",
"DINGTALK_CLIENT_SECRET": "your-app-secret"
}
}
Interactive Cards
Add an interactiveCards object to opt in to DingTalk status and question
cards. Omitting the object disables interactive cards. When the object is
present, the overall switch and both card types default to enabled, and
question cards time out after 270,000 milliseconds (270 seconds).
{
"channels": {
"my-dingtalk": {
"type": "dingtalk",
"clientId": "$DINGTALK_CLIENT_ID",
"clientSecret": "$DINGTALK_CLIENT_SECRET",
"interactiveCards": {
"enabled": true,
"statusCard": { "enabled": true },
"questionCard": {
"enabled": true,
"timeoutMs": 270000
}
}
}
}
}
Set interactiveCards.enabled to false to disable all interactive cards.
Use statusCard.enabled or questionCard.enabled to disable one card type,
and set questionCard.timeoutMs to a finite positive number to change how long
Qwen Code waits for a question-card response. Values above 2,147,483,647
milliseconds (about 24.8 days) are capped at that maximum. Interactive cards
are configured through settings.json or the management API; the Web Shell
channel editor does not render them, and it preserves the stored object when
you edit other fields.
Connection Recovery
useConnectionManager defaults to true. The connection manager monitors the Stream WebSocket and replaces the DingTalk SDK client when the connection stops responding. You should normally leave it enabled.
Set "useConnectionManager": false to disable Qwen Code's connection manager and fall back to the SDK's keepalive and automatic reconnect behavior.
Running
# Start only the DingTalk channel
qwen channel start my-dingtalk
# Or start all configured channels together
qwen channel start
Open DingTalk and send a message to the bot. You should see a 👀 emoji reaction appear while the agent processes, followed by the response.
Daemon Webhook Delivery
When the channel runs under qwen serve, authenticated external Webhook events can trigger unattended agent tasks and deliver the final Markdown response to either a DingTalk user or group. Use the existing Webhook target fields; no separate channel type is required:
{
"webhooks": {
"sources": {
"manual-test": {
"secretEnv": "QWEN_CHANNEL_DINGTALK_TEST_SECRET",
"targets": {
"operator": {
"chatId": "DINGTALK_USER_ID",
"senderId": "webhook:manual-test",
"isGroup": false
},
"team": {
"chatId": "OPEN_CONVERSATION_ID",
"senderId": "webhook:manual-test",
"isGroup": true
}
}
}
}
}
}
Every target must set isGroup explicitly. For a direct message, chatId is the recipient's DingTalk user ID. For a group message, chatId is the group's openConversationId. Thread targets and incoming robot Webhook URLs are not supported for proactive delivery. See Webhook-triggered tasks for the complete channel configuration and request format.
Group Chats
DingTalk bots work in both DM and group conversations. To enable group support:
- Set
groupPolicyto"allowlist","pairing", or"open"in your channel config - Add the bot to a DingTalk group
- @mention the bot in the group to trigger a response
- If using
groupPolicy: "pairing", approve the group's pairing request once before responses start
By default, the bot requires an @mention in group chats (requireMention: true). Set "requireMention": false for a specific group to make it respond to all messages. See Group Chats for full details.
Set "atSender": true to have the bot @mention the member whose group message triggered its response. It is off by default and only applies to agent replies with a DingTalk staff ID. Replies are sent as DingTalk markdown whether or not they carry a mention; the mention prefix is included in the first message chunk.
Finding a Group's Conversation ID
DingTalk uses conversationId to identify groups. You can find it in the channel service logs when someone sends a message in the group — look for the conversationId field in the log output.
Images and Files
You can send photos and documents to the bot, not just text.
Photos: Send an image (screenshot, diagram, etc.) and the agent will analyze it using its vision capabilities. This requires a multimodal model — add "model": "qwen3.5-plus" (or another vision-capable model) to your channel config. DingTalk supports sending images directly or as part of rich text messages (mixed text + images).
Files: Send a PDF, code file, or any document. The bot downloads it from DingTalk's servers and saves it locally so the agent can read it with its file tools. Audio and video files are also supported. This works with any model.
Key Differences from Telegram
- Authentication: AppKey + AppSecret instead of a static bot token. The SDK manages access token refresh automatically.
- Connection: WebSocket stream instead of polling — no public IP or webhook URL needed.
- Formatting: Responses use DingTalk's markdown dialect. Markdown tables are passed through to the DingTalk client; long messages are split into chunks at ~3800 characters.
- Working indicator: A 👀 emoji reaction is added to the user's message while processing, then removed when the response is sent.
- Media download: Two-step process — a
downloadCodefrom the message is exchanged for a temporary download URL via DingTalk's API. - Groups: DingTalk uses
isInAtListfor @mention detection instead of parsing message entities.
Tips
- Use DingTalk markdown-aware instructions — DingTalk supports headings, bold text, links, code blocks, and tables. Keep tables compact because narrow screens may scroll horizontally.
- Restrict access — In an organization context,
senderPolicy: "open"may be acceptable. For tighter control, use"allowlist"or"pairing". See DM Pairing for details. - Referenced messages — Quoting (replying to) a user message includes the quoted text as context for the agent. Quoting bot responses is not yet supported.
Troubleshooting
Bot doesn't connect
- Verify your AppKey and AppSecret are correct
- Check that the environment variables are set before running
qwen channel start - Make sure Stream Mode is enabled in the bot's settings on the DingTalk Developer Portal
- Check the terminal output for connection errors
Bot doesn't respond in groups
- Check that
groupPolicyis set to"allowlist","pairing", or"open"(default is"disabled") - If using
"pairing", verify the group's pairing request has been approved - Make sure you @mention the bot in the group message
- Verify the bot has been added to the group
"No sessionWebhook in message"
This means DingTalk didn't include a reply endpoint in the message callback. This can happen if the bot's permissions are misconfigured. Check the bot's settings in the Developer Portal.
"Sorry, something went wrong processing your message"
This usually means the agent encountered an error. Check the terminal output for details.