docs/design/2026-07-14-dingtalk-webhook-direct-message.md
已实现并完成单聊真实链路验证。对应 Issue: QwenLM/qwen-code#6883。
由 daemon 托管的 channel 可以接收经过鉴权的外部 Webhook 事件,以无人值守任务的方式运行 agent,并将最终结果主动投递到预先配置的聊天目标。目前钉钉只支持投递到群聊:目标必须设置 isGroup: true,adapter 通过群消息 API 发送 Markdown。
这使得 CI 系统、监控告警等 Webhook 来源无法直接通知某个负责的钉钉用户,只能投递到群聊。
无需新增配置字段。现有 Webhook 目标字段在钉钉 channel 中的含义如下:
isGroup | chatId 含义 | 投递 API |
|---|---|---|
true | 钉钉群聊 openConversationId | robot/groupMessages/send |
false | 钉钉用户 ID | robot/oToMessages/batchSend |
senderId 仍然是用于将 Webhook 任务路由到 agent session 的虚拟身份,不是钉钉接收人 ID。
配置示例:
{
"webhooks": {
"sources": {
"github-ci": {
"secretEnv": "QWEN_CHANNEL_GITHUB_CI_SECRET",
"targets": {
"operator": {
"chatId": "DINGTALK_USER_ID",
"senderId": "webhook:github-ci",
"isGroup": false
},
"team": {
"chatId": "OPEN_CONVERSATION_ID",
"senderId": "webhook:github-ci",
"isGroup": true
}
}
}
}
}
}
目标必须显式设置 isGroup。以下目标继续被 adapter 拒绝:chatId 为空、设置了 threadId、缺少 isGroup,或者使用 Webhook URL 代替稳定的目标 ID。
daemon 路由和 worker IPC 保持不变;共享 channel runtime 仅增加 Webhook 专用目标检查:
POST /channels/:channelName/webhooks/:source
-> daemon 对事件进行鉴权和校验
-> channel worker 运行无人值守 agent 任务
-> ChannelBase 调用 DingtalkChannel.pushProactive()
-> adapter 根据 target.isGroup 选择钉钉 API
-> 钉钉接收 Markdown
共享 channel runtime 使用独立的 Webhook 目标能力检查。默认实现仍沿用普通主动投递的目标规则;钉钉仅在 Webhook 任务解析时额外接受 isGroup: false。因此普通 channel loop 继续拒绝单聊目标,避免把入站单聊的 conversationId 错当成一对一消息 API 所需的用户 ID。
群聊目标继续使用现有请求体:
{
"robotCode": "CLIENT_ID",
"openConversationId": "OPEN_CONVERSATION_ID",
"msgKey": "sampleMarkdown",
"msgParam": "{...}"
}
单聊目标通过一对一消息 API 发送相同的 Markdown 模板:
{
"robotCode": "CLIENT_ID",
"userIds": ["DINGTALK_USER_ID"],
"msgKey": "sampleMarkdown",
"msgParam": "{...}"
}
两条路径共用现有的 access token 缓存,在 Token 到期前一分钟刷新;遇到 HTTP 401 时重试一次;同时使用相同的 Markdown 规范化和分片限制。多分片投递在首个分片失败后停止。
202 {"accepted": true} 仍然只表示 worker 已接收任务,不代表钉钉投递成功。本期范围内仅支持 Markdown,因此无需设计 Markdown 降级策略。
isGroup、ID 为空、使用 Webhook URL 和设置 threadId 的目标。openConversationId 的请求体不变。userIds 的请求体。在 .qwen/e2e-tests/ 下编写测试计划,并先使用全局安装的 qwen CLI,记录当前单聊 Webhook 目标被拒绝的基线行为。实现完成后:
qwen serve。curl 分别向两个 targetRef 提交一条事件。202。如果本地没有可用的钉钉凭据或接收目标,则以单元测试作为自动化投递验证,并明确说明缺少的在线验证步骤。
更新 channel Webhook 文档,展示钉钉单聊和群聊两种目标配置,并说明单聊目标的 chatId 填写钉钉用户 ID。
本次为增量变更。现有群聊目标的配置、校验、endpoint、请求体、格式化和重试行为均不变,无需迁移配置。共享 runtime 新增的 Webhook 目标检查默认委托给原有主动投递目标检查,因此其他 channel 的行为不变。