@soimy/dingtalk 3.2.0 → 3.4.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +796 -42
  3. package/index.ts +62 -0
  4. package/package.json +4 -2
  5. package/src/access-control.ts +83 -0
  6. package/src/ack-reaction/dynamic-ack-reaction-controller.ts +271 -0
  7. package/src/ack-reaction/dynamic-ack-reaction-events.ts +123 -0
  8. package/src/ack-reaction/dynamic-ack-reaction-progress.ts +59 -0
  9. package/src/ack-reaction-classifier.ts +75 -0
  10. package/src/ack-reaction-service.ts +182 -0
  11. package/src/attachment-text-extractor.ts +148 -0
  12. package/src/card-callback-service.ts +119 -0
  13. package/src/card-draft-controller.ts +114 -0
  14. package/src/card-service.ts +666 -26
  15. package/src/channel.ts +455 -150
  16. package/src/config-schema.ts +64 -6
  17. package/src/config.ts +161 -5
  18. package/src/connection-manager.ts +354 -47
  19. package/src/dedup.ts +1 -0
  20. package/src/docs-service.ts +198 -0
  21. package/src/draft-stream-loop.ts +119 -0
  22. package/src/feedback-learning-service.ts +643 -0
  23. package/src/feedback-learning-store.ts +543 -0
  24. package/src/group-members-store.ts +48 -14
  25. package/src/inbound-handler.ts +1374 -259
  26. package/src/learning-command-service.ts +339 -0
  27. package/src/media-utils.ts +94 -50
  28. package/src/message-context-store.ts +787 -0
  29. package/src/message-utils.ts +487 -46
  30. package/src/messaging/quoted-context.ts +269 -0
  31. package/src/messaging/quoted-ref.ts +97 -0
  32. package/src/onboarding.ts +96 -1
  33. package/src/peer-id-registry.ts +102 -0
  34. package/src/persistence-store.ts +131 -0
  35. package/src/quoted-file-service.ts +385 -0
  36. package/src/reply-strategy-card.ts +225 -0
  37. package/src/reply-strategy-markdown.ts +55 -0
  38. package/src/reply-strategy-with-reaction.ts +190 -0
  39. package/src/reply-strategy.ts +72 -0
  40. package/src/send-service.ts +267 -45
  41. package/src/session-command-service.ts +147 -0
  42. package/src/session-lock.ts +2 -0
  43. package/src/session-peer-store.ts +77 -0
  44. package/src/session-routing.ts +33 -0
  45. package/src/targeting/agent-name-matcher.ts +148 -0
  46. package/src/targeting/agent-routing.ts +181 -0
  47. package/src/targeting/target-directory-adapter.ts +151 -0
  48. package/src/targeting/target-directory-store.ts +396 -0
  49. package/src/targeting/target-input.ts +62 -0
  50. package/src/types.ts +261 -28
  51. package/src/utils.ts +231 -12
package/src/types.ts CHANGED
@@ -17,6 +17,12 @@ import type {
17
17
  ChannelGatewayContext as SDKChannelGatewayContext,
18
18
  ChannelPlugin as SDKChannelPlugin,
19
19
  } from "openclaw/plugin-sdk";
20
+ import { mergeAccountWithDefaults } from "./config";
21
+
22
+ export type AckReactionMode = "off" | "emoji" | "kaomoji";
23
+ // Accept arbitrary strings for backward compatibility; the recommended
24
+ // explicit modes remain: "off" | "emoji" | "kaomoji".
25
+ export type AckReactionConfigValue = string;
20
26
 
