@soimy/dingtalk 3.4.1 → 3.5.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/src/onboarding.ts CHANGED
@@ -115,9 +115,6 @@ function applyAccountConfig(params: {
115
115
  const payload: Partial<DingTalkConfig> = {
116
116
  ...(input.clientId ? { clientId: input.clientId } : {}),
117
117
  ...(input.clientSecret ? { clientSecret: input.clientSecret } : {}),
118
- ...(input.robotCode ? { robotCode: input.robotCode } : {}),
119
- ...(input.corpId ? { corpId: input.corpId } : {}),
120
- ...(input.agentId ? { agentId: input.agentId } : {}),
121
118
  ...(input.dmPolicy ? { dmPolicy: input.dmPolicy } : {}),
122
119
  ...(input.groupPolicy ? { groupPolicy: input.groupPolicy } : {}),
123
120
  ...(input.allowFrom && input.allowFrom.length > 0 ? { allowFrom: input.allowFrom } : {}),
@@ -192,7 +189,6 @@ function applyGenericSetupInput(params: {
192
189
  clientId: typeof params.input.token === "string" ? params.input.token.trim() : undefined,
193
190
  clientSecret:
194
191
  typeof params.input.password === "string" ? params.input.password.trim() : undefined,
195
- robotCode: typeof params.input.code === "string" ? params.input.code.trim() : undefined,
196
192
  },
197
193
  });
198
194
  }
@@ -233,44 +229,6 @@ async function configureDingTalkAccount(params: {
233
229
  validate: (value: string) => (String(value ?? "").trim() ? undefined : "Required"),
234
230
  });
235
231
 
236
- const wantsFullConfig = await prompter.confirm({
237
- message: "Configure robot code, corp ID, and agent ID? (recommended for full features)",
238
- initialValue: false,
239
- });
240
-
241
- let robotCode: string | undefined;
242
- let corpId: string | undefined;
243
- let agentId: string | undefined;
244
-
245
- if (wantsFullConfig) {
246
- robotCode =
247
- String(
248
- await prompter.text({
249
- message: "Robot Code",
250
- placeholder: "dingxxxxxxxx",
251
- initialValue: resolved.robotCode ?? undefined,
252
- }),
253
- ).trim() || undefined;
254
-
255
- corpId =
256
- String(
257
- await prompter.text({
258
- message: "Corp ID",
259
- placeholder: "dingxxxxxxxx",
260
- initialValue: resolved.corpId ?? undefined,
261
- }),
262
- ).trim() || undefined;
263
-
264
- agentId =
265
- String(
266
- await prompter.text({
267
- message: "Agent ID",
268
- placeholder: "123456789",
269
- initialValue: resolved.agentId ? String(resolved.agentId) : undefined,
270
- }),
271
- ).trim() || undefined;
272
- }
273
-
274
232
  const wantsCardMode = await prompter.confirm({
275
233
  message: "Enable AI interactive card mode? (for streaming AI responses)",
276
234
  initialValue: resolved.messageType === "card",
@@ -471,9 +429,6 @@ async function configureDingTalkAccount(params: {
471
429
  input: {
472
430
  clientId: String(clientId).trim(),
473
431
  clientSecret: String(clientSecret).trim(),
474
- robotCode,
475
- corpId,
476
- agentId,
477
432
  dmPolicy: dmPolicyValue as "open" | "allowlist",
478
433
  groupPolicy: groupPolicyValue as "open" | "allowlist" | "disabled",
479
434
  allowFrom,
@@ -8,7 +8,6 @@
8
8
 
9
9
  import {
10
10
  finishAICard,
11
- formatContentForCard,
12
11
  isCardInTerminalState,
13
12
  } from "./card-service";
14
13
  import { createCardDraftController } from "./card-draft-controller";
@@ -18,6 +17,8 @@ import type { AICardInstance } from "./types";
18
17
  import { AICardStatus } from "./types";
19
18
  import { formatDingTalkErrorPayloadLog } from "./utils";
20
19
 
20
+ const FILE_ONLY_FALLBACK_ANSWER = "附件已发送,请查收。";
21
+
21
22
  export function createCardReplyStrategy(
22
23
  ctx: ReplyStrategyContext & { card: AICardInstance },
23
24
  ): ReplyStrategy {
@@ -25,6 +26,15 @@ export function createCardReplyStrategy(
25
26
 
26
27
  const controller = createCardDraftController({ card, log });
27
28
  let finalTextForFallback: string | undefined;
29
+ let sawFinalDelivery = false;
30
+
31
+ const getRenderedTimeline = (options: { preferFinalAnswer?: boolean } = {}): string => {
32
+ const fallbackAnswer = finalTextForFallback || (sawFinalDelivery ? FILE_ONLY_FALLBACK_ANSWER : undefined);
33
+ return controller.getRenderedContent({
34
+ fallbackAnswer,
35
+ overrideAnswer: options.preferFinalAnswer ? finalTextForFallback : undefined,
36
+ });
37
+ };
28
38
 
29
39
  return {
30
40
  getReplyOptions(): ReplyOptions {
@@ -33,21 +43,21 @@ export function createCardReplyStrategy(
33
43
  // onPartialReply (real-time) or deliver(final) -> finishAICard.
34
44
  disableBlockStreaming: true,
35
45
 
36
- onAssistantMessageStart: () => {
37
- controller.notifyNewAssistantTurn();
46
+ onAssistantMessageStart: async () => {
47
+ await controller.notifyNewAssistantTurn();
38
48
  },
39
49
 
40
50
  onPartialReply: config.cardRealTimeStream
41
- ? (payload) => {
51
+ ? async (payload) => {
42
52
  if (payload.text) {
43
- controller.updateAnswer(payload.text);
53
+ await controller.updateAnswer(payload.text);
44
54
  }
45
55
  }
46
56
  : undefined,
47
57
 
48
- onReasoningStream: (payload) => {
58
+ onReasoningStream: async (payload) => {
49
59
  if (payload.text) {
50
- controller.updateReasoning(payload.text);
60
+ await controller.updateThinking(payload.text);
51
61
  }
52
62
  },
53
63
  };
@@ -65,6 +75,7 @@ export function createCardReplyStrategy(
65
75
 
66
76
  // ---- final: defer to finalize, just save text ----
67
77
  if (payload.kind === "final") {
78
+ sawFinalDelivery = true;
68
79
  log?.info?.(
69
80
  `[DingTalk][Finalize] deliver(final) received — cardState=${card.state} ` +
70
81
  `textLen=${typeof textToSend === "string" ? textToSend.length : "null"} ` +
@@ -88,27 +99,10 @@ export function createCardReplyStrategy(
88
99
  log?.debug?.("[DingTalk] Card failed, skipping tool result (will send full reply on final)");
89
100
  return;
90
101
  }
91
- await controller.flush();
92
- await controller.waitForInFlight();
93
102
  log?.info?.(
94
103
  `[DingTalk] Tool result received, streaming to AI Card: ${(textToSend ?? "").slice(0, 100)}`,
95
104
  );
96
- const toolText = typeof textToSend === "string" ? formatContentForCard(textToSend, "tool") : "";
97
- if (toolText) {
98
- const sendResult = await sendMessage(ctx.config, ctx.to, toolText, {
99
- sessionWebhook: ctx.sessionWebhook,
100
- atUserId: !ctx.isDirect ? ctx.senderId : null,
101
- log,
102
- card,
103
- accountId: ctx.accountId,
104
- storePath: ctx.storePath,
105
- conversationId: ctx.groupId,
106
- cardUpdateMode: "append",
107
- });
108
- if (!sendResult.ok) {
109
- throw new Error(sendResult.error || "Tool stream send failed");
110
- }
111
- }
105
+ await controller.appendTool(textToSend ?? "");
112
106
  return;
113
107
  }
114
108
 
@@ -135,7 +129,7 @@ export function createCardReplyStrategy(
135
129
 
136
130
  // Card failed -> markdown fallback (bypass sendMessage to avoid duplicate card).
137
131
  if (card.state === AICardStatus.FAILED || controller.isFailed()) {
138
- const fallbackText = finalTextForFallback
132
+ const fallbackText = getRenderedTimeline({ preferFinalAnswer: true })
139
133
  || controller.getLastAnswerContent()
140
134
  || controller.getLastContent()
141
135
  || card.lastStreamedContent;
@@ -164,13 +158,11 @@ export function createCardReplyStrategy(
164
158
  try {
165
159
  await controller.flush();
166
160
  await controller.waitForInFlight();
161
+ const finalText = getRenderedTimeline() || "✅ Done";
167
162
  controller.stop();
168
- const finalText = controller.getLastAnswerContent()
169
- || finalTextForFallback
170
- || "✅ Done";
171
163
  log?.info?.(
172
164
  `[DingTalk][Finalize] Calling finishAICard — finalTextLen=${finalText.length} ` +
173
- `source=${controller.getLastAnswerContent() ? "lastAnswerContent" : finalTextForFallback ? "finalTextForFallback" : "fallbackDone"} ` +
165
+ `source=${controller.getFinalAnswerContent() ? "timeline.answer" : sawFinalDelivery ? "timeline.fileOnly" : "fallbackDone"} ` +
174
166
  `preview="${finalText.slice(0, 120)}"`,
175
167
  );
176
168
  await finishAICard(card, finalText, log, {
@@ -219,7 +211,9 @@ export function createCardReplyStrategy(
219
211
  },
220
212
 
221
213
  getFinalText(): string | undefined {
222
- return controller.getLastAnswerContent() || finalTextForFallback;
214
+ return controller.getFinalAnswerContent()
215
+ || finalTextForFallback
216
+ || (sawFinalDelivery ? FILE_ONLY_FALLBACK_ANSWER : undefined);
223
217
  },
224
218
  };
225
219
  }
@@ -20,9 +20,9 @@ export interface DeliverPayload {
20
20
 
21
21
  export interface ReplyOptions {
22
22
  disableBlockStreaming: boolean;
23
- onPartialReply?: (payload: { text?: string }) => void;
24
- onReasoningStream?: (payload: { text?: string }) => void;
25
- onAssistantMessageStart?: () => void;
23
+ onPartialReply?: (payload: { text?: string }) => void | Promise<void>;
24
+ onReasoningStream?: (payload: { text?: string }) => void | Promise<void>;
25
+ onAssistantMessageStart?: () => void | Promise<void>;
26
26
  }
27
27
 
28
28
  export interface ReplyStrategy {
@@ -4,13 +4,17 @@ import { getAccessToken } from "./auth";
4
4
  import {
5
5
  isCardInTerminalState,
6
6
  sendProactiveCardText,
7
- streamAICard,
8
7
  } from "./card-service";
9
- import { stripTargetPrefix } from "./config";
8
+ import { resolveRobotCode, stripTargetPrefix } from "./config";
10
9
  import { getLogger } from "./logger-context";
11
- import { getVoiceDurationMs, uploadMedia as uploadMediaUtil, type UploadMediaResult } from "./media-utils";
10
+ import { getVoiceDurationMs, uploadMedia as uploadMediaUtil } from "./media-utils";
12
11
  import { convertMarkdownTablesToPlainText, detectMarkdownAndExtractTitle } from "./message-utils";
13
- import { DEFAULT_MESSAGE_CONTEXT_TTL_DAYS, upsertOutboundMessageContext } from "./message-context-store";
12
+ import {
13
+ DEFAULT_MESSAGE_CONTEXT_TTL_DAYS,
14
+ DEFAULT_OUTBOUND_SENDER,
15
+ inferConversationChatType,
16
+ upsertOutboundMessageContext,
17
+ } from "./message-context-store";
14
18
  import { resolveOriginalPeerId } from "./peer-id-registry";
15
19
  import {
16
20
  deleteProactiveRiskObservation,
@@ -18,6 +22,7 @@ import {
18
22
  recordProactiveRiskObservation,
19
23
  } from "./proactive-risk-registry";
20
24
  import { formatDingTalkErrorPayloadLog, getProxyBypassOption } from "./utils";
25
+ import type { UploadMediaResult } from "./media-utils";
21
26
  import type {
22
27
  AICardInstance,
23
28
  AxiosResponse,
@@ -29,7 +34,6 @@ import type {
29
34
  SendMessageOptions,
30
35
  SessionWebhookResponse,
31
36
  } from "./types";
32
- import { AICardStatus } from "./types";
33
37
 
34
38
  export { detectMediaTypeFromExtension } from "./media-utils";
35
39
 
@@ -78,6 +82,9 @@ function persistOutboundMessageContext(params: {
78
82
  createdAt?: number;
79
83
  quotedRef?: QuotedRef;
80
84
  log?: Logger;
85
+ senderId?: string;
86
+ senderName?: string;
87
+ chatType?: "direct" | "group";
81
88
  delivery: {
82
89
  messageId?: string;
83
90
  processQueryKey?: string;
@@ -101,6 +108,9 @@ function persistOutboundMessageContext(params: {
101
108
  createdAt: params.createdAt ?? Date.now(),
102
109
  text: params.text,
103
110
  messageType: params.messageType,
111
+ senderId: params.senderId,
112
+ senderName: params.senderName,
113
+ chatType: params.chatType,
104
114
  ttlMs: DEFAULT_MESSAGE_CONTEXT_TTL_DAYS * 24 * 60 * 60 * 1000,
105
115
  topic: null,
106
116
  quotedRef: params.quotedRef,
@@ -118,26 +128,6 @@ function buildPersistedOutboundText(text: string, options: SendMessageOptions):
118
128
  return text;
119
129
  }
120
130
 
121
- function composeCardContentForAppend(previous: string | undefined, incoming: string): string {
122
- const prev = previous ?? "";
123
- if (!prev) {
124
- return incoming;
125
- }
126
- if (!incoming) {
127
- return prev;
128
- }
129
- if (incoming.startsWith(prev)) {
130
- return incoming;
131
- }
132
- if (prev.endsWith(incoming)) {
133
- return prev;
134
- }
135
- if (prev.endsWith("\n") || incoming.startsWith("\n")) {
136
- return `${prev}${incoming}`;
137
- }
138
- return `${prev}${incoming}`;
139
- }
140
-
141
131
  const DINGTALK_TEXT_CHUNK_LIMIT = 3800;
142
132
 
143
133
  function splitMarkdownChunks(text: string, limit = DINGTALK_TEXT_CHUNK_LIMIT): string[] {
@@ -203,7 +193,6 @@ function isProactivePermissionOrScopeError(code: string | null): boolean {
203
193
 
204
194
  /**
205
195
  * Wrapper to upload media with shared getAccessToken binding.
206
- * Supports sandbox/container paths via mediaLocalRoots option.
207
196
  */
208
197
  export async function uploadMedia(
209
198
  config: DingTalkConfig,
@@ -277,7 +266,7 @@ export async function sendProactiveTextOrMarkdown(
277
266
  : JSON.stringify({ content: normalizedText });
278
267
 
279
268
  const payload: ProactiveMessagePayload = {
280
- robotCode: config.robotCode || config.clientId,
269
+ robotCode: resolveRobotCode(config),
281
270
  msgKey,
282
271
  msgParam,
283
272
  };
@@ -343,7 +332,7 @@ export async function sendProactiveMedia(
343
332
  target: string,
344
333
  mediaPath: string,
345
334
  mediaType: "image" | "voice" | "video" | "file",
346
- options: SendMessageOptions & { accountId?: string; mediaLocalRoots?: string[] } = {},
335
+ options: SendMessageOptions & { accountId?: string } = {},
347
336
  ): Promise<{ ok: boolean; error?: string; data?: any; messageId?: string }> {
348
337
  const log = options.log || getLogger();
349
338
 
@@ -355,7 +344,7 @@ export async function sendProactiveMedia(
355
344
  if (!uploadResult) {
356
345
  return { ok: false, error: "Failed to upload media" };
357
346
  }
358
- const { mediaId, buffer: uploadedBuffer } = uploadResult;
347
+ const { mediaId, buffer } = uploadResult;
359
348
 
360
349
  const token = await getAccessToken(config, log);
361
350
  const { targetId, isExplicitUser } = stripTargetPrefix(target);
@@ -376,10 +365,7 @@ export async function sendProactiveMedia(
376
365
  msgParam = JSON.stringify({ photoURL: mediaId });
377
366
  } else if (mediaType === "voice") {
378
367
  msgKey = "sampleAudio";
379
- // Reuse buffer from upload to avoid reading the file twice
380
- const durationMs = await getVoiceDurationMs(mediaPath, mediaType, log, {
381
- preReadBuffer: uploadedBuffer,
382
- });
368
+ const durationMs = await getVoiceDurationMs(mediaPath, mediaType, log, { preReadBuffer: buffer });
383
369
  msgParam = JSON.stringify({ mediaId, duration: String(durationMs) });
384
370
  } else {
385
371
  // sampleVideo requires picMediaId; fallback to sampleFile for broader compatibility.
@@ -391,7 +377,7 @@ export async function sendProactiveMedia(
391
377
  }
392
378
 
393
379
  const payload: ProactiveMessagePayload = {
394
- robotCode: config.robotCode || config.clientId,
380
+ robotCode: resolveRobotCode(config),
395
381
  msgKey,
396
382
  msgParam,
397
383
  };
@@ -427,6 +413,8 @@ export async function sendProactiveMedia(
427
413
  messageType: "outbound-proactive-media",
428
414
  quotedRef: options.quotedRef,
429
415
  log,
416
+ ...DEFAULT_OUTBOUND_SENDER,
417
+ chatType: inferConversationChatType(options.conversationId || resolvedTarget),
430
418
  delivery: {
431
419
  ...delivery,
432
420
  kind: "proactive-media",
@@ -485,6 +473,8 @@ export async function sendProactiveMedia(
485
473
  messageType: "outbound-proactive-fallback",
486
474
  quotedRef: options.quotedRef,
487
475
  log,
476
+ ...DEFAULT_OUTBOUND_SENDER,
477
+ chatType: inferConversationChatType(options.conversationId || normalizedTarget),
488
478
  delivery: {
489
479
  ...fallbackDelivery,
490
480
  kind: isTrackingResult(fallback as ProactiveTextSendResult) ? "proactive-card" : "proactive-text",
@@ -509,16 +499,13 @@ export async function sendBySession(
509
499
  mediaLocalRoots: options.mediaLocalRoots,
510
500
  });
511
501
  if (uploadResult) {
512
- const { mediaId, buffer: uploadedBuffer } = uploadResult;
502
+ const { mediaId, buffer } = uploadResult;
513
503
  let body: any;
514
504
 
515
505
  if (options.mediaType === "image") {
516
506
  body = { msgtype: "image", image: { media_id: mediaId } };
517
507
  } else if (options.mediaType === "voice") {
518
- // Reuse buffer from upload to avoid reading the file twice
519
- const durationMs = await getVoiceDurationMs(options.mediaPath, options.mediaType, log, {
520
- preReadBuffer: uploadedBuffer,
521
- });
508
+ const durationMs = await getVoiceDurationMs(options.mediaPath, options.mediaType, log, { preReadBuffer: buffer });
522
509
  body = { msgtype: "voice", voice: { media_id: mediaId, duration: String(durationMs) } };
523
510
  } else if (options.mediaType === "video") {
524
511
  body = { msgtype: "video", video: { media_id: mediaId } };
@@ -609,17 +596,6 @@ export async function sendMessage(
609
596
  },
610
597
  };
611
598
  }
612
- } else if (options.cardUpdateMode === "append") {
613
- try {
614
- const nextContent = composeCardContentForAppend(card.lastStreamedContent, text);
615
- await streamAICard(card, nextContent, false, log);
616
- return { ok: true };
617
- } catch (err: any) {
618
- log?.warn?.(`[DingTalk] AI Card streaming failed: ${err.message}`);
619
- card.state = AICardStatus.FAILED;
620
- card.lastUpdated = Date.now();
621
- return { ok: false, error: err.message };
622
- }
623
599
  }
624
600
  }
625
601
 
@@ -636,6 +612,8 @@ export async function sendMessage(
636
612
  messageType: options.mediaPath && options.mediaType ? "outbound-media" : "outbound",
637
613
  quotedRef: options.quotedRef,
638
614
  log,
615
+ ...DEFAULT_OUTBOUND_SENDER,
616
+ chatType: inferConversationChatType(options.conversationId || conversationId),
639
617
  delivery: {
640
618
  ...delivery,
641
619
  kind: "session",
@@ -655,6 +633,8 @@ export async function sendMessage(
655
633
  messageType: "outbound-proactive",
656
634
  quotedRef: options.quotedRef,
657
635
  log,
636
+ ...DEFAULT_OUTBOUND_SENDER,
637
+ chatType: inferConversationChatType(options.conversationId || conversationId),
658
638
  delivery: {
659
639
  ...delivery,
660
640
  kind: isTrackingResult(result) ? "proactive-card" : "proactive-text",
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
11
11
  import { resolveAtAgents } from "./agent-name-matcher";
12
+ import { resolveRobotCode } from "../config";
12
13
  import { parseLearnCommand } from "../learning-command-service";
13
14
  import { getDingTalkRuntime } from "../runtime";
14
15
  import { sendBySession } from "../send-service";
@@ -65,7 +66,12 @@ function sanitizeAgentName(name: string): string {
65
66
  }
66
67
 
67
68
  /**
68
- * Resolve @mention-based sub-agent routing for a group message.
69
+ * Resolve @mention-based sub-agent routing for a group or direct message.
70
+ *
71
+ * In group chats, @mentions are populated by the DingTalk SDK (atMentions field).
72
+ * In direct messages (DM), the SDK also populates atMentions for text-type messages
73
+ * via extractMessageContent in message-utils.ts, so the same field is reused here.
74
+ * The !isGroup guard is removed to enable sub-agent routing in DM as well.
69
75
  *
70
76
  * Returns matched agents if any @mentions resolve to configured agents,
71
77
  * or null if the message should be handled by the default agent.
@@ -85,14 +91,14 @@ export async function resolveSubAgentRoute(params: {
85
91
  const { extractedContent, cfg, isGroup, dingtalkConfig, sessionWebhook, senderId, log } = params;
86
92
 
87
93
  const atMentions = extractedContent.atMentions || [];
88
- const atUserDingtalkIds = extractedContent.atUserDingtalkIds;
94
+ // DM has no @picker list from DingTalk; only group chats provide atUsers for real-user hints.
95
+ const atUserDingtalkIds = isGroup ? extractedContent.atUserDingtalkIds : undefined;
89
96
  // Strip quoted prefix before checking /learn to avoid false positives
90
97
  // when the quoted message itself contains a /learn command.
91
98
  const textForCommandCheck = extractedContent.text.replace(/^\[引用[^\]]*\]\s*/, "");
92
99
  const isLearnCommand = parseLearnCommand(textForCommandCheck).scope !== "unknown";
93
100
 
94
101
  if (
95
- !isGroup ||
96
102
  atMentions.length === 0 ||
97
103
  !cfg.agents?.list ||
98
104
  cfg.agents.list.length === 0 ||
@@ -114,9 +120,9 @@ export async function resolveSubAgentRoute(params: {
114
120
  if (hasInvalidAgentNames) {
115
121
  const fallbackReason = `未找到名为"${unmatchedNames.join("、")}"的助手`;
116
122
  try {
123
+ const sendOptions = isGroup ? { atUserId: senderId, log } : { log };
117
124
  await sendBySession(dingtalkConfig, sessionWebhook, `⚠️ ${fallbackReason}`, {
118
- atUserId: senderId,
119
- log,
125
+ ...sendOptions,
120
126
  });
121
127
  } catch (err: any) {
122
128
  log?.debug?.(`[DingTalk] Failed to send fallback notice: ${err.message}`);
@@ -149,7 +155,7 @@ export async function dispatchSubAgents(params: {
149
155
 
150
156
  // Pre-download media once to avoid duplication across sub-agents
151
157
  let preDownloadedMedia: { mediaPath?: string; mediaType?: string } | undefined;
152
- if (extractedContent.mediaPath && dingtalkConfig.robotCode) {
158
+ if (extractedContent.mediaPath && resolveRobotCode(dingtalkConfig)) {
153
159
  const media = await download(dingtalkConfig, extractedContent.mediaPath, log);
154
160
  if (media) {
155
161
  preDownloadedMedia = { mediaPath: media.path, mediaType: media.mimeType };
package/src/types.ts CHANGED
@@ -32,9 +32,6 @@ export type AckReactionConfigValue = string;
32
32
  export interface DingTalkConfig extends OpenClawConfig {
33
33
  clientId: string;
34
34
  clientSecret: string;
35
- robotCode?: string;
36
- corpId?: string;
37
- agentId?: string;
38
35
  name?: string;
39
36
  enabled?: boolean;
40
37
  dmPolicy?: "open" | "pairing" | "allowlist";
@@ -82,12 +79,6 @@ export interface DingTalkConfig extends OpenClawConfig {
82
79
  learningAutoApply?: boolean;
83
80
  /** Session learning note TTL in milliseconds (default 6h) */
84
81
  learningNoteTtlMs?: number;
85
- /** @deprecated Use learningEnabled */
86
- feedbackLearningEnabled?: boolean;
87
- /** @deprecated Use learningAutoApply */
88
- feedbackLearningAutoApply?: boolean;
89
- /** @deprecated Use learningNoteTtlMs */
90
- feedbackLearningNoteTtlMs?: number;
91
82
  /** Whether to convert markdown tables to plain text for better rendering on some clients (default: true) */
92
83
  convertMarkdownTables?: boolean;
93
84
  /** @mention the sender after card finalization in group chats; value is the message text */
@@ -101,9 +92,6 @@ export interface DingTalkChannelConfig {
101
92
  enabled?: boolean;
102
93
  clientId: string;
103
94
  clientSecret: string;
104
- robotCode?: string;
105
- corpId?: string;
106
- agentId?: string;
107
95
  name?: string;
108
96
  dmPolicy?: "open" | "pairing" | "allowlist";
109
97
  groupPolicy?: "open" | "allowlist" | "disabled";
@@ -149,12 +137,6 @@ export interface DingTalkChannelConfig {
149
137
  learningAutoApply?: boolean;
150
138
  /** Session learning note TTL in milliseconds (default 6h) */
151
139
  learningNoteTtlMs?: number;
152
- /** @deprecated Use learningEnabled */
153
- feedbackLearningEnabled?: boolean;
154
- /** @deprecated Use learningAutoApply */
155
- feedbackLearningAutoApply?: boolean;
156
- /** @deprecated Use learningNoteTtlMs */
157
- feedbackLearningNoteTtlMs?: number;
158
140
  /** Whether to convert markdown tables to plain text for better rendering on some clients (default: true) */
159
141
  convertMarkdownTables?: boolean;
160
142
  /** @mention the sender after card finalization in group chats; value is the message text */
@@ -313,6 +295,7 @@ export interface QuotedInfo {
313
295
  cardCreatedAt?: number;
314
296
  processQueryKey?: string;
315
297
  fileCreatedAt?: number;
298
+ fileDownloadCode?: string;
316
299
  msgId?: string;
317
300
  previewText?: string;
318
301
  previewMessageType?: string;
@@ -771,9 +754,6 @@ export function resolveDingTalkAccount(
771
754
  const config: DingTalkConfig = {
772
755
  clientId: dingtalk?.clientId ?? "",
773
756
  clientSecret: dingtalk?.clientSecret ?? "",
774
- robotCode: dingtalk?.robotCode,
775
- corpId: dingtalk?.corpId,
776
- agentId: dingtalk?.agentId,
777
757
  name: dingtalk?.name,
778
758
  enabled: dingtalk?.enabled,
779
759
  dmPolicy: dingtalk?.dmPolicy,
@@ -802,12 +782,9 @@ export function resolveDingTalkAccount(
802
782
  proactivePermissionHint: dingtalk?.proactivePermissionHint,
803
783
  cardRealTimeStream: dingtalk?.cardRealTimeStream,
804
784
  aicardDegradeMs: dingtalk?.aicardDegradeMs,
805
- learningEnabled: dingtalk?.learningEnabled ?? dingtalk?.feedbackLearningEnabled,
806
- learningAutoApply: dingtalk?.learningAutoApply ?? dingtalk?.feedbackLearningAutoApply,
807
- learningNoteTtlMs: dingtalk?.learningNoteTtlMs ?? dingtalk?.feedbackLearningNoteTtlMs,
808
- feedbackLearningEnabled: dingtalk?.feedbackLearningEnabled,
809
- feedbackLearningAutoApply: dingtalk?.feedbackLearningAutoApply,
810
- feedbackLearningNoteTtlMs: dingtalk?.feedbackLearningNoteTtlMs,
785
+ learningEnabled: dingtalk?.learningEnabled,
786
+ learningAutoApply: dingtalk?.learningAutoApply,
787
+ learningNoteTtlMs: dingtalk?.learningNoteTtlMs,
811
788
  convertMarkdownTables: dingtalk?.convertMarkdownTables,
812
789
  cardAtSender: dingtalk?.cardAtSender,
813
790
  };