@jeik/dingtalk-connector 0.8.21

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.
Files changed (154) hide show
  1. package/CHANGELOG.md +684 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +179 -0
  4. package/README.md +219 -0
  5. package/bin/dingtalk-connector.js +838 -0
  6. package/bin/wizard-config.mjs +94 -0
  7. package/dist/accounts-BAzdqkAV.mjs +268 -0
  8. package/dist/accounts-BQptOmgB.mjs +2 -0
  9. package/dist/chunk-upload-BBQgGtcZ.mjs +193 -0
  10. package/dist/chunk-upload-DaLXXZH3.mjs +2 -0
  11. package/dist/common-C8pYKU_y.mjs +2 -0
  12. package/dist/common-Dt9n6fQN.mjs +101 -0
  13. package/dist/connection-DHHFFNQJ.mjs +423 -0
  14. package/dist/entry-bundled.d.mts +16 -0
  15. package/dist/entry-bundled.mjs +31 -0
  16. package/dist/game-xiyou-CqHt-6Q1.mjs +4271 -0
  17. package/dist/gateway-methods-C4tcgI7P.mjs +771 -0
  18. package/dist/gateway-methods-Ci31A3vg.mjs +2 -0
  19. package/dist/http-client-CpnJHB89.mjs +2 -0
  20. package/dist/http-client-DFWZgO1n.mjs +33 -0
  21. package/dist/index.d.mts +193 -0
  22. package/dist/index.mjs +45 -0
  23. package/dist/logger-BmJkQkm1.mjs +2 -0
  24. package/dist/logger-mZ9OSbmD.mjs +58 -0
  25. package/dist/media-C_SVin7s.mjs +2 -0
  26. package/dist/media-cz72EVS3.mjs +509 -0
  27. package/dist/message-handler-DESzFFDc.mjs +1971 -0
  28. package/dist/messaging-B6l1sRvX.mjs +1044 -0
  29. package/dist/runtime-DUgpo5zC.mjs +1422 -0
  30. package/dist/session-DJ4jYqPv.mjs +114 -0
  31. package/dist/utils-Bjh4r_qS.mjs +4 -0
  32. package/dist/utils-CIfI_3Jh.mjs +63 -0
  33. package/dist/utils-legacy-CALCPP1t.mjs +230 -0
  34. package/dist/utils-legacy-CFYDBM4r.mjs +3 -0
  35. package/docs/DEAP_AGENT_GUIDE.en.md +115 -0
  36. package/docs/DEAP_AGENT_GUIDE.md +115 -0
  37. package/docs/DINGTALK_MANUAL_SETUP.md +50 -0
  38. package/docs/MULTI_AGENT_SETUP.md +306 -0
  39. package/docs/RELEASE_NOTES_V0.7.10.md +40 -0
  40. package/docs/RELEASE_NOTES_V0.7.2.md +143 -0
  41. package/docs/RELEASE_NOTES_V0.7.3.md +149 -0
  42. package/docs/RELEASE_NOTES_V0.7.4.md +206 -0
  43. package/docs/RELEASE_NOTES_V0.7.5.md +267 -0
  44. package/docs/RELEASE_NOTES_V0.7.6.md +219 -0
  45. package/docs/RELEASE_NOTES_V0.7.7.md +122 -0
  46. package/docs/RELEASE_NOTES_V0.7.8.md +101 -0
  47. package/docs/RELEASE_NOTES_V0.7.9.md +65 -0
  48. package/docs/RELEASE_NOTES_V0.8.0.md +53 -0
  49. package/docs/RELEASE_NOTES_V0.8.1.md +47 -0
  50. package/docs/RELEASE_NOTES_V0.8.10.md +49 -0
  51. package/docs/RELEASE_NOTES_V0.8.11.md +51 -0
  52. package/docs/RELEASE_NOTES_V0.8.12.md +63 -0
  53. package/docs/RELEASE_NOTES_V0.8.13-beta.0.md +69 -0
  54. package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
  55. package/docs/RELEASE_NOTES_V0.8.14.md +86 -0
  56. package/docs/RELEASE_NOTES_V0.8.16.md +40 -0
  57. package/docs/RELEASE_NOTES_V0.8.17.md +87 -0
  58. package/docs/RELEASE_NOTES_V0.8.18.md +64 -0
  59. package/docs/RELEASE_NOTES_V0.8.19.md +62 -0
  60. package/docs/RELEASE_NOTES_V0.8.2.md +55 -0
  61. package/docs/RELEASE_NOTES_V0.8.20.md +49 -0
  62. package/docs/RELEASE_NOTES_V0.8.3.md +63 -0
  63. package/docs/RELEASE_NOTES_V0.8.4.md +45 -0
  64. package/docs/RELEASE_NOTES_V0.8.7.md +49 -0
  65. package/docs/RELEASE_NOTES_V0.8.8.md +63 -0
  66. package/docs/RELEASE_NOTES_V0.8.9.md +81 -0
  67. package/docs/RELEASE_NOTES_v0.7.0.md +142 -0
  68. package/docs/RELEASE_NOTES_v0.7.1.md +74 -0
  69. package/docs/TROUBLESHOOTING.md +122 -0
  70. package/index.ts +77 -0
  71. package/openclaw.plugin.json +551 -0
  72. package/package.json +147 -0
  73. package/skills/dingtalk-channel-rules/SKILL.md +91 -0
  74. package/skills/dingtalk-troubleshoot/SKILL.md +93 -0
  75. package/skills/dws-cli/SKILL.md +129 -0
  76. package/skills/dws-cli/references/error-codes.md +95 -0
  77. package/skills/dws-cli/references/field-rules.md +105 -0
  78. package/skills/dws-cli/references/global-reference.md +104 -0
  79. package/skills/dws-cli/references/intent-guide.md +114 -0
  80. package/skills/dws-cli/references/products/aitable.md +452 -0
  81. package/skills/dws-cli/references/products/attendance.md +93 -0
  82. package/skills/dws-cli/references/products/calendar.md +217 -0
  83. package/skills/dws-cli/references/products/chat.md +292 -0
  84. package/skills/dws-cli/references/products/contact.md +108 -0
  85. package/skills/dws-cli/references/products/ding.md +57 -0
  86. package/skills/dws-cli/references/products/report.md +162 -0
  87. package/skills/dws-cli/references/products/simple.md +128 -0
  88. package/skills/dws-cli/references/products/todo.md +138 -0
  89. package/skills/dws-cli/references/products/workbench.md +39 -0
  90. package/skills/dws-cli/references/recovery-guide.md +94 -0
  91. package/src/channel.ts +588 -0
  92. package/src/config/accounts.ts +242 -0
  93. package/src/config/schema.ts +180 -0
  94. package/src/core/connection.ts +741 -0
  95. package/src/core/message-handler.ts +1788 -0
  96. package/src/core/provider.ts +111 -0
  97. package/src/core/state.ts +54 -0
  98. package/src/device-auth-config.ts +14 -0
  99. package/src/device-auth.ts +197 -0
  100. package/src/directory.ts +95 -0
  101. package/src/docs.ts +293 -0
  102. package/src/game-xiyou/achievement-engine.ts +252 -0
  103. package/src/game-xiyou/bounty-system.ts +315 -0
  104. package/src/game-xiyou/commands.ts +223 -0
  105. package/src/game-xiyou/drop-engine.ts +241 -0
  106. package/src/game-xiyou/encounter-system.ts +135 -0
  107. package/src/game-xiyou/escape-engine.ts +164 -0
  108. package/src/game-xiyou/exp-calculator.ts +139 -0
  109. package/src/game-xiyou/index.ts +479 -0
  110. package/src/game-xiyou/level-system.ts +91 -0
  111. package/src/game-xiyou/monster-pool.ts +180 -0
  112. package/src/game-xiyou/pity-counter.ts +114 -0
  113. package/src/game-xiyou/random-event-engine.ts +648 -0
  114. package/src/game-xiyou/renderer.ts +679 -0
  115. package/src/game-xiyou/storage.ts +218 -0
  116. package/src/game-xiyou/treasure-system.ts +105 -0
  117. package/src/game-xiyou/types.ts +582 -0
  118. package/src/game-xiyou/uid-resolver.ts +49 -0
  119. package/src/gateway-methods.ts +740 -0
  120. package/src/onboarding.ts +553 -0
  121. package/src/policy.ts +32 -0
  122. package/src/probe.ts +210 -0
  123. package/src/reply-dispatcher.ts +874 -0
  124. package/src/runtime.ts +32 -0
  125. package/src/sdk/helpers.ts +322 -0
  126. package/src/sdk/types.ts +519 -0
  127. package/src/secret-input.ts +19 -0
  128. package/src/services/media/audio.ts +54 -0
  129. package/src/services/media/chunk-upload.ts +296 -0
  130. package/src/services/media/common.ts +155 -0
  131. package/src/services/media/file.ts +75 -0
  132. package/src/services/media/image.ts +81 -0
  133. package/src/services/media/index.ts +10 -0
  134. package/src/services/media/video.ts +162 -0
  135. package/src/services/media.ts +1143 -0
  136. package/src/services/messaging/card.ts +604 -0
  137. package/src/services/messaging/index.ts +18 -0
  138. package/src/services/messaging/mentions.ts +267 -0
  139. package/src/services/messaging/send.ts +141 -0
  140. package/src/services/messaging.ts +1191 -0
  141. package/src/services/reply-markers.ts +55 -0
  142. package/src/targets.ts +45 -0
  143. package/src/types/index.ts +59 -0
  144. package/src/types/pdf-parse.d.ts +3 -0
  145. package/src/utils/agent.ts +63 -0
  146. package/src/utils/async.ts +51 -0
  147. package/src/utils/constants.ts +27 -0
  148. package/src/utils/http-client.ts +38 -0
  149. package/src/utils/index.ts +8 -0
  150. package/src/utils/logger.ts +78 -0
  151. package/src/utils/session.ts +147 -0
  152. package/src/utils/token.ts +93 -0
  153. package/src/utils/utils-legacy.ts +454 -0
  154. package/tsconfig.json +20 -0
