@yaoyuanchao/dingtalk 1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,149 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.2.0] - 2026-01-28
9
+
10
+ ### 🎉 Major Features - Official Plugin Release
11
+
12
+ This release transforms the DingTalk plugin into an official Clawdbot plugin that can be installed via `clawdbot plugins install` command.
13
+
14
+ ### Added
15
+
16
+ - **Official NPM Installation Support**
17
+ - Published to NPM as `@yaoyuanchao/dingtalk`
18
+ - Install with: `clawdbot plugins install @yaoyuanchao/dingtalk`
19
+ - Added `clawdbot.install` configuration in package.json
20
+
21
+ - **Interactive Onboarding Wizard** (`src/onboarding.ts`)
22
+ - Run with: `clawdbot onboard --channel dingtalk`
23
+ - Step-by-step guided configuration
24
+ - Automatic connection testing
25
+ - Policy selection with descriptions
26
+ - Auto-save to configuration file
27
+
28
+ - **Zod Schema Validation** (`src/config-schema.ts`)
29
+ - Type-safe configuration validation
30
+ - Automatic error messages with detailed paths
31
+ - Default values for all optional fields
32
+ - Strict mode to catch unknown properties
33
+
34
+ - **Health Check System** (`src/probe.ts`)
35
+ - Connection status monitoring
36
+ - Latency measurement
37
+ - Improved `status.probeAccount` with detailed error reporting
38
+
39
+ - **Enhanced Error Messages**
40
+ - Validation errors show exact field and reason
41
+ - Helpful hints for environment variable configuration
42
+ - Guidance for troubleshooting connection issues
43
+
44
+ ### Changed
45
+
46
+ - **Package Configuration**
47
+ - Renamed from `@clawdbot/dingtalk` to `@yaoyuanchao/dingtalk`
48
+ - Version bumped from 0.1.0 to 1.2.0
49
+ - Added MIT license
50
+ - Added keywords for NPM discoverability
51
+ - Added `peerDependencies` for clawdbot version requirement
52
+ - Added `files` field to control NPM publish content
53
+
54
+ - **Configuration Validation**
55
+ - Migrated from JSON Schema to Zod
56
+ - Environment variables merged before validation
57
+ - Better error messages for invalid configurations
58
+
59
+ - **Module Imports**
60
+ - Separated probe functionality into dedicated module
61
+ - Improved type safety with Zod type inference
62
+
63
+ ### Technical Details
64
+
65
+ - **Dependencies**: Added `zod@^3.22.0`
66
+ - **Peer Dependencies**: Requires `clawdbot >= 2026.1.24`
67
+ - **TypeScript**: Published as TypeScript source (not compiled JS)
68
+ - **Backward Compatibility**: All v0.1.0 features preserved
69
+
70
+ ### Migration Guide
71
+
72
+ If you're upgrading from v0.1.0:
73
+
74
+ 1. **No configuration changes required** - existing configs work as-is
75
+ 2. **Optional**: Try the new onboarding wizard for fresh setup
76
+ 3. **Optional**: Reinstall via NPM for easier updates:
77
+ ```bash
78
+ clawdbot plugins uninstall dingtalk
79
+ clawdbot plugins install @yaoyuanchao/dingtalk
80
+ ```
81
+
82
+ ## [0.1.0] - 2026-01-26
83
+
84
+ ### Initial Release
85
+
86
+ #### Added
87
+
88
+ - **Stream Mode Connection**
89
+ - WebSocket-based connection via `dingtalk-stream@2.1.4`
90
+ - No public domain required
91
+ - Auto-reconnection support
92
+
93
+ - **Private Messages (DM)**
94
+ - Support for 1-on-1 conversations
95
+ - Pairing mode with automatic staffId display
96
+ - Allowlist mode for restricted access
97
+ - Open mode for unrestricted access
98
+
99
+ - **Group Chat Support**
100
+ - @mention detection
101
+ - Group allowlist (conversation IDs)
102
+ - Optional mention requirement
103
+
104
+ - **Message Sending**
105
+ - Dual-route strategy:
106
+ - SessionWebhook (preferred, 35-minute validity)
107
+ - REST API (fallback for expired sessions)
108
+ - Text message support
109
+ - Markdown format support (limited by DingTalk)
110
+ - Auto-converts tables to code blocks
111
+
112
+ - **Access Control**
113
+ - DM policies: disabled, pairing, allowlist, open
114
+ - Group policies: disabled, allowlist, open
115
+ - Per-user staffId authorization
116
+ - Per-group conversationId authorization
117
+
118
+ - **Configuration**
119
+ - Environment variable support (DINGTALK_CLIENT_ID, etc.)
120
+ - JSON configuration in clawdbot.json
121
+ - Credential source detection (config vs env)
122
+
123
+ - **Core Modules**
124
+ - `src/monitor.ts` - Stream connection and message handling
125
+ - `src/api.ts` - DingTalk REST API wrapper
126
+ - `src/channel.ts` - Clawdbot ChannelPlugin implementation
127
+ - `src/accounts.ts` - Account credential resolution
128
+ - `src/types.ts` - TypeScript type definitions
129
+
130
+ #### Known Limitations
131
+
132
+ - **Markdown Support**: DingTalk doesn't support tables in markdown
133
+ - **Media Messages**: Sending files/images not yet implemented
134
+ - **Rate Limits**: 20 messages/minute/group (DingTalk limit)
135
+
136
+ ---
137
+
138
+ ## Legend
139
+
140
+ - 🎉 **Major Features**: Significant new functionality
141
+ - ✨ **Enhancements**: Improvements to existing features
142
+ - 🐛 **Bug Fixes**: Fixes for issues
143
+ - 🔒 **Security**: Security-related changes
144
+ - 📝 **Documentation**: Documentation updates
145
+ - ⚠️ **Breaking Changes**: Changes that may require migration
146
+
147
+ ---
148
+
149
+ **Note**: This changelog focuses on user-facing changes. For detailed commit history, see the Git log.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yao Yuanchao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # DingTalk Channel Plugin for Clawdbot
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@yaoyuanchao/dingtalk.svg)](https://www.npmjs.com/package/@yaoyuanchao/dingtalk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ 钉钉(DingTalk)频道插件,支持通过 Stream 模式接收和回复私聊及群聊消息。**无需公网域名,开箱即用!**
7
+
8
+ ---
9
+
10
+ ## 功能特性
11
+
12
+ - ✅ Stream 模式连接(无需公网域名)
13
+ - ✅ 私聊消息支持(DM)
14
+ - ✅ 群聊消息支持(@机器人)
15
+ - ✅ Pairing 模式访问控制
16
+ - ✅ SessionWebhook 优先回复(35分钟内快速响应)
17
+ - ✅ REST API 兜底回复
18
+
19
+ ## 前置要求
20
+
21
+ 1. 钉钉企业内部应用(已创建并配置 Stream 模式)
22
+ 2. Node.js 环境(clawdbot 已安装)
23
+ 3. 应用的 AppKey (clientId) 和 AppSecret (clientSecret)
24
+
25
+ ## 快速开始
26
+
27
+ ### 方式一:官方安装(推荐)
28
+
29
+ ```bash
30
+ # 安装插件
31
+ clawdbot plugins install @yaoyuanchao/dingtalk
32
+
33
+ # 运行交互式配置向导
34
+ clawdbot onboard --channel dingtalk
35
+
36
+ # 启动网关
37
+ clawdbot gateway
38
+ ```
39
+
40
+ 就这么简单!配置向导会引导你完成所有设置。
41
+
42
+ ### 方式二:手动安装
43
+
44
+ 如果你想从源码安装或进行开发:
45
+
46
+ ```bash
47
+ # 克隆或下载源码
48
+ git clone https://github.com/yourusername/dingtalk-clawdbot.git
49
+
50
+ # 本地安装
51
+ cd dingtalk-clawdbot
52
+ npm install
53
+ clawdbot plugins install .
54
+ ```
55
+
56
+ ## 配置说明
57
+
58
+ ### 交互式配置(推荐)
59
+
60
+ 运行 `clawdbot onboard --channel dingtalk` 启动配置向导,它会:
61
+
62
+ 1. ✅ 要求输入 Client ID 和 Client Secret
63
+ 2. ✅ 自动测试连接
64
+ 3. ✅ 引导选择私聊策略(Pairing/Allowlist/Open/Disabled)
65
+ 4. ✅ 引导选择群聊策略(Allowlist/Open/Disabled)
66
+ 5. ✅ 自动保存配置到 `~/.clawdbot/clawdbot.json`
67
+
68
+ ### 手动配置 clawdbot.json
69
+
70
+ 编辑 `~/.clawdbot/clawdbot.json`,添加 DingTalk 频道配置:
71
+
72
+ ```json
73
+ {
74
+ "channels": {
75
+ "dingtalk": {
76
+ "enabled": true,
77
+ "clientId": "dingXXXXXXXXXXXXXXXX",
78
+ "clientSecret": "YOUR_APP_SECRET_HERE",
79
+ "dm": {
80
+ "policy": "pairing",
81
+ "allowFrom": ["YOUR_STAFF_ID"]
82
+ },
83
+ "groupPolicy": "allowlist",
84
+ "groupAllowlist": ["cidlnNrtqQ4kGskU56Qni6zTg=="],
85
+ "requireMention": true
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ **配置说明**:
92
+ - `clientId`: 钉钉应用的 AppKey
93
+ - `clientSecret`: 钉钉应用的 AppSecret
94
+ - `dm.policy`: 私聊策略
95
+ - `"pairing"`: 需要明确允许的用户才能使用(推荐)
96
+ - `"open"`: 任何人都可以私聊
97
+ - `"disabled"`: 禁用私聊
98
+ - `dm.allowFrom`: 允许私聊的 staffId 列表(当 policy 为 pairing 时生效)
99
+ - `groupPolicy`: 群聊策略
100
+ - `"allowlist"`: 仅允许列表中的群
101
+ - `"open"`: 允许所有群
102
+ - `"disabled"`: 禁用群聊
103
+ - `groupAllowlist`: 允许的群聊 conversationId 列表(当 groupPolicy 为 allowlist 时生效)
104
+ - `requireMention`: 是否要求在群聊中 @机器人(推荐 true)
105
+ - `messageFormat`: 消息格式(可选)
106
+ - `"text"`: 纯文本格式(默认,推荐)
107
+ - `"markdown"`: Markdown 格式(⚠️ 钉钉仅支持有限的 Markdown 语法,**不支持表格**)
108
+
109
+ **Markdown 格式说明**:
110
+ - ✅ 支持:标题、粗体、斜体、链接、图片、引用、列表、代码块
111
+ - ❌ 不支持:**表格**、复杂嵌套列表、HTML 标签、任务列表
112
+ - 📝 自动转换:插件会自动将 Markdown 表格转换为纯文本代码块显示
113
+ - ⏰ 仅 SessionWebhook 可用:REST API 兜底时自动降级为纯文本
114
+
115
+ **配置示例**:
116
+ ```json
117
+ {
118
+ "messageFormat": "markdown"
119
+ }
120
+ ```
121
+
122
+ ### 4. 启动 clawdbot gateway
123
+
124
+ ```bash
125
+ # 如果已经在运行,需要重启
126
+ clawdbot gateway restart
127
+
128
+ # 或者首次启动
129
+ clawdbot gateway
130
+ ```
131
+
132
+ ### 5. 查看日志验证
133
+
134
+ ```bash
135
+ # 查看实时日志
136
+ tail -f /tmp/clawdbot/clawdbot-$(date +%Y-%m-%d).log | grep dingtalk
137
+
138
+ # 成功的标志:
139
+ # [dingtalk:default] Starting Stream...
140
+ # [dingtalk:default] Stream connected
141
+ # [dingtalk] Stream connection started successfully
142
+ ```
143
+
144
+ ## 获取你的 staffId
145
+
146
+ 首次使用时,机器人会告诉你的 staffId:
147
+
148
+ 1. 在钉钉中找到机器人
149
+ 2. 发送任意消息
150
+ 3. 机器人会回复:"Access denied. Your staffId: 050914185922786044 Ask admin to add you."
151
+ 4. 将这个 staffId 添加到配置文件的 `dm.allowFrom` 数组中
152
+ 5. 重启 gateway
153
+
154
+ ## 获取群聊 conversationId
155
+
156
+ 如果使用 `groupPolicy: "allowlist"`,需要获取群聊的 conversationId:
157
+
158
+ **方法1: 查看日志**
159
+ 1. 在群聊中 @机器人发送消息
160
+ 2. 查看 gateway 日志:
161
+ ```bash
162
+ tail -f /tmp/clawdbot/clawdbot-$(date +%Y-%m-%d).log | grep "dingtalk.*Group"
163
+ ```
164
+ 3. 日志会显示:`[dingtalk] Group from XXX: ...` 以及相关的 conversationId
165
+ 4. 或者查看日志中的 "Group not in allowlist" 消息获取 conversationId
166
+
167
+ **方法2: 临时设置为 open**
168
+ 1. 临时修改配置为 `groupPolicy: "open"`
169
+ 2. 重启 gateway
170
+ 3. 在群聊中 @机器人发送消息
171
+ 4. 查看日志获取 conversationId(格式类似 `cidlnNrtqQ4kGskU56Qni6zTg==`)
172
+ 5. 将 conversationId 添加到 `groupAllowlist` 数组
173
+ 6. 改回 `groupPolicy: "allowlist"` 并重启
174
+
175
+ **配置示例**:
176
+ ```json
177
+ {
178
+ "groupPolicy": "allowlist",
179
+ "groupAllowlist": [
180
+ "cidlnNrtqQ4kGskU56Qni6zTg==",
181
+ "anotherConversationId123=="
182
+ ],
183
+ "requireMention": true
184
+ }
185
+ ```
186
+
187
+ ## 钉钉应用配置
188
+
189
+ ### 创建应用
190
+
191
+ 1. 登录 [钉钉开发者平台](https://open-dev.dingtalk.com)
192
+ 2. 进入 **应用开发** → **企业内部开发**
193
+ 3. 点击 **创建应用**
194
+ 4. 添加 **机器人** 能力
195
+ 5. 消息接收模式选择 **Stream 模式**
196
+
197
+ ### 配置权限
198
+
199
+ 应用需要以下权限:
200
+ - `qyapi_chat_manage`(企业会话管理)
201
+ - `qyapi_robot_sendmsg`(机器人发送消息)
202
+
203
+ ### 获取凭证
204
+
205
+ 在应用详情页面:
206
+ - **AppKey** → 这是你的 `clientId`
207
+ - **AppSecret** → 点击查看并复制,这是你的 `clientSecret`
208
+
209
+ ### 发布应用
210
+
211
+ 配置完成后,点击 **发布** 使应用生效。
212
+
213
+ ## 故障排查
214
+
215
+ ### Stream 连接失败
216
+
217
+ ```
218
+ [dingtalk] Failed to start Stream
219
+ ```
220
+
221
+ **可能原因**:
222
+ 1. clientId 或 clientSecret 错误
223
+ 2. 应用未选择 Stream 模式
224
+ 3. 应用未发布
225
+
226
+ **解决方法**:
227
+ - 检查配置文件中的凭证是否正确
228
+ - 确认钉钉应用已选择 Stream 模式并发布
229
+
230
+ ### 发送消息无响应
231
+
232
+ **可能原因**:
233
+ 1. staffId 未添加到 allowFrom 列表
234
+ 2. 群聊未 @机器人(requireMention 为 true 时)
235
+
236
+ **解决方法**:
237
+ - 查看日志获取 staffId 并添加到配置
238
+ - 在群聊中使用 @机器人名称 来触发
239
+
240
+ ### 配置修改未生效
241
+
242
+ **原因**:配置修改需要重启 gateway
243
+
244
+ **解决方法**:
245
+ ```bash
246
+ clawdbot gateway restart
247
+ ```
248
+
249
+ ## 技术细节
250
+
251
+ ### 核心实现
252
+
253
+ - **Stream SDK**: `dingtalk-stream@2.1.4`
254
+ - **消息处理**: 通过 `DWClient` 建立 WebSocket 长连接
255
+ - **配置加载**: 自动调用 `cfg.loadConfig()` 获取实际配置(重要修复)
256
+ - **回复策略**: SessionWebhook (35分钟内) → REST API (兜底)
257
+
258
+ ### 关键文件
259
+
260
+ | 文件 | 作用 |
261
+ |------|------|
262
+ | `src/monitor.ts` | Stream 连接 + 消息处理核心逻辑 |
263
+ | `src/api.ts` | 钉钉 REST API 封装 |
264
+ | `src/channel.ts` | ChannelPlugin 接口实现 |
265
+ | `src/accounts.ts` | 账号凭证解析 |
266
+
267
+ ### 已知限制
268
+
269
+ - **Markdown 有限支持**:钉钉不支持 Markdown 表格,插件会自动转换为纯文本代码块
270
+ - **消息格式**:默认纯文本,可选 Markdown(但有语法限制)
271
+ - **媒体消息**:暂不支持文件、图片等媒体消息发送
272
+ - **钉钉限流**:20条/分钟/群,超限后10分钟限流
273
+
274
+ ## 更新日志
275
+
276
+ 查看 [CHANGELOG.md](./CHANGELOG.md) 获取详细的版本历史。
277
+
278
+ ### v1.2.0 (2026-01-28) - 官方插件发布
279
+
280
+ - ✅ **官方 NPM 安装支持** - `clawdbot plugins install @yaoyuanchao/dingtalk`
281
+ - ✅ **交互式配置向导** - `clawdbot onboard --channel dingtalk`
282
+ - ✅ **Zod 配置验证** - 类型安全、自动错误提示
283
+ - ✅ **健康检查** - 自动探测连接状态和延迟
284
+ - ✅ 保留所有 v0.1.0 功能
285
+
286
+ ### v0.1.0 (2026-01-26)
287
+
288
+ - ✅ 初始版本发布
289
+ - ✅ Stream 模式连接
290
+ - ✅ 私聊 + 群聊支持
291
+ - ✅ Pairing 访问控制
292
+ - ✅ 群聊白名单(groupAllowlist)
293
+ - ✅ Markdown 消息格式支持(自动转换表格为纯文本)
294
+
295
+ ## 许可证
296
+
297
+ MIT License - 查看 [LICENSE](./LICENSE) 文件获取详细信息。
298
+
299
+ ## 贡献
300
+
301
+ 发现问题或有改进建议?欢迎提交 Issue 或 Pull Request。
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "dingtalk",
3
+ "channels": ["dingtalk"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {}
8
+ }
9
+ }
package/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { dingtalkPlugin } from "./src/channel.js";
2
+ import { setDingTalkRuntime } from "./src/runtime.js";
3
+ import { dingTalkConfigSchema } from "./src/config-schema.js";
4
+
5
+ const plugin = {
6
+ id: "dingtalk",
7
+ name: "DingTalk",
8
+ description: "DingTalk channel plugin with Stream Mode support",
9
+ configSchema: dingTalkConfigSchema,
10
+ register(api: any) {
11
+ setDingTalkRuntime(api.runtime);
12
+ api.registerChannel({ plugin: dingtalkPlugin });
13
+ },
14
+ };
15
+
16
+ export default plugin;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@yaoyuanchao/dingtalk",
3
+ "version": "1.2.0",
4
+ "type": "module",
5
+ "description": "DingTalk channel plugin for Clawdbot with Stream Mode support",
6
+ "license": "MIT",
7
+ "author": "Yao Yuanchao",
8
+ "keywords": [
9
+ "clawdbot",
10
+ "dingtalk",
11
+ "channel-plugin",
12
+ "stream-mode",
13
+ "ai-assistant"
14
+ ],
15
+ "clawdbot": {
16
+ "extensions": [
17
+ "./index.ts"
18
+ ],
19
+ "channel": {
20
+ "id": "dingtalk",
21
+ "label": "DingTalk",
22
+ "selectionLabel": "DingTalk (钉钉)",
23
+ "detailLabel": "DingTalk Stream Mode",
24
+ "docsPath": "/channels/dingtalk",
25
+ "docsLabel": "dingtalk",
26
+ "blurb": "DingTalk bot via Stream Mode (WebSocket) - No public domain required",
27
+ "aliases": [
28
+ "dingding",
29
+ "dd"
30
+ ],
31
+ "order": 75,
32
+ "quickstartAllowFrom": true
33
+ },
34
+ "install": {
35
+ "npmSpec": "@yaoyuanchao/dingtalk",
36
+ "localPath": ".",
37
+ "defaultChoice": "npm"
38
+ }
39
+ },
40
+ "dependencies": {
41
+ "dingtalk-stream": "^2.1.4",
42
+ "zod": "^3.22.0"
43
+ },
44
+ "peerDependencies": {
45
+ "clawdbot": ">=2026.1.24"
46
+ },
47
+ "files": [
48
+ "index.ts",
49
+ "src/**/*.ts",
50
+ "clawdbot.plugin.json",
51
+ "README.md",
52
+ "CHANGELOG.md",
53
+ "LICENSE"
54
+ ]
55
+ }
@@ -0,0 +1,73 @@
1
+ import type { ResolvedDingTalkAccount, DingTalkChannelConfig } from "./types.js";
2
+ import { validateDingTalkConfig, type DingTalkConfig } from "./config-schema.js";
3
+
4
+ const DEFAULT_ACCOUNT_ID = "default";
5
+ const ENV_CLIENT_ID = "DINGTALK_CLIENT_ID";
6
+ const ENV_CLIENT_SECRET = "DINGTALK_CLIENT_SECRET";
7
+ const ENV_ROBOT_CODE = "DINGTALK_ROBOT_CODE";
8
+
9
+ export function listDingTalkAccountIds(cfg: any): string[] {
10
+ const channel = cfg?.channels?.dingtalk as DingTalkChannelConfig | undefined;
11
+ if (!channel) return [DEFAULT_ACCOUNT_ID];
12
+ return [DEFAULT_ACCOUNT_ID];
13
+ }
14
+
15
+ export function resolveDefaultDingTalkAccountId(cfg: any): string {
16
+ return DEFAULT_ACCOUNT_ID;
17
+ }
18
+
19
+ export function resolveDingTalkAccount(params: {
20
+ cfg: any;
21
+ accountId?: string | null;
22
+ }): ResolvedDingTalkAccount {
23
+ const accountId = params.accountId?.trim() || DEFAULT_ACCOUNT_ID;
24
+ const rawConfig = params.cfg?.channels?.dingtalk ?? {};
25
+
26
+ // Merge configuration with environment variables
27
+ // Environment variables serve as fallback for missing config values
28
+ const configWithEnv = {
29
+ ...rawConfig,
30
+ clientId: rawConfig.clientId || process.env[ENV_CLIENT_ID],
31
+ clientSecret: rawConfig.clientSecret || process.env[ENV_CLIENT_SECRET],
32
+ robotCode: rawConfig.robotCode || process.env[ENV_ROBOT_CODE],
33
+ };
34
+
35
+ // Validate configuration with Zod
36
+ let validatedConfig: DingTalkConfig;
37
+ let credentialSource: "config" | "env" | "none" = "none";
38
+
39
+ try {
40
+ validatedConfig = validateDingTalkConfig(configWithEnv);
41
+
42
+ // Determine credential source
43
+ if (rawConfig.clientId && rawConfig.clientSecret) {
44
+ credentialSource = "config";
45
+ } else if (validatedConfig.clientId && validatedConfig.clientSecret) {
46
+ credentialSource = "env";
47
+ }
48
+ } catch (error) {
49
+ // If validation fails, throw a helpful error message
50
+ const errorMsg = error instanceof Error ? error.message : String(error);
51
+ throw new Error(
52
+ `DingTalk configuration validation failed for account "${accountId}":\n${errorMsg}\n\n` +
53
+ `Please check your configuration at channels.dingtalk or set environment variables:\n` +
54
+ ` - ${ENV_CLIENT_ID}\n` +
55
+ ` - ${ENV_CLIENT_SECRET}\n` +
56
+ ` - ${ENV_ROBOT_CODE} (optional)`
57
+ );
58
+ }
59
+
60
+ const configured = !!(validatedConfig.clientId && validatedConfig.clientSecret);
61
+
62
+ return {
63
+ accountId,
64
+ name: "DingTalk Bot",
65
+ enabled: validatedConfig.enabled,
66
+ configured,
67
+ clientId: validatedConfig.clientId,
68
+ clientSecret: validatedConfig.clientSecret,
69
+ robotCode: validatedConfig.robotCode || validatedConfig.clientId,
70
+ credentialSource,
71
+ config: validatedConfig as unknown as Record<string, any>,
72
+ };
73
+ }