@soimy/dingtalk 3.2.0 → 3.3.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/LICENSE +21 -0
- package/README.md +657 -32
- package/index.ts +62 -0
- package/package.json +3 -1
- package/src/access-control.ts +18 -0
- package/src/ack-reaction-classifier.ts +62 -0
- package/src/ack-reaction-service.ts +135 -0
- package/src/attachment-text-extractor.ts +147 -0
- package/src/card-callback-service.ts +119 -0
- package/src/card-draft-controller.ts +114 -0
- package/src/card-service.ts +776 -24
- package/src/channel.ts +408 -135
- package/src/config-schema.ts +40 -4
- package/src/config.ts +136 -4
- package/src/connection-manager.ts +354 -47
- package/src/dedup.ts +1 -0
- package/src/docs-service.ts +198 -0
- package/src/draft-stream-loop.ts +119 -0
- package/src/feedback-learning-service.ts +643 -0
- package/src/feedback-learning-store.ts +543 -0
- package/src/group-members-store.ts +48 -14
- package/src/inbound-handler.ts +1100 -159
- package/src/learning-command-service.ts +339 -0
- package/src/media-utils.ts +94 -50
- package/src/message-utils.ts +301 -39
- package/src/onboarding.ts +38 -0
- package/src/peer-id-registry.ts +102 -0
- package/src/persistence-store.ts +131 -0
- package/src/quote-journal.ts +242 -0
- package/src/quoted-file-service.ts +385 -0
- package/src/quoted-msg-cache.ts +226 -0
- package/src/send-service.ts +177 -43
- package/src/session-command-service.ts +147 -0
- package/src/session-lock.ts +2 -0
- package/src/session-peer-store.ts +77 -0
- package/src/session-routing.ts +33 -0
- package/src/types.ts +152 -24
- package/src/utils.ts +231 -12
package/src/types.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
ChannelGatewayContext as SDKChannelGatewayContext,
|
|
18
18
|
ChannelPlugin as SDKChannelPlugin,
|
|
19
19
|
} from "openclaw/plugin-sdk";
|
|
20
|
+
import { mergeAccountWithDefaults } from "./config";
|
|
20
21
|
|
|
21
22
|
export interface DingtalkPluginModule {
|
|
22
23
|
id: string;
|
|
@@ -41,7 +42,8 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
41
42
|
groupPolicy?: "open" | "allowlist";
|
|
42
43
|
allowFrom?: string[];
|
|
43
44
|
mediaUrlAllowlist?: string[];
|
|
44
|
-
|
|
45
|
+
journalTTLDays?: number;
|
|
46
|
+
ackReaction?: string;
|
|
45
47
|
debug?: boolean;
|
|
46
48
|
messageType?: "markdown" | "card";
|
|
47
49
|
cardTemplateId?: string;
|
|
@@ -55,14 +57,36 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
55
57
|
reconnectJitter?: number;
|
|
56
58
|
/** Maximum number of runtime reconnect cycles before giving up (default: 10) */
|
|
57
59
|
maxReconnectCycles?: number;
|
|
60
|
+
/** Maximum time (ms) for a single reconnect cycle before giving up and starting a new cycle (default: 50000) */
|
|
61
|
+
reconnectDeadlineMs?: number;
|
|
58
62
|
/** Whether to use ConnectionManager; when false, use DWClient native keepAlive+autoReconnect */
|
|
59
63
|
useConnectionManager?: boolean;
|
|
60
64
|
/** Maximum inbound media file size in MB (overrides runtime default when set) */
|
|
61
65
|
mediaMaxMb?: number;
|
|
66
|
+
/** Whether to enable underlying stream keepAlive heartbeat; defaults to !useConnectionManager when omitted */
|
|
67
|
+
keepAlive?: boolean;
|
|
68
|
+
/** Bypass system/global HTTP(S) proxy for DingTalk outbound send/card/upload APIs */
|
|
69
|
+
bypassProxyForSend?: boolean;
|
|
62
70
|
proactivePermissionHint?: {
|
|
63
71
|
enabled?: boolean;
|
|
64
72
|
cooldownHours?: number;
|
|
65
73
|
};
|
|
74
|
+
/** Enable real-time card streaming (default false, true = 300ms throttled per-token updates) */
|
|
75
|
+
cardRealTimeStream?: boolean;
|
|
76
|
+
/** AICard degrade duration in milliseconds after trigger errors (default 30m) */
|
|
77
|
+
aicardDegradeMs?: number;
|
|
78
|
+
/** Enable local learning loop (events/reflections/session notes/global rules) */
|
|
79
|
+
learningEnabled?: boolean;
|
|
80
|
+
/** Auto-apply generated reflections into session notes/global rules (default false) */
|
|
81
|
+
learningAutoApply?: boolean;
|
|
82
|
+
/** Session learning note TTL in milliseconds (default 6h) */
|
|
83
|
+
learningNoteTtlMs?: number;
|
|
84
|
+
/** @deprecated Use learningEnabled */
|
|
85
|
+
feedbackLearningEnabled?: boolean;
|
|
86
|
+
/** @deprecated Use learningAutoApply */
|
|
87
|
+
feedbackLearningAutoApply?: boolean;
|
|
88
|
+
/** @deprecated Use learningNoteTtlMs */
|
|
89
|
+
feedbackLearningNoteTtlMs?: number;
|
|
66
90
|
}
|
|
67
91
|
|
|
68
92
|
/**
|
|
@@ -80,7 +104,8 @@ export interface DingTalkChannelConfig {
|
|
|
80
104
|
groupPolicy?: "open" | "allowlist";
|
|
81
105
|
allowFrom?: string[];
|
|
82
106
|
mediaUrlAllowlist?: string[];
|
|
83
|
-
|
|
107
|
+
journalTTLDays?: number;
|
|
108
|
+
ackReaction?: string;
|
|
84
109
|
debug?: boolean;
|
|
85
110
|
messageType?: "markdown" | "card";
|
|
86
111
|
cardTemplateId?: string;
|
|
@@ -93,14 +118,36 @@ export interface DingTalkChannelConfig {
|
|
|
93
118
|
reconnectJitter?: number;
|
|
94
119
|
/** Maximum number of runtime reconnect cycles before giving up (default: 10) */
|
|
95
120
|
maxReconnectCycles?: number;
|
|
121
|
+
/** Maximum time (ms) for a single reconnect cycle before giving up and starting a new cycle (default: 50000) */
|
|
122
|
+
reconnectDeadlineMs?: number;
|
|
96
123
|
/** Whether to use ConnectionManager; when false, use DWClient native keepAlive+autoReconnect */
|
|
97
124
|
useConnectionManager?: boolean;
|
|
98
125
|
/** Maximum inbound media file size in MB (overrides runtime default when set) */
|
|
99
126
|
mediaMaxMb?: number;
|
|
127
|
+
/** Whether to enable underlying stream keepAlive heartbeat; defaults to !useConnectionManager when omitted */
|
|
128
|
+
keepAlive?: boolean;
|
|
129
|
+
/** Bypass system/global HTTP(S) proxy for DingTalk outbound send/card/upload APIs */
|
|
130
|
+
bypassProxyForSend?: boolean;
|
|
100
131
|
proactivePermissionHint?: {
|
|
101
132
|
enabled?: boolean;
|
|
102
133
|
cooldownHours?: number;
|
|
103
134
|
};
|
|
135
|
+
/** Enable real-time card streaming (default false, true = 300ms throttled per-token updates) */
|
|
136
|
+
cardRealTimeStream?: boolean;
|
|
137
|
+
/** AICard degrade duration in milliseconds after trigger errors (default 30m) */
|
|
138
|
+
aicardDegradeMs?: number;
|
|
139
|
+
/** Enable local learning loop (events/reflections/session notes/global rules) */
|
|
140
|
+
learningEnabled?: boolean;
|
|
141
|
+
/** Auto-apply generated reflections into session notes/global rules (default false) */
|
|
142
|
+
learningAutoApply?: boolean;
|
|
143
|
+
/** Session learning note TTL in milliseconds (default 6h) */
|
|
144
|
+
learningNoteTtlMs?: number;
|
|
145
|
+
/** @deprecated Use learningEnabled */
|
|
146
|
+
feedbackLearningEnabled?: boolean;
|
|
147
|
+
/** @deprecated Use learningAutoApply */
|
|
148
|
+
feedbackLearningAutoApply?: boolean;
|
|
149
|
+
/** @deprecated Use learningNoteTtlMs */
|
|
150
|
+
feedbackLearningNoteTtlMs?: number;
|
|
104
151
|
}
|
|
105
152
|
|
|
106
153
|
/**
|
|
@@ -145,6 +192,14 @@ export interface MediaFile {
|
|
|
145
192
|
mimeType: string;
|
|
146
193
|
}
|
|
147
194
|
|
|
195
|
+
export interface DocInfo {
|
|
196
|
+
docId: string;
|
|
197
|
+
title: string;
|
|
198
|
+
docType: string;
|
|
199
|
+
creatorId?: string;
|
|
200
|
+
updatedAt?: number | string;
|
|
201
|
+
}
|
|
202
|
+
|
|
148
203
|
/**
|
|
149
204
|
* DingTalk incoming message (Stream mode)
|
|
150
205
|
*/
|
|
@@ -156,15 +211,22 @@ export interface DingTalkInboundMessage {
|
|
|
156
211
|
content: string;
|
|
157
212
|
isReplyMsg?: boolean; // 是否是回复消息
|
|
158
213
|
repliedMsg?: {
|
|
159
|
-
|
|
214
|
+
msgType?: string;
|
|
215
|
+
msgId?: string;
|
|
216
|
+
senderId?: string;
|
|
217
|
+
createdAt?: number;
|
|
160
218
|
content?: {
|
|
161
219
|
text?: string;
|
|
220
|
+
downloadCode?: string;
|
|
221
|
+
biz_custom_action_url?: string;
|
|
162
222
|
richText?: Array<{
|
|
163
223
|
msgType?: string;
|
|
164
224
|
type?: string;
|
|
165
225
|
content?: string;
|
|
226
|
+
text?: string;
|
|
166
227
|
code?: string;
|
|
167
228
|
atName?: string;
|
|
229
|
+
downloadCode?: string;
|
|
168
230
|
}>;
|
|
169
231
|
};
|
|
170
232
|
};
|
|
@@ -173,13 +235,16 @@ export interface DingTalkInboundMessage {
|
|
|
173
235
|
downloadCode?: string;
|
|
174
236
|
fileName?: string;
|
|
175
237
|
recognition?: string;
|
|
238
|
+
spaceId?: string;
|
|
239
|
+
fileId?: string;
|
|
240
|
+
biz_custom_action_url?: string;
|
|
176
241
|
richText?: Array<{
|
|
177
242
|
type: string;
|
|
178
243
|
text?: string;
|
|
179
244
|
atName?: string;
|
|
180
|
-
downloadCode?: string;
|
|
245
|
+
downloadCode?: string;
|
|
181
246
|
}>;
|
|
182
|
-
quoteContent?: string;
|
|
247
|
+
quoteContent?: string;
|
|
183
248
|
};
|
|
184
249
|
// Legacy 引用格式
|
|
185
250
|
quoteMessage?: {
|
|
@@ -191,6 +256,7 @@ export interface DingTalkInboundMessage {
|
|
|
191
256
|
};
|
|
192
257
|
// 富媒体引用,仅有消息ID的情况(包括手机端和PC端)
|
|
193
258
|
originalMsgId?: string;
|
|
259
|
+
originalProcessQueryKey?: string;
|
|
194
260
|
conversationType: string;
|
|
195
261
|
conversationId: string;
|
|
196
262
|
conversationTitle?: string;
|
|
@@ -201,14 +267,39 @@ export interface DingTalkInboundMessage {
|
|
|
201
267
|
sessionWebhook: string;
|
|
202
268
|
}
|
|
203
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Quoted/reply message metadata extracted from repliedMsg.
|
|
272
|
+
* Populated when isReplyMsg is true; downstream handlers use these fields
|
|
273
|
+
* to download quoted media or look up cached card content.
|
|
274
|
+
*/
|
|
275
|
+
export interface QuotedInfo {
|
|
276
|
+
prefix: string;
|
|
277
|
+
mediaDownloadCode?: string;
|
|
278
|
+
mediaType?: string;
|
|
279
|
+
isQuotedFile?: boolean;
|
|
280
|
+
isQuotedCard?: boolean;
|
|
281
|
+
isQuotedDocCard?: boolean;
|
|
282
|
+
docSpaceId?: string;
|
|
283
|
+
docFileId?: string;
|
|
284
|
+
cardCreatedAt?: number;
|
|
285
|
+
processQueryKey?: string;
|
|
286
|
+
fileCreatedAt?: number;
|
|
287
|
+
msgId?: string;
|
|
288
|
+
}
|
|
289
|
+
|
|
204
290
|
/**
|
|
205
291
|
* Extracted message content for unified processing
|
|
206
292
|
*/
|
|
207
293
|
export interface MessageContent {
|
|
208
294
|
text: string;
|
|
209
295
|
mediaPath?: string;
|
|
296
|
+
mediaPaths?: string[];
|
|
210
297
|
mediaType?: string;
|
|
298
|
+
mediaTypes?: string[];
|
|
211
299
|
messageType: string;
|
|
300
|
+
docSpaceId?: string;
|
|
301
|
+
docFileId?: string;
|
|
302
|
+
quoted?: QuotedInfo;
|
|
212
303
|
}
|
|
213
304
|
|
|
214
305
|
/**
|
|
@@ -218,14 +309,21 @@ export interface SendMessageOptions {
|
|
|
218
309
|
title?: string;
|
|
219
310
|
useMarkdown?: boolean;
|
|
220
311
|
atUserId?: string | null;
|
|
221
|
-
log?:
|
|
312
|
+
log?: Logger;
|
|
313
|
+
conversationId?: string;
|
|
222
314
|
mediaPath?: string;
|
|
223
315
|
filePath?: string;
|
|
224
316
|
mediaUrl?: string;
|
|
225
317
|
mediaType?: "image" | "voice" | "video" | "file";
|
|
226
318
|
accountId?: string;
|
|
227
|
-
|
|
228
|
-
|
|
319
|
+
storePath?: string;
|
|
320
|
+
cardUpdateMode?: "append";
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export interface DingTalkTrackingMetadata {
|
|
324
|
+
processQueryKey?: string;
|
|
325
|
+
outTrackId?: string;
|
|
326
|
+
cardInstanceId?: string;
|
|
229
327
|
}
|
|
230
328
|
|
|
231
329
|
/**
|
|
@@ -254,7 +352,7 @@ export interface HandleDingTalkMessageParams {
|
|
|
254
352
|
accountId: string;
|
|
255
353
|
data: DingTalkInboundMessage;
|
|
256
354
|
sessionWebhook: string;
|
|
257
|
-
log?:
|
|
355
|
+
log?: Logger;
|
|
258
356
|
dingtalkConfig: DingTalkConfig;
|
|
259
357
|
}
|
|
260
358
|
|
|
@@ -295,7 +393,7 @@ export interface ResolvedAccount {
|
|
|
295
393
|
export interface AxiosRequestConfig {
|
|
296
394
|
url?: string;
|
|
297
395
|
method?: string;
|
|
298
|
-
data?:
|
|
396
|
+
data?: unknown;
|
|
299
397
|
headers?: Record<string, string>;
|
|
300
398
|
responseType?: "arraybuffer" | "json" | "text";
|
|
301
399
|
}
|
|
@@ -303,7 +401,7 @@ export interface AxiosRequestConfig {
|
|
|
303
401
|
/**
|
|
304
402
|
* HTTP response from axios
|
|
305
403
|
*/
|
|
306
|
-
export interface AxiosResponse<T =
|
|
404
|
+
export interface AxiosResponse<T = unknown> {
|
|
307
405
|
data: T;
|
|
308
406
|
status: number;
|
|
309
407
|
statusText: string;
|
|
@@ -325,15 +423,15 @@ export interface StreamCallbackResponse {
|
|
|
325
423
|
*/
|
|
326
424
|
export interface ReplyDispatchContext {
|
|
327
425
|
responsePrefix?: string;
|
|
328
|
-
deliver: (payload:
|
|
426
|
+
deliver: (payload: unknown) => Promise<{ ok: boolean; error?: string }>;
|
|
329
427
|
}
|
|
330
428
|
|
|
331
429
|
/**
|
|
332
430
|
* Reply dispatcher result
|
|
333
431
|
*/
|
|
334
432
|
export interface ReplyDispatcherResult {
|
|
335
|
-
dispatcher:
|
|
336
|
-
replyOptions:
|
|
433
|
+
dispatcher: unknown;
|
|
434
|
+
replyOptions: unknown;
|
|
337
435
|
markDispatchIdle: () => void;
|
|
338
436
|
}
|
|
339
437
|
|
|
@@ -343,7 +441,7 @@ export interface ReplyDispatcherResult {
|
|
|
343
441
|
export interface RetryOptions {
|
|
344
442
|
maxRetries?: number;
|
|
345
443
|
baseDelayMs?: number;
|
|
346
|
-
log?:
|
|
444
|
+
log?: Logger;
|
|
347
445
|
}
|
|
348
446
|
|
|
349
447
|
/**
|
|
@@ -397,7 +495,7 @@ export interface TargetResolutionResult {
|
|
|
397
495
|
*/
|
|
398
496
|
export interface ResolveTargetParams {
|
|
399
497
|
to?: string | null;
|
|
400
|
-
[key: string]:
|
|
498
|
+
[key: string]: unknown;
|
|
401
499
|
}
|
|
402
500
|
|
|
403
501
|
/**
|
|
@@ -408,7 +506,7 @@ export interface SendTextParams {
|
|
|
408
506
|
to: string;
|
|
409
507
|
text: string;
|
|
410
508
|
accountId?: string;
|
|
411
|
-
[key: string]:
|
|
509
|
+
[key: string]: unknown;
|
|
412
510
|
}
|
|
413
511
|
|
|
414
512
|
/**
|
|
@@ -419,7 +517,7 @@ export interface SendMediaParams {
|
|
|
419
517
|
to: string;
|
|
420
518
|
mediaPath: string;
|
|
421
519
|
accountId?: string;
|
|
422
|
-
[key: string]:
|
|
520
|
+
[key: string]: unknown;
|
|
423
521
|
}
|
|
424
522
|
|
|
425
523
|
/**
|
|
@@ -428,8 +526,8 @@ export interface SendMediaParams {
|
|
|
428
526
|
export interface DingTalkOutboundHandler {
|
|
429
527
|
deliveryMode: "direct" | "queued" | "batch";
|
|
430
528
|
resolveTarget: (params: ResolveTargetParams) => TargetResolutionResult;
|
|
431
|
-
sendText: (params: SendTextParams) => Promise<{ ok: boolean; data?:
|
|
432
|
-
sendMedia?: (params: SendMediaParams) => Promise<{ ok: boolean; data?:
|
|
529
|
+
sendText: (params: SendTextParams) => Promise<{ ok: boolean; data?: unknown; error?: unknown }>;
|
|
530
|
+
sendMedia?: (params: SendMediaParams) => Promise<{ ok: boolean; data?: unknown; error?: unknown }>;
|
|
433
531
|
}
|
|
434
532
|
|
|
435
533
|
/**
|
|
@@ -452,13 +550,17 @@ export type AICardState = (typeof AICardStatus)[keyof typeof AICardStatus];
|
|
|
452
550
|
*/
|
|
453
551
|
export interface AICardInstance {
|
|
454
552
|
cardInstanceId: string;
|
|
553
|
+
processQueryKey?: string;
|
|
455
554
|
accessToken: string;
|
|
456
555
|
conversationId: string;
|
|
556
|
+
accountId?: string;
|
|
557
|
+
storePath?: string;
|
|
457
558
|
createdAt: number;
|
|
458
559
|
lastUpdated: number;
|
|
459
560
|
state: AICardState; // Current card state: PROCESSING, INPUTING, FINISHED, FAILED
|
|
460
561
|
config?: DingTalkConfig; // Store config reference for token refresh
|
|
461
562
|
lastStreamedContent?: string;
|
|
563
|
+
outTrackId?: string;
|
|
462
564
|
}
|
|
463
565
|
|
|
464
566
|
/**
|
|
@@ -485,6 +587,14 @@ export enum ConnectionState {
|
|
|
485
587
|
FAILED = "FAILED",
|
|
486
588
|
}
|
|
487
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Factory function that creates a fresh DWClient with callback listeners
|
|
592
|
+
* already registered. Used by ConnectionManager to create a new client
|
|
593
|
+
* on reconnection so the new WebSocket can start receiving messages
|
|
594
|
+
* while the old zombie socket is still being cleaned up.
|
|
595
|
+
*/
|
|
596
|
+
export type StreamClientFactory = () => import("dingtalk-stream").DWClient;
|
|
597
|
+
|
|
488
598
|
/**
|
|
489
599
|
* Connection manager configuration
|
|
490
600
|
*/
|
|
@@ -495,6 +605,8 @@ export interface ConnectionManagerConfig {
|
|
|
495
605
|
jitter: number;
|
|
496
606
|
/** Maximum number of runtime reconnect cycles before giving up (default: 10) */
|
|
497
607
|
maxReconnectCycles?: number;
|
|
608
|
+
/** Maximum time (ms) for a single reconnect cycle before giving up and starting a new cycle (default: 50000) */
|
|
609
|
+
reconnectDeadlineMs?: number;
|
|
498
610
|
/** Callback invoked when connection state changes */
|
|
499
611
|
onStateChange?: (state: ConnectionState, error?: string) => void;
|
|
500
612
|
}
|
|
@@ -568,7 +680,8 @@ export function resolveDingTalkAccount(
|
|
|
568
680
|
dmPolicy: dingtalk?.dmPolicy,
|
|
569
681
|
groupPolicy: dingtalk?.groupPolicy,
|
|
570
682
|
allowFrom: dingtalk?.allowFrom,
|
|
571
|
-
|
|
683
|
+
journalTTLDays: dingtalk?.journalTTLDays,
|
|
684
|
+
ackReaction: dingtalk?.ackReaction,
|
|
572
685
|
debug: dingtalk?.debug,
|
|
573
686
|
messageType: dingtalk?.messageType,
|
|
574
687
|
cardTemplateId: dingtalk?.cardTemplateId,
|
|
@@ -580,9 +693,20 @@ export function resolveDingTalkAccount(
|
|
|
580
693
|
maxReconnectDelay: dingtalk?.maxReconnectDelay,
|
|
581
694
|
reconnectJitter: dingtalk?.reconnectJitter,
|
|
582
695
|
maxReconnectCycles: dingtalk?.maxReconnectCycles,
|
|
696
|
+
reconnectDeadlineMs: dingtalk?.reconnectDeadlineMs,
|
|
583
697
|
useConnectionManager: dingtalk?.useConnectionManager,
|
|
584
698
|
mediaMaxMb: dingtalk?.mediaMaxMb,
|
|
699
|
+
keepAlive: dingtalk?.keepAlive,
|
|
700
|
+
bypassProxyForSend: dingtalk?.bypassProxyForSend,
|
|
585
701
|
proactivePermissionHint: dingtalk?.proactivePermissionHint,
|
|
702
|
+
cardRealTimeStream: dingtalk?.cardRealTimeStream,
|
|
703
|
+
aicardDegradeMs: dingtalk?.aicardDegradeMs,
|
|
704
|
+
learningEnabled: dingtalk?.learningEnabled ?? dingtalk?.feedbackLearningEnabled,
|
|
705
|
+
learningAutoApply: dingtalk?.learningAutoApply ?? dingtalk?.feedbackLearningAutoApply,
|
|
706
|
+
learningNoteTtlMs: dingtalk?.learningNoteTtlMs ?? dingtalk?.feedbackLearningNoteTtlMs,
|
|
707
|
+
feedbackLearningEnabled: dingtalk?.feedbackLearningEnabled,
|
|
708
|
+
feedbackLearningAutoApply: dingtalk?.feedbackLearningAutoApply,
|
|
709
|
+
feedbackLearningNoteTtlMs: dingtalk?.feedbackLearningNoteTtlMs,
|
|
586
710
|
};
|
|
587
711
|
return {
|
|
588
712
|
...config,
|
|
@@ -591,13 +715,17 @@ export function resolveDingTalkAccount(
|
|
|
591
715
|
};
|
|
592
716
|
}
|
|
593
717
|
|
|
594
|
-
// If named account,
|
|
718
|
+
// If named account, merge channel-level defaults with account-level overrides
|
|
595
719
|
const accountConfig = dingtalk?.accounts?.[id];
|
|
596
720
|
if (accountConfig) {
|
|
721
|
+
const merged = mergeAccountWithDefaults(
|
|
722
|
+
dingtalk as DingTalkConfig,
|
|
723
|
+
accountConfig,
|
|
724
|
+
);
|
|
597
725
|
return {
|
|
598
|
-
...
|
|
726
|
+
...merged,
|
|
599
727
|
accountId: id,
|
|
600
|
-
configured: Boolean(
|
|
728
|
+
configured: Boolean(merged.clientId && merged.clientSecret),
|
|
601
729
|
};
|
|
602
730
|
}
|
|
603
731
|
|