@soimy/dingtalk 3.1.4 → 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.
@@ -0,0 +1,33 @@
1
+ import type { DingTalkConfig } from "./types";
2
+
3
+ export interface ResolveDingTalkSessionPeerParams {
4
+ isDirect: boolean;
5
+ senderId: string;
6
+ conversationId: string;
7
+ peerIdOverride?: string;
8
+ config: DingTalkConfig;
9
+ }
10
+
11
+ export interface ResolvedDingTalkSessionPeer {
12
+ kind: "direct" | "group";
13
+ peerId: string;
14
+ }
15
+
16
+ // Keep DingTalk aligned with Feishu's explicit peerId -> sessionKey model:
17
+ // resolve a stable peer identity first, then let OpenClaw build the final session key.
18
+ export function resolveDingTalkSessionPeer(
19
+ params: ResolveDingTalkSessionPeerParams,
20
+ ): ResolvedDingTalkSessionPeer {
21
+ const normalizedPeerIdOverride = params.peerIdOverride?.trim();
22
+ if (params.isDirect) {
23
+ return {
24
+ kind: "direct",
25
+ peerId: normalizedPeerIdOverride || params.senderId,
26
+ };
27
+ }
28
+
29
+ return {
30
+ kind: "group",
31
+ peerId: normalizedPeerIdOverride || params.conversationId,
32
+ };
33
+ }
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;
@@ -40,7 +41,9 @@ export interface DingTalkConfig extends OpenClawConfig {
40
41
  dmPolicy?: "open" | "pairing" | "allowlist";
41
42
  groupPolicy?: "open" | "allowlist";
42
43
  allowFrom?: string[];
43
- showThinking?: boolean;
44
+ mediaUrlAllowlist?: string[];
45
+ journalTTLDays?: number;
46
+ ackReaction?: string;
44
47
  debug?: boolean;
45
48
  messageType?: "markdown" | "card";
46
49
  cardTemplateId?: string;
@@ -54,10 +57,36 @@ export interface DingTalkConfig extends OpenClawConfig {
54
57
  reconnectJitter?: number;
55
58
  /** Maximum number of runtime reconnect cycles before giving up (default: 10) */
56
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;
62
+ /** Whether to use ConnectionManager; when false, use DWClient native keepAlive+autoReconnect */
63
+ useConnectionManager?: boolean;
64
+ /** Maximum inbound media file size in MB (overrides runtime default when set) */
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;
57
70
  proactivePermissionHint?: {
58
71
  enabled?: boolean;
59
72
  cooldownHours?: number;
60
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;
61
90
  }
62
91
 
63
92
  /**
@@ -74,7 +103,9 @@ export interface DingTalkChannelConfig {
74
103
  dmPolicy?: "open" | "pairing" | "allowlist";
75
104
  groupPolicy?: "open" | "allowlist";
76
105
  allowFrom?: string[];
77
- showThinking?: boolean;
106
+ mediaUrlAllowlist?: string[];
107
+ journalTTLDays?: number;
108
+ ackReaction?: string;
78
109
  debug?: boolean;
79
110
  messageType?: "markdown" | "card";
80
111
  cardTemplateId?: string;
@@ -87,10 +118,36 @@ export interface DingTalkChannelConfig {
87
118
  reconnectJitter?: number;
88
119
  /** Maximum number of runtime reconnect cycles before giving up (default: 10) */
89
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;
123
+ /** Whether to use ConnectionManager; when false, use DWClient native keepAlive+autoReconnect */
124
+ useConnectionManager?: boolean;
125
+ /** Maximum inbound media file size in MB (overrides runtime default when set) */
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;
90
131
  proactivePermissionHint?: {
91
132
  enabled?: boolean;
92
133
  cooldownHours?: number;
93
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;
94
151
  }
95
152
 
96
153
  /**
@@ -135,6 +192,14 @@ export interface MediaFile {
135
192
  mimeType: string;
136
193
  }
137
194
 
195
+ export interface DocInfo {
196
+ docId: string;
197
+ title: string;
198
+ docType: string;
199
+ creatorId?: string;
200
+ updatedAt?: number | string;
201
+ }
202
+
138
203
  /**
139
204
  * DingTalk incoming message (Stream mode)
140
205
  */
@@ -146,15 +211,22 @@ export interface DingTalkInboundMessage {
146
211
  content: string;
147
212
  isReplyMsg?: boolean; // 是否是回复消息
148
213
  repliedMsg?: {
149
- // 被回复的消息
214
+ msgType?: string;
215
+ msgId?: string;
216
+ senderId?: string;
217
+ createdAt?: number;
150
218
  content?: {
151
219
  text?: string;
220
+ downloadCode?: string;
221
+ biz_custom_action_url?: string;
152
222
  richText?: Array<{
153
223
  msgType?: string;
154
224
  type?: string;
155
225
  content?: string;
226
+ text?: string;
156
227
  code?: string;
157
228
  atName?: string;
229
+ downloadCode?: string;
158
230
  }>;
159
231
  };
160
232
  };
@@ -163,13 +235,16 @@ export interface DingTalkInboundMessage {
163
235
  downloadCode?: string;
164
236
  fileName?: string;
165
237
  recognition?: string;
238
+ spaceId?: string;
239
+ fileId?: string;
240
+ biz_custom_action_url?: string;
166
241
  richText?: Array<{
167
242
  type: string;
168
243
  text?: string;
169
244
  atName?: string;
170
- downloadCode?: string; // For picture type in richText
245
+ downloadCode?: string;
171
246
  }>;
172
- quoteContent?: string; // 替代引用格式
247
+ quoteContent?: string;
173
248
  };
174
249
  // Legacy 引用格式
175
250
  quoteMessage?: {
@@ -181,6 +256,7 @@ export interface DingTalkInboundMessage {
181
256
  };
182
257
  // 富媒体引用,仅有消息ID的情况(包括手机端和PC端)
183
258
  originalMsgId?: string;
259
+ originalProcessQueryKey?: string;
184
260
  conversationType: string;
185
261
  conversationId: string;
186
262
  conversationTitle?: string;
@@ -191,14 +267,39 @@ export interface DingTalkInboundMessage {
191
267
  sessionWebhook: string;
192
268
  }
193
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
+
194
290
  /**
195
291
  * Extracted message content for unified processing
196
292
  */
197
293
  export interface MessageContent {
198
294
  text: string;
199
295
  mediaPath?: string;
296
+ mediaPaths?: string[];
200
297
  mediaType?: string;
298
+ mediaTypes?: string[];
201
299
  messageType: string;
300
+ docSpaceId?: string;
301
+ docFileId?: string;
302
+ quoted?: QuotedInfo;
202
303
  }
203
304
 
204
305
  /**
@@ -208,12 +309,21 @@ export interface SendMessageOptions {
208
309
  title?: string;
209
310
  useMarkdown?: boolean;
210
311
  atUserId?: string | null;
211
- log?: any;
312
+ log?: Logger;
313
+ conversationId?: string;
212
314
  mediaPath?: string;
213
315
  filePath?: string;
214
316
  mediaUrl?: string;
215
317
  mediaType?: "image" | "voice" | "video" | "file";
216
318
  accountId?: string;
319
+ storePath?: string;
320
+ cardUpdateMode?: "append";
321
+ }
322
+
323
+ export interface DingTalkTrackingMetadata {
324
+ processQueryKey?: string;
325
+ outTrackId?: string;
326
+ cardInstanceId?: string;
217
327
  }
218
328
 
219
329
  /**
@@ -242,7 +352,7 @@ export interface HandleDingTalkMessageParams {
242
352
  accountId: string;
243
353
  data: DingTalkInboundMessage;
244
354
  sessionWebhook: string;
245
- log?: any;
355
+ log?: Logger;
246
356
  dingtalkConfig: DingTalkConfig;
247
357
  }
248
358
 
@@ -283,7 +393,7 @@ export interface ResolvedAccount {
283
393
  export interface AxiosRequestConfig {
284
394
  url?: string;
285
395
  method?: string;
286
- data?: any;
396
+ data?: unknown;
287
397
  headers?: Record<string, string>;
288
398
  responseType?: "arraybuffer" | "json" | "text";
289
399
  }
@@ -291,7 +401,7 @@ export interface AxiosRequestConfig {
291
401
  /**
292
402
  * HTTP response from axios
293
403
  */
294
- export interface AxiosResponse<T = any> {
404
+ export interface AxiosResponse<T = unknown> {
295
405
  data: T;
296
406
  status: number;
297
407
  statusText: string;
@@ -313,15 +423,15 @@ export interface StreamCallbackResponse {
313
423
  */
314
424
  export interface ReplyDispatchContext {
315
425
  responsePrefix?: string;
316
- deliver: (payload: any) => Promise<{ ok: boolean; error?: string }>;
426
+ deliver: (payload: unknown) => Promise<{ ok: boolean; error?: string }>;
317
427
  }
318
428
 
319
429
  /**
320
430
  * Reply dispatcher result
321
431
  */
322
432
  export interface ReplyDispatcherResult {
323
- dispatcher: any;
324
- replyOptions: any;
433
+ dispatcher: unknown;
434
+ replyOptions: unknown;
325
435
  markDispatchIdle: () => void;
326
436
  }
327
437
 
@@ -331,7 +441,7 @@ export interface ReplyDispatcherResult {
331
441
  export interface RetryOptions {
332
442
  maxRetries?: number;
333
443
  baseDelayMs?: number;
334
- log?: any;
444
+ log?: Logger;
335
445
  }
336
446
 
337
447
  /**
@@ -385,7 +495,7 @@ export interface TargetResolutionResult {
385
495
  */
386
496
  export interface ResolveTargetParams {
387
497
  to?: string | null;
388
- [key: string]: any;
498
+ [key: string]: unknown;
389
499
  }
390
500
 
391
501
  /**
@@ -396,7 +506,7 @@ export interface SendTextParams {
396
506
  to: string;
397
507
  text: string;
398
508
  accountId?: string;
399
- [key: string]: any;
509
+ [key: string]: unknown;
400
510
  }
401
511
 
402
512
  /**
@@ -407,7 +517,7 @@ export interface SendMediaParams {
407
517
  to: string;
408
518
  mediaPath: string;
409
519
  accountId?: string;
410
- [key: string]: any;
520
+ [key: string]: unknown;
411
521
  }
412
522
 
413
523
  /**
@@ -416,8 +526,8 @@ export interface SendMediaParams {
416
526
  export interface DingTalkOutboundHandler {
417
527
  deliveryMode: "direct" | "queued" | "batch";
418
528
  resolveTarget: (params: ResolveTargetParams) => TargetResolutionResult;
419
- sendText: (params: SendTextParams) => Promise<{ ok: boolean; data?: any; error?: any }>;
420
- sendMedia?: (params: SendMediaParams) => Promise<{ ok: boolean; data?: any; error?: any }>;
529
+ sendText: (params: SendTextParams) => Promise<{ ok: boolean; data?: unknown; error?: unknown }>;
530
+ sendMedia?: (params: SendMediaParams) => Promise<{ ok: boolean; data?: unknown; error?: unknown }>;
421
531
  }
422
532
 
423
533
  /**
@@ -440,12 +550,17 @@ export type AICardState = (typeof AICardStatus)[keyof typeof AICardStatus];
440
550
  */
441
551
  export interface AICardInstance {
442
552
  cardInstanceId: string;
553
+ processQueryKey?: string;
443
554
  accessToken: string;
444
555
  conversationId: string;
556
+ accountId?: string;
557
+ storePath?: string;
445
558
  createdAt: number;
446
559
  lastUpdated: number;
447
560
  state: AICardState; // Current card state: PROCESSING, INPUTING, FINISHED, FAILED
448
561
  config?: DingTalkConfig; // Store config reference for token refresh
562
+ lastStreamedContent?: string;
563
+ outTrackId?: string;
449
564
  }
450
565
 
451
566
  /**
@@ -472,6 +587,14 @@ export enum ConnectionState {
472
587
  FAILED = "FAILED",
473
588
  }
474
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
+
475
598
  /**
476
599
  * Connection manager configuration
477
600
  */
@@ -482,6 +605,8 @@ export interface ConnectionManagerConfig {
482
605
  jitter: number;
483
606
  /** Maximum number of runtime reconnect cycles before giving up (default: 10) */
484
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;
485
610
  /** Callback invoked when connection state changes */
486
611
  onStateChange?: (state: ConnectionState, error?: string) => void;
487
612
  }
@@ -555,7 +680,8 @@ export function resolveDingTalkAccount(
555
680
  dmPolicy: dingtalk?.dmPolicy,
556
681
  groupPolicy: dingtalk?.groupPolicy,
557
682
  allowFrom: dingtalk?.allowFrom,
558
- showThinking: dingtalk?.showThinking,
683
+ journalTTLDays: dingtalk?.journalTTLDays,
684
+ ackReaction: dingtalk?.ackReaction,
559
685
  debug: dingtalk?.debug,
560
686
  messageType: dingtalk?.messageType,
561
687
  cardTemplateId: dingtalk?.cardTemplateId,
@@ -567,7 +693,20 @@ export function resolveDingTalkAccount(
567
693
  maxReconnectDelay: dingtalk?.maxReconnectDelay,
568
694
  reconnectJitter: dingtalk?.reconnectJitter,
569
695
  maxReconnectCycles: dingtalk?.maxReconnectCycles,
696
+ reconnectDeadlineMs: dingtalk?.reconnectDeadlineMs,
697
+ useConnectionManager: dingtalk?.useConnectionManager,
698
+ mediaMaxMb: dingtalk?.mediaMaxMb,
699
+ keepAlive: dingtalk?.keepAlive,
700
+ bypassProxyForSend: dingtalk?.bypassProxyForSend,
570
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,
571
710
  };
572
711
  return {
573
712
  ...config,
@@ -576,13 +715,17 @@ export function resolveDingTalkAccount(
576
715
  };
577
716
  }
578
717
 
579
- // If named account, get from accounts object
718
+ // If named account, merge channel-level defaults with account-level overrides
580
719
  const accountConfig = dingtalk?.accounts?.[id];
581
720
  if (accountConfig) {
721
+ const merged = mergeAccountWithDefaults(
722
+ dingtalk as DingTalkConfig,
723
+ accountConfig,
724
+ );
582
725
  return {
583
- ...accountConfig,
726
+ ...merged,
584
727
  accountId: id,
585
- configured: Boolean(accountConfig.clientId && accountConfig.clientSecret),
728
+ configured: Boolean(merged.clientId && merged.clientSecret),
586
729
  };
587
730
  }
588
731