@@ -0,0 +1,242 @@
1
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId , normalizeResolvedSecretInputString, normalizeSecretInputString } from "../sdk/helpers.ts";
2
+ import type { ClawdbotConfig } from "openclaw/plugin-sdk";
3
+ import type {
4
+ DingtalkConfig,
5
+ DingtalkAccountConfig,
6
+ DingtalkDefaultAccountSelectionSource,
7
+ ResolvedDingtalkAccount,
8
+ } from "../types/index.ts";
9
+
10
+ /**
11
+ * List all configured account IDs from the accounts field.
12
+ */
13
+ function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
14
+ const accounts = (cfg.channels?.["dingtalk-connector"] as DingtalkConfig)?.accounts;
15
+ if (!accounts || typeof accounts !== "object") {
16
+ return [];
17
+ }
18
+ return Object.keys(accounts).filter(Boolean);
19
+ }
20
+
21
+ /**
22
+ * List all DingTalk account IDs.
23
+ * If no accounts are configured, returns [DEFAULT_ACCOUNT_ID] for backward compatibility.
24
+ */
25
+ export function listDingtalkAccountIds(cfg: ClawdbotConfig): string[] {
26
+ const ids = listConfiguredAccountIds(cfg);
27
+ if (ids.length === 0) {
28
+ // Backward compatibility: no accounts configured, use default
29
+ return [DEFAULT_ACCOUNT_ID];
30
+ }
31
+ return [...ids].sort((a, b) => a.localeCompare(b));
32
+ }
33
+
34
+ /**
35
+ * Resolve the default account selection and its source.
36
+ */
37
+ export function resolveDefaultDingtalkAccountSelection(cfg: ClawdbotConfig): {
38
+ accountId: string;
39
+ source: DingtalkDefaultAccountSelectionSource;
40
+ } {
41
+ const preferredRaw = (cfg.channels?.["dingtalk-connector"] as DingtalkConfig | undefined)?.defaultAccount?.trim();
42
+ const preferred = preferredRaw ? normalizeAccountId(preferredRaw) : undefined;
43
+ if (preferred) {
44
+ return {
45
+ accountId: preferred,
46
+ source: "explicit-default",
47
+ };
48
+ }
49
+ const ids = listDingtalkAccountIds(cfg);
50
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
51
+ return {
52
+ accountId: DEFAULT_ACCOUNT_ID,
53
+ source: "mapped-default",
54
+ };
55
+ }
56
+ return {
57
+ accountId: ids[0] ?? DEFAULT_ACCOUNT_ID,
58
+ source: "fallback",
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Resolve the default account ID.
64
+ */
65
+ export function resolveDefaultDingtalkAccountId(cfg: ClawdbotConfig): string {
66
+ return resolveDefaultDingtalkAccountSelection(cfg).accountId;
67
+ }
68
+
69
+ /**
70
+ * Get the raw account-specific config.
71
+ */
72
+ function resolveAccountConfig(
73
+ cfg: ClawdbotConfig,
74
+ accountId: string,
75
+ ): DingtalkAccountConfig | undefined {
76
+ const accounts = (cfg.channels?.["dingtalk-connector"] as DingtalkConfig)?.accounts;
77
+ if (!accounts || typeof accounts !== "object") {
78
+ return undefined;
79
+ }
80
+ return accounts[accountId];
81
+ }
82
+
83
+ /**
84
+ * Merge top-level config with account-specific config.
85
+ * Account-specific fields override top-level fields.
86
+ */
87
+ function mergeDingtalkAccountConfig(cfg: ClawdbotConfig, accountId: string): DingtalkConfig {
88
+ const dingtalkCfg = cfg.channels?.["dingtalk-connector"] as DingtalkConfig | undefined;
89
+
90
+ // Extract base config (exclude accounts field to avoid recursion)
91
+ const { accounts: _ignored, defaultAccount: _ignoredDefaultAccount, ...base } = dingtalkCfg ?? {};
92
+
93
+ // Get account-specific overrides
94
+ const account = resolveAccountConfig(cfg, accountId) ?? {};
95
+
96
+ // Merge: account config overrides base config
97
+ return { ...base, ...account } as DingtalkConfig;
98
+ }
99
+
100
+ /**
101
+ * Resolve DingTalk credentials from a config.
102
+ */
103
+ export function resolveDingtalkCredentials(cfg?: DingtalkConfig): {
104
+ clientId: string;
105
+ clientSecret: string;
106
+ } | null;
107
+ export function resolveDingtalkCredentials(
108
+ cfg: DingtalkConfig | undefined,
109
+ options: { allowUnresolvedSecretRef?: boolean },
110
+ ): {
111
+ clientId: string;
112
+ clientSecret: string;
113
+ } | null;
114
+ export function resolveDingtalkCredentials(
115
+ cfg?: DingtalkConfig,
116
+ options?: { allowUnresolvedSecretRef?: boolean },
117
+ ): {
118
+ clientId: string;
119
+ clientSecret: string;
120
+ } | null {
121
+ const normalizeString = (value: unknown): string | undefined => {
122
+ if (typeof value === "number") {
123
+ return String(value);
124
+ }
125
+ if (typeof value !== "string") {
126
+ return undefined;
127
+ }
128
+ const trimmed = value.trim();
129
+ return trimmed ? trimmed : undefined;
130
+ };
131
+
132
+ const resolveSecretLike = (value: unknown, path: string): string | undefined => {
133
+ // Missing credential: treat as not configured (no exception).
134
+ // This path is used in non-onboarding contexts (e.g. channel listing/status),
135
+ // so we must not throw when credentials are absent.
136
+ if (value === undefined || value === null) {
137
+ return undefined;
138
+ }
139
+
140
+ const asString = normalizeString(value);
141
+ if (asString) {
142
+ return asString;
143
+ }
144
+
145
+ // In relaxed/onboarding paths only: allow direct env SecretRef reads for UX.
146
+ // Default resolution path must preserve unresolved-ref diagnostics/policy semantics.
147
+ if (options?.allowUnresolvedSecretRef && typeof value === "object" && value !== null) {
148
+ const rec = value as Record<string, unknown>;
149
+ const source = normalizeString(rec.source)?.toLowerCase();
150
+ const id = normalizeString(rec.id);
151
+ if (source === "env" && id) {
152
+ const envValue = normalizeString(process.env[id]);
153
+ if (envValue) {
154
+ return envValue;
155
+ }
156
+ }
157
+ }
158
+
159
+ if (options?.allowUnresolvedSecretRef) {
160
+ return normalizeSecretInputString(value);
161
+ }
162
+ return normalizeResolvedSecretInputString({ value, path });
163
+ };
164
+
165
+ const clientId = resolveSecretLike(cfg?.clientId, "channels.dingtalk-connector.clientId");
166
+ const clientSecret = resolveSecretLike(cfg?.clientSecret, "channels.dingtalk-connector.clientSecret");
167
+
168
+ if (!clientId || !clientSecret) {
169
+ return null;
170
+ }
171
+ return {
172
+ clientId,
173
+ clientSecret,
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Resolve a complete DingTalk account with merged config.
179
+ */
180
+ export function resolveDingtalkAccount(params: {
181
+ cfg: ClawdbotConfig;
182
+ accountId?: string | null;
183
+ }): ResolvedDingtalkAccount {
184
+ const hasExplicitAccountId =
185
+ typeof params.accountId === "string" && params.accountId.trim() !== "";
186
+ const defaultSelection = hasExplicitAccountId
187
+ ? null
188
+ : resolveDefaultDingtalkAccountSelection(params.cfg);
189
+ const accountId = hasExplicitAccountId
190
+ ? normalizeAccountId(params.accountId ?? "")
191
+ : (defaultSelection?.accountId ?? DEFAULT_ACCOUNT_ID);
192
+ const selectionSource = hasExplicitAccountId
193
+ ? "explicit"
194
+ : (defaultSelection?.source ?? "fallback");
195
+ const dingtalkCfg = params.cfg.channels?.["dingtalk-connector"] as DingtalkConfig | undefined;
196
+
197
+ // Base enabled state (top-level)
198
+ const baseEnabled = dingtalkCfg?.enabled !== false;
199
+
200
+ // Merge configs
201
+ const merged = mergeDingtalkAccountConfig(params.cfg, accountId);
202
+
203
+ // Account-level enabled state
204
+ const accountEnabled = merged.enabled !== false;
205
+ const enabled = baseEnabled && accountEnabled;
206
+
207
+ // Resolve credentials from merged config
208
+ const creds = resolveDingtalkCredentials(merged);
209
+ const accountName = (merged as DingtalkAccountConfig).name;
210
+
211
+ return {
212
+ accountId,
213
+ selectionSource,
214
+ enabled,
215
+ configured: Boolean(creds),
216
+ name: typeof accountName === "string" ? accountName.trim() || undefined : undefined,
217
+ clientId: creds?.clientId,
218
+ clientSecret: creds?.clientSecret,
219
+ config: merged,
220
+ };
221
+ }
222
+
223
+ /**
224
+ * List all enabled and configured accounts.
225
+ * Deduplicates by clientId to avoid creating multiple connections with the same credentials.
226
+ */
227
+ export function listEnabledDingtalkAccounts(cfg: ClawdbotConfig): ResolvedDingtalkAccount[] {
228
+ const accounts = listDingtalkAccountIds(cfg)
229
+ .map((accountId) => resolveDingtalkAccount({ cfg, accountId }))
230
+ .filter((account) => account.enabled && account.configured);
231
+
232
+ // Deduplicate by clientId to avoid multiple connections with same credentials
233
+ const seen = new Set<string>();
234
+ return accounts.filter((account) => {
235
+ if (!account.clientId) return true;
236
+ if (seen.has(account.clientId)) {
237
+ return false;
238
+ }
239
+ seen.add(account.clientId);
240
+ return true;
241
+ });
242
+ }
@@ -0,0 +1,180 @@
1
+ import { normalizeAccountId } from "../sdk/helpers.ts";
2
+ import { z } from "zod";
3
+ export { z };
4
+ import { buildSecretInputSchema, hasConfiguredSecretInput } from "../secret-input.ts";
5
+
6
+ const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]);
7
+ const GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
8
+
9
+ const ToolPolicySchema = z
10
+ .object({
11
+ allow: z.array(z.string()).optional(),
12
+ deny: z.array(z.string()).optional(),
13
+ })
14
+ .strict()
15
+ .optional();
16
+
17
+ /**
18
+ * Group session scope for routing DingTalk group messages.
19
+ * - "group" (default): one session per group chat
20
+ * - "group_sender": one session per (group + sender)
21
+ */
22
+ const GroupSessionScopeSchema = z
23
+ .enum(["group", "group_sender"])
24
+ .optional();
25
+
26
+ /**
27
+ * Group reply mode for DingTalk group messages.
28
+ * - "aicard" (default): use AI Card with streaming support
29
+ * - "text": use plain text reply (supports @bot mentions, no AI Card)
30
+ * - "markdown": use markdown reply (supports @bot mentions, no AI Card)
31
+ *
32
+ * When set to "text" or "markdown", group messages will be sent as
33
+ * plain text/markdown instead of AI Card. This enables bots to @mention
34
+ * each other in multi-Agent group scenarios.
35
+ *
36
+ * ⚠️ Warning: enabling text/markdown mode disables AI Card in group chats.
37
+ */
38
+ const GroupReplyModeSchema = z
39
+ .enum(["aicard", "text", "markdown"])
40
+ .optional();
41
+
42
+ /**
43
+ * Dingtalk tools configuration.
44
+ * Controls which tool categories are enabled.
45
+ */
46
+ const DingtalkToolsConfigSchema = z
47
+ .object({
48
+ docs: z.boolean().optional(), // Document operations (default: true)
49
+ media: z.boolean().optional(), // Media upload operations (default: true)
50
+ })
51
+ .strict()
52
+ .optional();
53
+
54
+ export const DingtalkGroupSchema = z
55
+ .object({
56
+ requireMention: z.boolean().optional(),
57
+ tools: ToolPolicySchema,
58
+ enabled: z.boolean().optional(),
59
+ allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
60
+ systemPrompt: z.string().optional(),
61
+ groupSessionScope: GroupSessionScopeSchema,
62
+ })
63
+ .strict();
64
+
65
+ const DingtalkSharedConfigShape = {
66
+ dmPolicy: DmPolicySchema.optional(),
67
+ allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
68
+ groupPolicy: GroupPolicySchema.optional(),
69
+ groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
70
+ requireMention: z.boolean().optional(),
71
+ groups: z.record(z.string(), DingtalkGroupSchema.optional()).optional(),
72
+ historyLimit: z.number().int().min(0).optional(),
73
+ textChunkLimit: z.number().int().positive().optional(),
74
+ mediaMaxMb: z.number().positive().optional(),
75
+ tools: DingtalkToolsConfigSchema,
76
+ typingIndicator: z.boolean().optional(),
77
+ resolveSenderNames: z.boolean().optional(),
78
+ separateSessionByConversation: z.boolean().optional(),
79
+ sharedMemoryAcrossConversations: z.boolean().optional(),
80
+ groupSessionScope: GroupSessionScopeSchema,
81
+ asyncMode: z.boolean().optional(),
82
+ ackText: z.string().optional(),
83
+ endpoint: z.string().optional(), // DWClient gateway endpoint
84
+ debug: z.boolean().optional(), // DWClient debug mode
85
+ enableMediaUpload: z.boolean().optional(),
86
+ systemPrompt: z.string().optional(),
87
+ groupReplyMode: GroupReplyModeSchema,
88
+ /** AI Card 模板 ID,不填则使用官方默认模板 */
89
+ cardTemplateId: z.string().optional(),
90
+ /** AI Card 最终内容变量名,对应卡片模板中的变量字段,不填默认 msgContent */
91
+ cardContentVar: z.string().optional().default("msgContent"),
92
+ /** AI Card 中间过程变量名,不填默认 cardContentVar 同值 */
93
+ cardProcessVar: z.string().optional(),
94
+ /** AI Card 工具输出变量名,不填则不写入工具输出 */
95
+ cardToolVar: z.string().optional(),
96
+ };
97
+
98
+ /**
99
+ * Per-account configuration.
100
+ * All fields are optional - missing fields inherit from top-level config.
101
+ */
102
+ export const DingtalkAccountConfigSchema = z
103
+ .object({
104
+ enabled: z.boolean().optional(),
105
+ name: z.string().optional(), // Display name for this account
106
+ clientId: z.union([z.string(), z.number()]).optional(),
107
+ clientSecret: buildSecretInputSchema().optional(),
108
+ /**
109
+ * Encrypted DingTalk identity of this bot, used by other agents to @-mention
110
+ * this bot in group messages. Fill from log line `[BotIdentity] chatbotUserId=...`
111
+ * after the bot has received at least one group/DM message.
112
+ */
113
+ chatbotUserId: z.string().optional(),
114
+ chatbotCorpId: z.string().optional(),
115
+ ...DingtalkSharedConfigShape,
116
+ })
117
+ .strict();
118
+
119
+ /**
120
+ * Base schema (ZodObject) without superRefine, used for JSON Schema generation (Web UI).
121
+ * superRefine turns the schema into ZodEffects which is not compatible with buildChannelConfigSchema.
122
+ */
123
+ export const DingtalkConfigBaseSchema = z
124
+ .object({
125
+ enabled: z.boolean().optional(),
126
+ defaultAccount: z.string().optional(),
127
+ // Top-level credentials (backward compatible for single-account mode)
128
+ clientId: z.union([z.string(), z.number()]).optional(),
129
+ clientSecret: buildSecretInputSchema().optional(),
130
+ ...DingtalkSharedConfigShape,
131
+ dmPolicy: DmPolicySchema.optional().default("open"),
132
+ groupPolicy: GroupPolicySchema.optional().default("open"),
133
+ requireMention: z.boolean().optional().default(true),
134
+ separateSessionByConversation: z.boolean().optional().default(true),
135
+ sharedMemoryAcrossConversations: z.boolean().optional().default(false),
136
+ groupSessionScope: GroupSessionScopeSchema.optional().default("group"),
137
+ // Multi-account configuration
138
+ accounts: z.record(z.string(), DingtalkAccountConfigSchema.optional()).optional(),
139
+ })
140
+ .strict();
141
+
142
+ export const DingtalkConfigSchema = DingtalkConfigBaseSchema.superRefine((value, ctx) => {
143
+ const defaultAccount = value.defaultAccount?.trim();
144
+ if (defaultAccount && value.accounts && Object.keys(value.accounts).length > 0) {
145
+ const normalizedDefaultAccount = normalizeAccountId(defaultAccount);
146
+ if (!Object.prototype.hasOwnProperty.call(value.accounts, normalizedDefaultAccount)) {
147
+ ctx.addIssue({
148
+ code: z.ZodIssueCode.custom,
149
+ path: ["defaultAccount"],
150
+ message: `channels.dingtalk-connector.defaultAccount="${defaultAccount}" does not match a configured account key`,
151
+ });
152
+ }
153
+ }
154
+
155
+ // Validate dmPolicy and allowFrom consistency
156
+ if (value.dmPolicy === "allowlist") {
157
+ const allowFrom = value.allowFrom ?? [];
158
+ if (allowFrom.length === 0) {
159
+ ctx.addIssue({
160
+ code: z.ZodIssueCode.custom,
161
+ path: ["allowFrom"],
162
+ message:
163
+ 'channels.dingtalk-connector.dmPolicy="allowlist" requires channels.dingtalk-connector.allowFrom to contain at least one entry',
164
+ });
165
+ }
166
+ }
167
+
168
+ // Validate groupPolicy and groupAllowFrom consistency
169
+ if (value.groupPolicy === "allowlist") {
170
+ const groupAllowFrom = value.groupAllowFrom ?? [];
171
+ if (groupAllowFrom.length === 0) {
172
+ ctx.addIssue({
173
+ code: z.ZodIssueCode.custom,
174
+ path: ["groupAllowFrom"],
175
+ message:
176
+ 'channels.dingtalk-connector.groupPolicy="allowlist" requires channels.dingtalk-connector.groupAllowFrom to contain at least one entry',
177
+ });
178
+ }
179
+ }
180
+ });