@largezhou/ddingtalk 1.3.2 → 1.4.0-beta.1

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/README.md CHANGED
@@ -1,18 +1,22 @@
1
1
  # @largezhou/ddingtalk
2
2
 
3
+ [English](README.en.md)
4
+
3
5
  OpenClaw 钉钉(DingTalk)渠道插件,使用 Stream 模式接入企业机器人。
4
6
 
5
7
  ## 功能特点
6
8
 
7
9
  - ✅ **Stream 模式**:无需公网 IP 和域名,开箱即用
10
+ - ✅ **多账号支持**:可同时接入多个钉钉机器人,分别配置凭证和权限
11
+ - ✅ **多 Agent 路由**:支持将不同账号、群聊、私聊绑定到不同的 Agent
8
12
  - ✅ **私聊/群聊**:支持私聊,群聊(仅@机器人)
9
13
  - ✅ **文本消息收发**:接收和发送文本消息
10
- - ✅ **Markdown回复**:机器人回复 Markdown 格式
14
+ - ✅ **Markdown 回复**:机器人回复 Markdown 格式
11
15
  - ✅ **图片消息收发**:接收用户发送的图片,支持发送本地/远程图片
12
16
  - ✅ **语音、视频、文件、图文混排**:接收用户发送语音、视频、文件、图文混排消息
13
- - ✅ **回复文件**:支持回复文件,音频、视频等统一按文件发送(按语音、视频发送,需要获取时长、视频封面,以后再支持)
17
+ - ✅ **回复文件**:支持回复文件,音频、视频等统一按文件发送
14
18
  - ✅ **主动推送消息**:支持主动推送消息,可以配置提醒或定时任务
15
- - ✅ **支持OpenClaw命令**:支持 /new、/compact 等 OpenClaw 官方命令
19
+ - ✅ **支持 OpenClaw 命令**:支持 /new、/compact 等 OpenClaw 官方命令
16
20
 
17
21
  ## 安装
18
22
 
@@ -20,53 +24,126 @@ OpenClaw 钉钉(DingTalk)渠道插件,使用 Stream 模式接入企业机
20
24
  openclaw plugins install @largezhou/ddingtalk