21
27
  export interface DingtalkPluginModule {
22
28
  id: string;
@@ -38,15 +44,18 @@ export interface DingTalkConfig extends OpenClawConfig {
38
44
  name?: string;
39
45
  enabled?: boolean;
40
46
  dmPolicy?: "open" | "pairing" | "allowlist";
41
- groupPolicy?: "open" | "allowlist";
47
+ groupPolicy?: "open" | "allowlist" | "disabled";
42
48
  allowFrom?: string[];
49
+ groupAllowFrom?: string[];
50
+ displayNameResolution?: "disabled" | "all";
43
51
  mediaUrlAllowlist?: string[];
44
- showThinking?: boolean;
52
+ journalTTLDays?: number;
53
+ ackReaction?: AckReactionConfigValue;
45
54
  debug?: boolean;
46
55
  messageType?: "markdown" | "card";
47
56
  cardTemplateId?: string;
48
57
  cardTemplateKey?: string;
49
- groups?: Record<string, { systemPrompt?: string }>;
58
+ groups?: Record<string, { systemPrompt?: string; requireMention?: boolean; groupAllowFrom?: string[] }>;
50
59
  accounts?: Record<string, DingTalkConfig>;
51
60
  // Connection robustness configuration
52
61
  maxConnectionAttempts?: number;
@@ -55,14 +64,40 @@ export interface DingTalkConfig extends OpenClawConfig {
55
64
  reconnectJitter?: number;
56
65
  /** Maximum number of runtime reconnect cycles before giving up (default: 10) */
57
66
  maxReconnectCycles?: number;
67
+ /** Maximum time (ms) for a single reconnect cycle before giving up and starting a new cycle (default: 50000) */
68
+ reconnectDeadlineMs?: number;
58
69
  /** Whether to use ConnectionManager; when false, use DWClient native keepAlive+autoReconnect */
59
70
  useConnectionManager?: boolean;
60
71
  /** Maximum inbound media file size in MB (overrides runtime default when set) */
61
72
  mediaMaxMb?: number;
73
+ /** Whether to enable underlying stream keepAlive heartbeat; defaults to !useConnectionManager when omitted */
74
+ keepAlive?: boolean;
75
+ /** Bypass system/global HTTP(S) proxy for DingTalk outbound send/card/upload APIs */
76
+ bypassProxyForSend?: boolean;
62
77
  proactivePermissionHint?: {
63
78
  enabled?: boolean;
64
79
  cooldownHours?: number;
65
80
  };
81
+ /** Enable real-time card streaming (default false, true = 300ms throttled per-token updates) */
82
+ cardRealTimeStream?: boolean;
83
+ /** AICard degrade duration in milliseconds after trigger errors (default 30m) */
84
+ aicardDegradeMs?: number;
85
+ /** Enable local learning loop (events/reflections/session notes/global rules) */
86
+ learningEnabled?: boolean;
87
+ /** Auto-apply generated reflections into session notes/global rules (default false) */
88
+ learningAutoApply?: boolean;
89
+ /** Session learning note TTL in milliseconds (default 6h) */
90
+ learningNoteTtlMs?: number;
91
+ /** @deprecated Use learningEnabled */
92
+ feedbackLearningEnabled?: boolean;
93
+ /** @deprecated Use learningAutoApply */
94
+ feedbackLearningAutoApply?: boolean;
95
+ /** @deprecated Use learningNoteTtlMs */
96
+ feedbackLearningNoteTtlMs?: number;
97
+ /** Whether to convert markdown tables to plain text for better rendering on some clients (default: true) */
98
+ convertMarkdownTables?: boolean;
99
+ /** @mention the sender after card finalization in group chats; value is the message text */
100
+ cardAtSender?: string;
66
101
  }
67
102
 
68
103
  /**
@@ -77,15 +112,18 @@ export interface DingTalkChannelConfig {
77
112
  agentId?: string;
78
113
  name?: string;
79
114
  dmPolicy?: "open" | "pairing" | "allowlist";
80
- groupPolicy?: "open" | "allowlist";
115
+ groupPolicy?: "open" | "allowlist" | "disabled";
81
116
  allowFrom?: string[];
117
+ groupAllowFrom?: string[];
118
+ displayNameResolution?: "disabled" | "all";
82
119
  mediaUrlAllowlist?: string[];
83
- showThinking?: boolean;
120
+ journalTTLDays?: number;
121
+ ackReaction?: AckReactionConfigValue;
84
122
  debug?: boolean;
85
123
  messageType?: "markdown" | "card";
86
124
  cardTemplateId?: string;
87
125
  cardTemplateKey?: string;
88
- groups?: Record<string, { systemPrompt?: string }>;
126
+ groups?: Record<string, { systemPrompt?: string; requireMention?: boolean; groupAllowFrom?: string[] }>;
89
127
  accounts?: Record<string, DingTalkConfig>;
90
128
  maxConnectionAttempts?: number;
91
129
  initialReconnectDelay?: number;
@@ -93,14 +131,40 @@ export interface DingTalkChannelConfig {
93
131
  reconnectJitter?: number;
94
132
  /** Maximum number of runtime reconnect cycles before giving up (default: 10) */
95
133
  maxReconnectCycles?: number;
134
+ /** Maximum time (ms) for a single reconnect cycle before giving up and starting a new cycle (default: 50000) */
135
+ reconnectDeadlineMs?: number;
96
136
  /** Whether to use ConnectionManager; when false, use DWClient native keepAlive+autoReconnect */
97
137
  useConnectionManager?: boolean;
98
138
  /** Maximum inbound media file size in MB (overrides runtime default when set) */
99
139
  mediaMaxMb?: number;
140
+ /** Whether to enable underlying stream keepAlive heartbeat; defaults to !useConnectionManager when omitted */
141
+ keepAlive?: boolean;
142
+ /** Bypass system/global HTTP(S) proxy for DingTalk outbound send/card/upload APIs */
143
+ bypassProxyForSend?: boolean;
100
144
  proactivePermissionHint?: {
101
145
  enabled?: boolean;
102
146
  cooldownHours?: number;
103
147
  };
148
+ /** Enable real-time card streaming (default false, true = 300ms throttled per-token updates) */
149
+ cardRealTimeStream?: boolean;
150
+ /** AICard degrade duration in milliseconds after trigger errors (default 30m) */
151
+ aicardDegradeMs?: number;
152
+ /** Enable local learning loop (events/reflections/session notes/global rules) */
153
+ learningEnabled?: boolean;
154
+ /** Auto-apply generated reflections into session notes/global rules (default false) */
155
+ learningAutoApply?: boolean;
156
+ /** Session learning note TTL in milliseconds (default 6h) */
157
+ learningNoteTtlMs?: number;
158
+ /** @deprecated Use learningEnabled */
159
+ feedbackLearningEnabled?: boolean;
160
+ /** @deprecated Use learningAutoApply */
161
+ feedbackLearningAutoApply?: boolean;
162
+ /** @deprecated Use learningNoteTtlMs */
163
+ feedbackLearningNoteTtlMs?: number;
164
+ /** Whether to convert markdown tables to plain text for better rendering on some clients (default: true) */
165
+ convertMarkdownTables?: boolean;
166
+ /** @mention the sender after card finalization in group chats; value is the message text */
167
+ cardAtSender?: string;
104
168
  }
105
169
 
106
170
  /**
@@ -145,6 +209,14 @@ export interface MediaFile {
145
209
  mimeType: string;
146
210
  }
147
211
 
212
+ export interface DocInfo {
213
+ docId: string;
214
+ title: string;
215
+ docType: string;
216
+ creatorId?: string;
217
+ updatedAt?: number | string;
218
+ }
219
+
148
220
  /**
149
221
  * DingTalk incoming message (Stream mode)
150
222
  */
@@ -152,19 +224,35 @@ export interface DingTalkInboundMessage {
152
224
  msgId: string;
153
225
  msgtype: string;
154
226
  createAt: number;
227
+ /**
228
+ * @ 提及的用户列表(消息顶层,与 text 同级)
229
+ * 包含通过 @picker 选中的所有真实钉钉用户和机器人
230
+ * 格式: [{ dingtalkId: "$:LWCP_v1:$xxx" }]
231
+ */
232
+ atUsers?: Array<{
233
+ dingtalkId: string;
234
+ }>;
155
235
  text?: {
156
236
  content: string;
157
237
  isReplyMsg?: boolean; // 是否是回复消息
158
238
  repliedMsg?: {
159
- // 被回复的消息
239
+ msgType?: string;
240
+ msgId?: string;
241
+ senderId?: string;
242
+ createdAt?: number;
160
243
  content?: {
161
244
  text?: string;
245
+ downloadCode?: string;
246
+ fileName?: string;
247
+ biz_custom_action_url?: string;
162
248
  richText?: Array<{
163
249
  msgType?: string;
164
250
  type?: string;
165
251
  content?: string;
252
+ text?: string;
166
253
  code?: string;
167
254
  atName?: string;
255
+ downloadCode?: string;
168
256
  }>;
169
257
  };
170
258
  };
@@ -173,13 +261,17 @@ export interface DingTalkInboundMessage {
173
261
  downloadCode?: string;
174
262
  fileName?: string;
175
263
  recognition?: string;
264
+ spaceId?: string;
265
+ fileId?: string;
266
+ biz_custom_action_url?: string;
176
267
  richText?: Array<{
177
268
  type: string;
178
269
  text?: string;
179
270
  atName?: string;
180
- downloadCode?: string; // For picture type in richText
271
+ atUserId?: string;
272
+ downloadCode?: string;
181
273
  }>;
182
- quoteContent?: string; // 替代引用格式
274
+ quoteContent?: string;
183
275
  };
184
276
  // Legacy 引用格式
185
277
  quoteMessage?: {
@@ -191,6 +283,7 @@ export interface DingTalkInboundMessage {
191
283
  };
192
284
  // 富媒体引用,仅有消息ID的情况(包括手机端和PC端)
193
285
  originalMsgId?: string;
286
+ originalProcessQueryKey?: string;
194
287
  conversationType: string;
195
288
  conversationId: string;
196
289
  conversationTitle?: string;
@@ -201,14 +294,83 @@ export interface DingTalkInboundMessage {
201
294
  sessionWebhook: string;
202
295
  }
203
296
 
297
+ export type QuotedRefKey = "msgId" | "processQueryKey" | "messageId" | "outTrackId" | "cardInstanceId";
298
+
299
+ export type AttachmentTextSource = "text" | "html" | "pdf" | "docx";
300
+
301
+ export interface QuotedRef {
302
+ targetDirection: "inbound" | "outbound";
303
+ key?: QuotedRefKey;
304
+ value?: string;
305
+ fallbackCreatedAt?: number;
306
+ }
307
+
308
+ /**
309
+ * Quoted/reply message metadata extracted from repliedMsg.
310
+ * Populated when isReplyMsg is true; downstream handlers use these fields
311
+ * to download quoted media or look up cached card content.
312
+ */
313
+ export interface QuotedInfo {
314
+ mediaDownloadCode?: string;
315
+ mediaType?: string;
316
+ isQuotedFile?: boolean;
317
+ isQuotedCard?: boolean;
318
+ isQuotedDocCard?: boolean;
319
+ cardCreatedAt?: number;
320
+ processQueryKey?: string;
321
+ fileCreatedAt?: number;
322
+ msgId?: string;
323
+ previewText?: string;
324
+ previewMessageType?: string;
325
+ previewFileName?: string;
326
+ previewSenderId?: string;
327
+ }
328
+
329
+ /**
330
+ * @ 提及信息
331
+ */
332
+ export interface AtMention {
333
+ /** @ 显示的名字(去除 @ 前缀) */
334
+ name: string;
335
+ /** 钉钉用户 ID(如果是 @ 真人) */
336
+ userId?: string;
337
+ }
338
+
339
+ /**
340
+ * Agent 名字匹配结果
341
+ */
342
+ export interface AgentNameMatch {
343
+ /** 匹配到的 agent ID */
344
+ agentId: string;
345
+ /** 匹配来源:'name' | 'id' */
346
+ matchSource: "name" | "id";
347
+ /** 匹配到的名字 */
348
+ matchedName: string;
349
+ }
350
+
204
351
  /**
205
352
  * Extracted message content for unified processing
206
353
  */
207
354
  export interface MessageContent {
208
355
  text: string;
209
356
  mediaPath?: string;
357
+ mediaPaths?: string[];
210
358
  mediaType?: string;
359
+ mediaTypes?: string[];
211
360
  messageType: string;
361
+ docSpaceId?: string;
362
+ docFileId?: string;
363
+ quoted?: QuotedInfo;
364
+ /** @ 提及列表(从文本或 richText 提取的名字) */
365
+ atMentions?: AtMention[];
366
+ /**
367
+ * 通过 @picker 选中的真实钉钉用户的 dingtalkId 列表
368
+ * - 仅包含真实钉钉用户和机器人,不包含 agent 名
369
+ * - 用于排除真人:如果 atMentions 中的名字匹配到 agent,说明是 agent;
370
+ * 如果没匹配到 agent 且有 atUserDingtalkIds,则可能是真人
371
+ * - 注意:无法将 dingtalkId 映射到具体名字,因为 webhook 不提供此映射
372
+ */
373
+ atUserDingtalkIds?: string[];
212
374
  }
213
375
 
214
376
  /**
@@ -218,14 +380,25 @@ export interface SendMessageOptions {
218
380
  title?: string;
219
381
  useMarkdown?: boolean;
220
382
  atUserId?: string | null;
221
- log?: any;
383
+ log?: Logger;
384
+ conversationId?: string;
222
385
  mediaPath?: string;
223
386
  filePath?: string;
224
387
  mediaUrl?: string;
225
388
  mediaType?: "image" | "voice" | "video" | "file";
226
389
  accountId?: string;
227
- cardUpdateMode?: "replace" | "append" | "finalize";
228
- cardFinalize?: boolean;
390
+ storePath?: string;
391
+ cardUpdateMode?: "append";
392
+ quotedRef?: QuotedRef;
393
+ /** Force markdown/text delivery even when messageType is "card". Bypasses card
394
+ * creation while preserving journal writes and other side-effects. */
395
+ forceMarkdown?: boolean;
396
+ }
397
+
398
+ export interface DingTalkTrackingMetadata {
399
+ processQueryKey?: string;
400
+ outTrackId?: string;
401
+ cardInstanceId?: string;
229
402
  }
230
403
 
231
404
  /**
@@ -246,6 +419,18 @@ export interface SessionWebhookResponse {
246
419
  };
247
420
  }
248
421
 
422
+ /**
423
+ * Sub-agent routing options for parameterized message handling
424
+ */
425
+ export interface SubAgentOptions {
426
+ /** The agent ID to route to */
427
+ agentId: string;
428
+ /** Prefix to add to response messages (e.g., "[AgentName] ") */
429
+ responsePrefix: string;
430
+ /** The matched agent name */
431
+ matchedName: string;
432
+ }
433
+
249
434
  /**
250
435
  * Message handler parameters
251
436
  */
@@ -254,8 +439,21 @@ export interface HandleDingTalkMessageParams {
254
439
  accountId: string;
255
440
  data: DingTalkInboundMessage;
256
441
  sessionWebhook: string;
257
- log?: any;
442
+ log?: Logger;
258
443
  dingtalkConfig: DingTalkConfig;
444
+ /**
445
+ * When set, routes message to the specified sub-agent instead of main agent.
446
+ * This enables reuse of the main message handling logic for sub-agents.
447
+ */
448
+ subAgentOptions?: SubAgentOptions;
449
+ /**
450
+ * Pre-downloaded media for sub-agent calls.
451
+ * When set, skips media download to avoid duplication in recursive calls.
452
+ */
453
+ preDownloadedMedia?: {
454
+ mediaPath?: string;
455
+ mediaType?: string;
456
+ };
259
457
  }
260
458
 
261
459
  /**
@@ -295,7 +493,7 @@ export interface ResolvedAccount {
295
493
  export interface AxiosRequestConfig {
296
494
  url?: string;
297
495
  method?: string;
298
- data?: any;
496
+ data?: unknown;
299
497
  headers?: Record<string, string>;
300
498
  responseType?: "arraybuffer" | "json" | "text";
301
499
  }
@@ -303,7 +501,7 @@ export interface AxiosRequestConfig {
303
501
  /**
304
502
  * HTTP response from axios
305
503
  */
306
- export interface AxiosResponse<T = any> {
504
+ export interface AxiosResponse<T = unknown> {
307
505
  data: T;
308
506
  status: number;
309
507
  statusText: string;
@@ -325,15 +523,15 @@ export interface StreamCallbackResponse {
325
523
  */
326
524
  export interface ReplyDispatchContext {
327
525
  responsePrefix?: string;
328
- deliver: (payload: any) => Promise<{ ok: boolean; error?: string }>;
526
+ deliver: (payload: unknown) => Promise<{ ok: boolean; error?: string }>;
329
527
  }
330
528
 
331
529
  /**
332
530
  * Reply dispatcher result
333
531
  */
334
532
  export interface ReplyDispatcherResult {
335
- dispatcher: any;
336
- replyOptions: any;
533
+ dispatcher: unknown;
534
+ replyOptions: unknown;
337
535
  markDispatchIdle: () => void;
338
536
  }
339
537
 
@@ -343,7 +541,7 @@ export interface ReplyDispatcherResult {
343
541
  export interface RetryOptions {
344
542
  maxRetries?: number;
345
543
  baseDelayMs?: number;
346
- log?: any;
544
+ log?: Logger;
347
545
  }
348
546
 
349
547
  /**
@@ -397,7 +595,7 @@ export interface TargetResolutionResult {
397
595
  */
398
596
  export interface ResolveTargetParams {
399
597
  to?: string | null;
400
- [key: string]: any;
598
+ [key: string]: unknown;
401
599
  }
402
600
 
403
601
  /**
@@ -408,7 +606,7 @@ export interface SendTextParams {
408
606
  to: string;
409
607
  text: string;
410
608
  accountId?: string;
411
- [key: string]: any;
609
+ [key: string]: unknown;
412
610
  }
413
611
 
414
612
  /**
@@ -419,7 +617,7 @@ export interface SendMediaParams {
419
617
  to: string;
420
618
  mediaPath: string;
421
619
  accountId?: string;
422
- [key: string]: any;
620
+ [key: string]: unknown;
423
621
  }
424
622
 
425
623
  /**
@@ -428,8 +626,8 @@ export interface SendMediaParams {
428
626
  export interface DingTalkOutboundHandler {
429
627
  deliveryMode: "direct" | "queued" | "batch";
430
628
  resolveTarget: (params: ResolveTargetParams) => TargetResolutionResult;
431
- sendText: (params: SendTextParams) => Promise<{ ok: boolean; data?: any; error?: any }>;
432
- sendMedia?: (params: SendMediaParams) => Promise<{ ok: boolean; data?: any; error?: any }>;
629
+ sendText: (params: SendTextParams) => Promise<{ ok: boolean; data?: unknown; error?: unknown }>;
630
+ sendMedia?: (params: SendMediaParams) => Promise<{ ok: boolean; data?: unknown; error?: unknown }>;
433
631
  }
434
632
 
435
633
  /**
@@ -452,13 +650,18 @@ export type AICardState = (typeof AICardStatus)[keyof typeof AICardStatus];
452
650
  */
453
651
  export interface AICardInstance {
454
652
  cardInstanceId: string;
653
+ processQueryKey?: string;
455
654
  accessToken: string;
456
655
  conversationId: string;
656
+ contextConversationId?: string;
657
+ accountId?: string;
658
+ storePath?: string;
457
659
  createdAt: number;
458
660
  lastUpdated: number;
459
661
  state: AICardState; // Current card state: PROCESSING, INPUTING, FINISHED, FAILED
460
662
  config?: DingTalkConfig; // Store config reference for token refresh
461
663
  lastStreamedContent?: string;
664
+ outTrackId?: string;
462
665
  }
463
666
 
464
667
  /**
@@ -485,6 +688,14 @@ export enum ConnectionState {
485
688
  FAILED = "FAILED",
486
689
  }
487
690
 
691
+ /**
692
+ * Factory function that creates a fresh DWClient with callback listeners
693
+ * already registered. Used by ConnectionManager to create a new client
694
+ * on reconnection so the new WebSocket can start receiving messages
695
+ * while the old zombie socket is still being cleaned up.
696
+ */
697
+ export type StreamClientFactory = () => import("dingtalk-stream").DWClient;
698
+
488
699
  /**
489
700
  * Connection manager configuration
490
701
  */
@@ -495,6 +706,8 @@ export interface ConnectionManagerConfig {
495
706
  jitter: number;
496
707
  /** Maximum number of runtime reconnect cycles before giving up (default: 10) */
497
708
  maxReconnectCycles?: number;
709
+ /** Maximum time (ms) for a single reconnect cycle before giving up and starting a new cycle (default: 50000) */
710
+ reconnectDeadlineMs?: number;
498
711
  /** Callback invoked when connection state changes */
499
712
  onStateChange?: (state: ConnectionState, error?: string) => void;
500
713
  }
@@ -568,7 +781,10 @@ export function resolveDingTalkAccount(
568
781
  dmPolicy: dingtalk?.dmPolicy,
569
782
  groupPolicy: dingtalk?.groupPolicy,
570
783
  allowFrom: dingtalk?.allowFrom,
571
- showThinking: dingtalk?.showThinking,
784
+ groupAllowFrom: dingtalk?.groupAllowFrom,
785
+ displayNameResolution: dingtalk?.displayNameResolution,
786
+ journalTTLDays: dingtalk?.journalTTLDays,
787
+ ackReaction: dingtalk?.ackReaction,
572
788
  debug: dingtalk?.debug,
573
789
  messageType: dingtalk?.messageType,
574
790
  cardTemplateId: dingtalk?.cardTemplateId,
@@ -580,9 +796,22 @@ export function resolveDingTalkAccount(
580
796
  maxReconnectDelay: dingtalk?.maxReconnectDelay,
581
797
  reconnectJitter: dingtalk?.reconnectJitter,
582
798
  maxReconnectCycles: dingtalk?.maxReconnectCycles,
799
+ reconnectDeadlineMs: dingtalk?.reconnectDeadlineMs,
583
800
  useConnectionManager: dingtalk?.useConnectionManager,
584
801
  mediaMaxMb: dingtalk?.mediaMaxMb,
802
+ keepAlive: dingtalk?.keepAlive,
803
+ bypassProxyForSend: dingtalk?.bypassProxyForSend,
585
804
  proactivePermissionHint: dingtalk?.proactivePermissionHint,
805
+ cardRealTimeStream: dingtalk?.cardRealTimeStream,
806
+ aicardDegradeMs: dingtalk?.aicardDegradeMs,
807
+ learningEnabled: dingtalk?.learningEnabled ?? dingtalk?.feedbackLearningEnabled,
808
+ learningAutoApply: dingtalk?.learningAutoApply ?? dingtalk?.feedbackLearningAutoApply,
809
+ learningNoteTtlMs: dingtalk?.learningNoteTtlMs ?? dingtalk?.feedbackLearningNoteTtlMs,
810
+ feedbackLearningEnabled: dingtalk?.feedbackLearningEnabled,
811
+ feedbackLearningAutoApply: dingtalk?.feedbackLearningAutoApply,
812
+ feedbackLearningNoteTtlMs: dingtalk?.feedbackLearningNoteTtlMs,
813
+ convertMarkdownTables: dingtalk?.convertMarkdownTables,
814
+ cardAtSender: dingtalk?.cardAtSender,
586
815
  };
587
816
  return {
588
817
  ...config,
@@ -591,13 +820,17 @@ export function resolveDingTalkAccount(
591
820
  };
592
821
  }
593
822
 
594
- // If named account, get from accounts object
823
+ // If named account, merge channel-level defaults with account-level overrides
595
824
  const accountConfig = dingtalk?.accounts?.[id];
596
825
  if (accountConfig) {
826
+ const merged = mergeAccountWithDefaults(
827
+ dingtalk as DingTalkConfig,
828
+ accountConfig,
829
+ );
597
830
  return {
598
- ...accountConfig,
831
+ ...merged,
599
832
  accountId: id,
600
- configured: Boolean(accountConfig.clientId && accountConfig.clientSecret),
833
+ configured: Boolean(merged.clientId && merged.clientSecret),
601
834
  };
602
835
  }
603
836