21
25
  ```
22
26
 
23
- ## 前置准备
27
+ ---
28
+
29
+ ## 快速开始
30
+
31
+ 添加钉钉渠道有两种方式:
32
+
33
+ ### 方式一:通过安装向导添加(推荐)
34
+
35
+ 如果您刚安装完 OpenClaw,可以直接运行向导,根据提示添加钉钉:
36
+
37
+ ```bash
38
+ openclaw onboard
39
+ ```
40
+
41
+ 向导会引导您完成:
42
+
43
+ 1. 创建钉钉应用机器人并获取凭证
44
+ 2. 配置应用凭证
45
+ 3. 启动网关
46
+
47
+ **完成配置后**,您可以使用以下命令检查网关状态:
48
+
49
+ - `openclaw gateway status` - 查看网关运行状态
50
+ - `openclaw logs --follow` - 查看实时日志
51
+
52
+ ### 方式二:通过命令行添加
53
+
54
+ 如果您已经完成了初始安装,可以用以下命令添加钉钉渠道:
55
+
56
+ ```bash
57
+ openclaw channels add
58
+ ```
59
+
60
+ 然后根据交互式提示选择 DingTalk,输入 AppKey (Client ID) 和 AppSecret (Client Secret) 即可。
61
+
62
+ **完成配置后**,您可以使用以下命令管理网关:
63
+
64
+ - `openclaw gateway status` - 查看网关运行状态
65
+ - `openclaw gateway restart` - 重启网关以应用新配置
66
+ - `openclaw logs --follow` - 查看实时日志
67
+
68
+ ---
69
+
70
+ ## 第一步:创建钉钉应用
71
+
72
+ ### 1. 打开钉钉开发者平台
73
+
74
+ 访问 [钉钉开发者平台](https://open-dev.dingtalk.com/fe/app),使用钉钉账号登录,选择组织进入。
75
+
76
+ ### 2. 创建应用
24
77
 
25
- ### 1. 创建钉钉企业内部应用
78
+ 1. 点击右上角 **创建应用**
79
+ 2. 填写应用名称和描述,上传图片(可选)
26
80
 
27
- 1. 登录 [钉钉开发者后台](https://open-dev.dingtalk.com/fe/app)
28
- 2. 创建应用
29
- 3. 记录 **AppKey** (ClientID) 和 **AppSecret** (ClientSecret)
81
+ ![创建应用](docs/images/dingtalk/dingtalk-create-app.png)
30
82
 
31
- ### 2. 开通机器人能力
83
+ ### 3. 获取应用凭证
32
84
 
33
- 1. 在应用详情页,点击 **应用能力** -> **添加应用能力**
34
- 2. 选择 **机器人**
35
- 3. 填写机器人基本信息
36
- 4. **重要**: 消息接收模式选择 **Stream 模式**
37
- 5. 发布应用
85
+ 在应用的 **凭证与基础信息** 页面,复制:
38
86
 
39
- ### 3. 配置应用权限
87
+ - **Client ID**(格式如 `dingxxxx`)
88
+ - **Client Secret**
89
+
90
+ ❗ **重要**:请妥善保管 Client Secret,不要分享给他人。
91
+
92
+ ![获取应用凭证](docs/images/dingtalk/dingtalk-credentials.png)
93
+
94
+ ### 4. 添加应用机器人
95
+
96
+ 1. 在应用的 **添加应用能力** 页面,选择 **机器人**,点击添加
97
+
98
+ ![添加机器人](docs/images/dingtalk/dingtalk-create-robot.png)
99
+
100
+ 2. 输入机器人相关信息,**消息接收模式** 选择 **Stream 模式**,然后保存
101
+
102
+ ![配置机器人](docs/images/dingtalk/dingtalk-robot-config.png)
103
+
104
+ ![配置机器人消息接收模式](docs/images/dingtalk/dingtalk-robot-config-stream.png)
105
+
106
+ ### 5. 配置应用权限
40
107
 
41
108
  在应用的权限管理中,确保开通以下权限:
42
109
 
43
110
  - 企业内机器人发送消息权限
44
111
  - 根据 downloadCode 获取机器人接收消息的下载链接(用于接收图片)
45
112
 
46
- ## 配置
113
+ ### 6. 发布机器人
114
+
115
+ 创建机器人版本,填入版本号、描述、应用可用范围,点击保存,点击确认发布。
116
+
117
+ ![创建机器人版本](docs/images/dingtalk/dingtalk-create-version.png)
118
+
119
+ ![编辑版本](docs/images/dingtalk/dingtalk-edit-version.png)
120
+
121
+ ---
47
122
 
48
- ### 方式一:交互式配置(推荐)
123
+ ## 第二步:配置 OpenClaw
124
+
125
+ ### 通过向导配置(推荐)
126
+
127
+ 运行以下命令,根据提示选择 DingTalk,粘贴 AppKey (Client ID) 和 AppSecret (Client Secret):
49
128
 
50
129
  ```bash
51
130
  openclaw channels add
52
131
  ```
53
132
 
54
- 选择 DingTalk,按提示输入 AppKey 和 AppSecret 即可。
133
+ ### 通过配置文件配置
55
134
 
56
- ### 方式二:手动配置
57
-
58
- 在 OpenClaw 配置文件 `~/.openclaw/openclaw.json` 中添加:
135
+ 编辑 `~/.openclaw/openclaw.json`:
59
136
 
60
137
  ```json
61
138
  {
62
- "channels": {
63
- "ddingtalk": {
64
- "enabled": true,
65
- "clientId": "your_app_key",
66
- "clientSecret": "your_app_secret",
67
- "allowFrom": ["*"]
68
- }
69
- }
139
+ "channels": {
140
+ "ddingtalk": {
141
+ "enabled": true,
142
+ "clientId": "your_app_key",
143
+ "clientSecret": "your_app_secret",
144
+ "allowFrom": ["*"]
145
+ }
146
+ }
70
147
  }
71
148
  ```
72
149
 
@@ -84,31 +161,197 @@ openclaw channels add
84
161
  }
85
162
  ```
86
163
 
87
- ## Demo
164
+ ---
165
+
166
+ ## 多账号配置
167
+
168
+ 支持同时接入多个钉钉机器人,每个机器人对应一个独立的账号(account)。适用场景:
169
+
170
+ - 不同部门使用不同的机器人
171
+ - 同一个 OpenClaw 实例服务多个钉钉组织
172
+ - 不同机器人配置不同的权限策略
173
+
174
+ ### 添加新账号
88
175
 
89
- 项目包含独立的 demo 示例,可以脱离 OpenClaw 框架单独测试钉钉机器人
176
+ 通过向导添加新账号,会交互式地提示输入账号 ID 和凭证:
90
177
 
91
178
  ```bash
92
- # 配置环境变量
93
- cp .env.example .env
94
- # 编辑 .env 填入 CLIENT_ID 和 CLIENT_SECRET
179
+ openclaw channels add
180
+ ```
181
+
182
+ ### 配置文件示例
95
183
 
96
- # 运行 demo
97
- npm run demo
184
+ 编辑 `~/.openclaw/openclaw.json`:
185
+
186
+ ```json
187
+ {
188
+ "channels": {
189
+ "ddingtalk": {
190
+ "enabled": true,
191
+ "accounts": {
192
+ "bot-hr": {
193
+ "name": "HR助手",
194
+ "clientId": "dingxxxxxxxx",
195
+ "clientSecret": "secret_1"
196
+ },
197
+ "bot-tech": {
198
+ "name": "技术支持",
199
+ "clientId": "dingyyyyyyyy",
200
+ "clientSecret": "secret_2"
201
+ }
202
+ },
203
+ "defaultAccount": "bot-hr"
204
+ }
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### 群组独立配置
210
+
211
+ 可以为特定群聊设置独立的权限和行为:
212
+
213
+ ```json
214
+ {
215
+ "accounts": {
216
+ "bot-hr": {
217
+ "enabled": true,
218
+ "clientId": "dingxxxxxxxx",
219
+ "clientSecret": "secret_1"
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### 单账号兼容
226
+
227
+ 如果只有一个机器人,无需使用 `accounts`,直接在顶层配置即可(兼容旧版格式):
228
+
229
+ ```json
230
+ {
231
+ "channels": {
232
+ "ddingtalk": {
233
+ "enabled": true,
234
+ "clientId": "your_app_key",
235
+ "clientSecret": "your_app_secret"
236
+ }
237
+ }
238
+ }
239
+ ```
240
+
241
+ ---
242
+
243
+ ## 多 Agent 路由
244
+
245
+ 通过 OpenClaw 的路由绑定(bindings)机制,可以将不同的账号、群聊、私聊分配给不同的 Agent 处理。
246
+
247
+ > 更多关于多 Agent 的概念和用法,请参阅 [OpenClaw 官方文档 - 多 Agent](https://docs.openclaw.ai/zh-CN/concepts/multi-agent)。
248
+
249
+ ### 按账号绑定 Agent
250
+
251
+ 使用命令行将不同钉钉账号绑定到不同的 Agent:
252
+
253
+ ```bash
254
+ # 将 bot-hr 账号绑定到 hr-agent
255
+ openclaw agents bind --agent hr-agent --bind ddingtalk:bot-hr
256
+
257
+ # 将 bot-tech 账号绑定到 tech-agent
258
+ openclaw agents bind --agent tech-agent --bind ddingtalk:bot-tech
259
+
260
+ # 将整个钉钉渠道(所有账号)绑定到默认 agent
261
+ openclaw agents bind --agent default-agent --bind ddingtalk
98
262
  ```
99
263
 
264
+ 查看当前绑定:
265
+
266
+ ```bash
267
+ openclaw agents bindings
268
+ ```
269
+
270
+ 解除绑定:
271
+
272
+ ```bash
273
+ openclaw agents unbind --agent hr-agent --bind ddingtalk:bot-hr
274
+ ```
275
+
276
+ ### 按群聊/私聊绑定 Agent
277
+
278
+ CLI 命令目前仅支持 `channel[:accountId]` 级别的绑定。如需将特定群聊或私聊绑定到不同 Agent,需要手动编辑 `~/.openclaw/openclaw.json` 的 `bindings` 配置:
279
+
280
+ ```json
281
+ {
282
+ "agents": {
283
+ "list": [
284
+ { "id": "hr-agent", "name": "HR助手" },
285
+ { "id": "tech-agent", "name": "技术支持" },
286
+ { "id": "general-agent", "name": "通用助手" }
287
+ ]
288
+ },
289
+ "bindings": [
290
+ {
291
+ "agentId": "tech-agent",
292
+ "comment": "技术交流群走技术支持 Agent",
293
+ "match": {
294
+ "channel": "ddingtalk",
295
+ "peer": {
296
+ "kind": "group",
297
+ "id": "cidTechGroup001"
298
+ }
299
+ }
300
+ },
301
+ {
302
+ "agentId": "hr-agent",
303
+ "comment": "张三的私聊走HR助手",
304
+ "match": {
305
+ "channel": "ddingtalk",
306
+ "peer": {
307
+ "kind": "direct",
308
+ "id": "user_zhangsan_staffId"
309
+ }
310
+ }
311
+ },
312
+ {
313
+ "agentId": "general-agent",
314
+ "comment": "bot-hr 账号的其他消息走通用助手",
315
+ "match": {
316
+ "channel": "ddingtalk",
317
+ "accountId": "bot-hr"
318
+ }
319
+ }
320
+ ]
321
+ }
322
+ ```
323
+
324
+ ---
325
+
326
+ ## 第三步:启动并测试
327
+
328
+ ### 1. 启动网关
329
+
330
+ ```bash
331
+ openclaw gateway --verbose
332
+ ```
333
+
334
+ ### 2. 发送测试消息
335
+
336
+ 在钉钉中找到您创建的机器人,即可正常对话。
337
+
338
+ ![钉钉对话](docs/images/dingtalk/dingtalk-chat.jpg)
339
+
340
+ ---
341
+
100
342
  ## 开发
101
343
 
102
344
  ```bash
103
345
  # 安装依赖
104
- npm install
346
+ pnpm install
105
347
 
106
348
  # 打包
107
- npm pack
349
+ pnpm pack
108
350
  ```
109
351
 
110
352
  ## 参考文档
111
353
 
354
+ - [OpenClaw 多 Agent 文档](https://docs.openclaw.ai/zh-CN/concepts/multi-agent)
112
355
  - [钉钉开放平台 - Stream 模式说明](https://opensource.dingtalk.com/developerpedia/docs/learn/stream/overview)
113
356
  - [钉钉开放平台 - 机器人接收消息](https://open.dingtalk.com/document/orgapp/robot-receive-message)
114
357
  - [钉钉开放平台 - 机器人发送消息](https://open.dingtalk.com/document/orgapp/robot-send-message)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@largezhou/ddingtalk",
3
- "version": "1.3.2",
3
+ "version": "1.4.0-beta.1",
4
4
  "description": "OpenClaw DingTalk (钉钉) channel plugin",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -18,9 +18,7 @@
18
18
  "openclaw.plugin.json",
19
19
  "README.md"
20
20
  ],
21
- "scripts": {
22
- "demo": "npx tsx demo/demo.ts"
23
- },
21
+ "scripts": {},
24
22
  "keywords": [
25
23
  "dingtalk",
26
24
  "openclaw",
@@ -31,20 +29,18 @@
31
29
  "author": "largezhou",
32
30
  "license": "MIT",
33
31
  "dependencies": {
34
- "@alicloud/dingtalk": "^2.2.38",
35
- "@alicloud/openapi-client": "^0.4.15",
36
- "@alicloud/tea-util": "^1.4.11",
37
- "dingtalk-stream": "^2.1.4"
32
+ "dingtalk-stream": "^2.1.4",
33
+ "zod": "^4.0.0"
38
34
  },
39
35
  "devDependencies": {
40
36
  "@types/node": "^20.10.0",
37
+ "dotenv": "^17.3.1",
41
38
  "openclaw": "*",
42
39
  "tsx": "^4.6.0",
43
40
  "typescript": "^5.3.0"
44
41
  },
45
42
  "peerDependencies": {
46
- "openclaw": "*",
47
- "zod": "^4.0.0"
43
+ "openclaw": "*"
48
44
  },
49
45
  "openclaw": {
50
46
  "extensions": [
package/src/accounts.ts CHANGED
@@ -1,82 +1,167 @@
1
- import { DEFAULT_ACCOUNT_ID, type OpenClawConfig } from "openclaw/plugin-sdk";
2
- import type { DingTalkConfig, ResolvedDingTalkAccount } from "./types.js";
1
+ import {
2
+ DEFAULT_ACCOUNT_ID,
3
+ normalizeAccountId,
4
+ type OpenClawConfig,
5
+ } from "openclaw/plugin-sdk";
6
+ import type {
7
+ DingTalkConfig,
8
+ DingTalkAccountConfig,
9
+ ResolvedDingTalkAccount,
10
+ } from "./types.js";
3
11
  import { PLUGIN_ID } from "./constants.js";
4
12
 
13
+ // ======================= Account List Helpers =======================
14
+
15
+ export { normalizeAccountId };
16
+
17
+ /**
18
+ * 列出所有钉钉账号 ID
19
+ *
20
+ * 方案 3 策略:顶层配置和 accounts 字典共存,不做迁移。
21
+ * - 顶层有 clientId → 视为 "default" 账号
22
+ * - accounts 字典中的 key → 各自独立的账号
23
+ * - 两者合并去重
24
+ */
25
+ export function listDingTalkAccountIds(cfg: OpenClawConfig): string[] {
26
+ const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
27
+ if (!dingtalkConfig) return [DEFAULT_ACCOUNT_ID];
28
+
29
+ const accountKeys = Object.keys(dingtalkConfig.accounts ?? {}).filter(Boolean);
30
+
31
+ // 顶层有凭据时,确保 "default" 在列表中
32
+ const hasTopLevel = Boolean(dingtalkConfig.clientId?.trim());
33
+ if (hasTopLevel && !accountKeys.includes(DEFAULT_ACCOUNT_ID)) {
34
+ accountKeys.push(DEFAULT_ACCOUNT_ID);
35
+ }
36
+
37
+ if (accountKeys.length === 0) {
38
+ return [DEFAULT_ACCOUNT_ID];
39
+ }
40
+
41
+ return accountKeys.slice().sort((a, b) => a.localeCompare(b));
42
+ }
43
+
5
44
  /**
6
- * 规范化账户 ID(始终返回默认账户 ID)
45
+ * 解析默认账号 ID
46
+ *
47
+ * 优先使用 defaultAccount 配置,否则返回 "default"
7
48
  */
8
- export function normalizeAccountId(_accountId?: string | null): string {
9
- return DEFAULT_ACCOUNT_ID;
49
+ export function resolveDefaultDingTalkAccountId(cfg: OpenClawConfig): string {
50
+ const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
51
+ return dingtalkConfig?.defaultAccount?.trim() || DEFAULT_ACCOUNT_ID;
10
52
  }
11
53
 
54
+ // ======================= Account Config Resolution =======================
55
+
12
56
  /**
13
- * 列出所有钉钉账户 ID(单账户,只返回默认账户)
57
+ * 获取指定 accountId 的账户级配置(从 accounts 字典中查找)
14
58
  */
15
- export function listDingTalkAccountIds(cfg: OpenClawConfig): string[] {
59
+ function resolveAccountConfig(
60
+ cfg: OpenClawConfig,
61
+ accountId: string,
62
+ ): DingTalkAccountConfig | undefined {
16
63
  const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
17
- if (!dingtalkConfig?.clientId) {
18
- return [];
64
+ const accounts = dingtalkConfig?.accounts;
65
+ if (!accounts || typeof accounts !== "object") {
66
+ return undefined;
19
67
  }
20
- return [DEFAULT_ACCOUNT_ID];
68
+ const normalized = normalizeAccountId(accountId);
69
+ // 精确匹配或大小写不敏感匹配
70
+ const key = Object.keys(accounts).find(
71
+ (k) => normalizeAccountId(k) === normalized,
72
+ );
73
+ return key ? accounts[key] : undefined;
21
74
  }
22
75
 
23
76
  /**
24
- * 解析默认钉钉账户 ID
77
+ * 合并顶层配置(作为默认值)和账户级配置
78
+ *
79
+ * 配置优先级:账户级 > 顶层
80
+ * 顶层的 accounts / defaultAccount 字段会被排除
25
81
  */
26
- export function resolveDefaultDingTalkAccountId(_cfg: OpenClawConfig): string {
27
- return DEFAULT_ACCOUNT_ID;
82
+ function mergeDingTalkAccountConfig(
83
+ cfg: OpenClawConfig,
84
+ accountId: string,
85
+ ): DingTalkAccountConfig {
86
+ const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
87
+ if (!dingtalkConfig) {
88
+ return {};
89
+ }
90
+
91
+ // 顶层字段作为 base(排除 accounts 和 defaultAccount)
92
+ const {
93
+ accounts: _accounts,
94
+ defaultAccount: _defaultAccount,
95
+ groups: channelGroups,
96
+ ...base
97
+ } = dingtalkConfig;
98
+
99
+ // 获取账户级配置
100
+ const account = resolveAccountConfig(cfg, accountId) ?? {};
101
+
102
+ // 多账户模式下,groups 不从顶层继承(每个账户应有自己的 groups)
103
+ const configuredAccountIds = Object.keys(dingtalkConfig.accounts ?? {});
104
+ const isMultiAccount = configuredAccountIds.length > 1;
105
+ const groups = account.groups ?? (isMultiAccount ? undefined : channelGroups);
106
+
107
+ return { ...base, ...account, groups };
28
108
  }
29
109
 
110
+ // ======================= Account Resolution =======================
111
+
30
112
  /**
31
- * 解析钉钉账户配置(单账户模式)
113
+ * 解析钉钉账户配置
114
+ *
115
+ * 支持两种模式:
116
+ * 1. 单账户(旧版兼容):顶层 clientId/clientSecret → accountId = "default"
117
+ * 2. 多账户:accounts 字典 → 顶层字段作为默认值,账户级字段覆盖
32
118
  */
33
119
  export function resolveDingTalkAccount(params: {
34
120
  cfg: OpenClawConfig;
35
- accountId?: string;
121
+ accountId?: string | null;
36
122
  }): ResolvedDingTalkAccount {
37
- const { cfg } = params;
38
- const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
123
+ const accountId = normalizeAccountId(params.accountId);
124
+ const dingtalkConfig = params.cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
125
+ const baseEnabled = dingtalkConfig?.enabled !== false;
39
126
 
40
- // 默认返回值
41
- const defaultResult: ResolvedDingTalkAccount = {
42
- accountId: DEFAULT_ACCOUNT_ID,
43
- enabled: false,
44
- clientId: "",
45
- clientSecret: "",
46
- tokenSource: "none",
47
- allowFrom: ["*"],
48
- groupPolicy: "open",
49
- groupAllowFrom: [],
50
- groups: {},
51
- };
52
-
53
- if (!dingtalkConfig) {
54
- return defaultResult;
55
- }
127
+ // 合并顶层 + 账户级配置
128
+ const merged = mergeDingTalkAccountConfig(params.cfg, accountId);
129
+ const accountEnabled = merged.enabled !== false;
130
+ const enabled = baseEnabled && accountEnabled;
56
131
 
57
132
  let clientId = "";
58
133
  let clientSecret = "";
59
134
  let tokenSource: ResolvedDingTalkAccount["tokenSource"] = "none";
60
135
 
61
- if (dingtalkConfig.clientId?.trim()) {
62
- clientId = dingtalkConfig.clientId.trim();
136
+ if (merged.clientId?.trim()) {
137
+ clientId = merged.clientId.trim();
63
138
  tokenSource = "config";
64
139
  }
65
-
66
- if (dingtalkConfig.clientSecret?.trim()) {
67
- clientSecret = dingtalkConfig.clientSecret.trim();
140
+ if (merged.clientSecret?.trim()) {
141
+ clientSecret = merged.clientSecret.trim();
68
142
  }
69
143
 
70
144
  return {
71
- accountId: DEFAULT_ACCOUNT_ID,
72
- name: dingtalkConfig.name,
73
- enabled: dingtalkConfig.enabled ?? true,
145
+ accountId,
146
+ name: merged.name?.trim() || undefined,
147
+ enabled,
74
148
  clientId,
75
149
  clientSecret,
76
150
  tokenSource,
77
- allowFrom: dingtalkConfig.allowFrom ?? ["*"],
78
- groupPolicy: dingtalkConfig.groupPolicy ?? "open",
79
- groupAllowFrom: dingtalkConfig.groupAllowFrom ?? [],
80
- groups: dingtalkConfig.groups ?? {},
151
+ allowFrom: merged.allowFrom ?? ["*"],
152
+ groupPolicy: merged.groupPolicy ?? "open",
153
+ groupAllowFrom: merged.groupAllowFrom ?? [],
154
+ groups: merged.groups ?? {},
81
155
  };
82
156
  }
157
+
158
+ /**
159
+ * 列出所有已启用的钉钉账户
160
+ */
161
+ export function listEnabledDingTalkAccounts(
162
+ cfg: OpenClawConfig,
163
+ ): ResolvedDingTalkAccount[] {
164
+ return listDingTalkAccountIds(cfg)
165
+ .map((accountId) => resolveDingTalkAccount({ cfg, accountId }))
166
+ .filter((account) => account.enabled);
167
+ }