@dingtalk-real-ai/dingtalk-connector 0.8.0-beta.2 → 0.8.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.1] - 2026-03-20
9
+
10
+ ### 修复 / Fixes
11
+ - 🐛 **文件和图片下载 OSS 签名验证失败** - 修复默认 `Content-Type` 请求头导致 OSS 签名验证失败的问题,确保文件和图片能够正常下载
12
+ **File and image download OSS signature verification failure** - Fixed OSS signature verification failure caused by default `Content-Type` header, ensuring files and images download correctly
13
+
14
+ ## [0.8.0] - 2026-03-20
15
+
16
+ ### 重构 / Refactoring
17
+ - ✅ **业务逻辑分层重构** - 对项目结构进行重构,采用业务逻辑分层设计,明确职责边界,降低模块耦合并提升可维护性
18
+ **Business-logic layered refactor** - Reworked project architecture with layered business logic design, clarifying responsibilities, reducing coupling, and improving maintainability
19
+ - ✅ **OpenClaw 对接方式升级** - 从 OpenClaw HTTP 对接迁移为 OpenClaw SDK,统一调用链路并增强集成稳定性
20
+ **OpenClaw integration upgrade** - Migrated from OpenClaw HTTP integration to OpenClaw SDK for a unified invocation flow and better integration stability
21
+
22
+ ### 改进 / Improvements
23
+ - ✅ **IM 交互体验优化** - 优化 IM 场景下的部分交互细节,提升消息处理与反馈体验
24
+ **IM interaction optimization** - Improved several interaction details in IM scenarios to provide smoother message handling and feedback
25
+ - ✅ **README 重写与配置简化** - 重写 README 文档,简化配置教程,降低接入门槛并提升上手效率
26
+ **README rewrite & simpler setup** - Rewrote README and simplified setup guidance to reduce onboarding complexity and speed up adoption
27
+
28
+ ### 修复 / Fixes
29
+ - 🐛 **dingtalk-stream 断连问题修复** - 修复 dingtalk-stream 相关的部分断连场景,增强长连接稳定性与恢复能力
30
+ **dingtalk-stream disconnect fixes** - Fixed several dingtalk-stream related disconnection scenarios, improving long-connection stability and recovery
31
+
8
32
  ## [0.7.10] - 2026-03-16
9
33
 
10
34
  ### 新增 / Added
package/README.en.md CHANGED
@@ -268,20 +268,26 @@ Configure multiple bots connected to different agents:
268
268
  "agents": {
269
269
  "list": [
270
270
  {
271
- "agentId": "ding-bot1",
271
+ "id": "ding-bot1",
272
+ "name": "Customer Service Bot",
272
273
  "model": "your-model-config",
273
- "persona": {
274
- "name": "Customer Service Bot",
275
- "systemPrompt": "You are a professional customer service assistant..."
274
+ "workspace": "~/.openclaw/workspace-bot1",
275
+ "identity": {
276
+ "name": "Service Assistant",
277
+ "theme": "customer service",
278
+ "emoji": "🤝"
276
279
  }
277
280
  // Other agent configurations...
278
281
  },
279
282
  {
280
- "agentId": "ding-bot2",
283
+ "id": "ding-bot2",
284
+ "name": "Technical Support Bot",
281
285
  "model": "your-model-config",
282
- "persona": {
283
- "name": "Technical Support Bot",
284
- "systemPrompt": "You are a technical support expert..."
286
+ "workspace": "~/.openclaw/workspace-bot2",
287
+ "identity": {
288
+ "name": "Tech Expert",
289
+ "theme": "technical support",
290
+ "emoji": "🔧"
285
291
  }
286
292
  // Other agent configurations...
287
293
  }
package/README.md CHANGED
@@ -279,20 +279,26 @@ openclaw logs --follow
279
279
  "agents": {
280
280
  "list": [
281
281
  {
282
- "agentId": "ding-bot1",
282
+ "id": "ding-bot1",
283
+ "name": "钉钉客服机器人",
283
284
  "model": "your-model-config",
284
- "persona": {
285
- "name": "钉钉客服机器人",
286
- "systemPrompt": "你是一个专业的客服助手..."
285
+ "workspace": "~/.openclaw/workspace-bot1",
286
+ "identity": {
287
+ "name": "客服小助手",
288
+ "theme": "专业客服",
289
+ "emoji": "🤝"
287
290
  }
288
291
  // 其他 agent 配置...
289
292
  },
290
293
  {
291
- "agentId": "ding-bot2",
294
+ "id": "ding-bot2",
295
+ "name": "钉钉技术支持机器人",
292
296
  "model": "your-model-config",
293
- "persona": {
294
- "name": "钉钉技术支持机器人",
295
- "systemPrompt": "你是一个技术支持专家..."
297
+ "workspace": "~/.openclaw/workspace-bot2",
298
+ "identity": {
299
+ "name": "技术专家",
300
+ "theme": "技术支持",
301
+ "emoji": "🔧"
296
302
  }
297
303
  // 其他 agent 配置...
298
304
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dingtalk-connector",
3
3
  "name": "DingTalk Channel",
4
- "version": "0.8.0-beta.2",
4
+ "version": "0.8.1",
5
5
  "description": "DingTalk (钉钉) messaging channel via Stream mode with AI Card streaming",
6
6
  "author": "DingTalk Real Team",
7
7
  "main": "index.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dingtalk-real-ai/dingtalk-connector",
3
- "version": "0.8.0-beta.2",
3
+ "version": "0.8.1",
4
4
  "description": "DingTalk (钉钉) channel connector — Stream mode with AI Card streaming",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -138,6 +138,7 @@ export async function monitorSingleAccount(
138
138
  // ============ 连接状态管理 ============
139
139
 
140
140
  let lastSocketAvailableTime = Date.now();
141
+ let connectionEstablishedTime = Date.now(); // 记录连接建立时间
141
142
  let isReconnecting = false;
142
143
  let reconnectAttempts = 0;
143
144
  let keepAliveTimer: NodeJS.Timeout | null = null;
@@ -226,18 +227,49 @@ export async function monitorSingleAccount(
226
227
  // 2. 重新建立连接
227
228
  await client.connect();
228
229
 
229
- // 3. 验证连接是否真正建立(必须检查 socket 状态)
230
- const socketState = client.socket?.readyState;
231
- if (socketState !== 1) {
232
- // socket 状态不是 OPEN (1),说明连接未真正建立
233
- throw new Error(`连接未建立,socket 状态=${socketState} (期望=1)`);
230
+ // 3. 等待连接真正建立(监听 open 事件,最多等待 10 秒)
231
+ const connectionEstablished = await new Promise<boolean>((resolve) => {
232
+ const timeout = setTimeout(() => {
233
+ resolve(false);
234
+ }, 10_000); // 10 秒超时
235
+
236
+ // 如果已经是 OPEN 状态,直接返回
237
+ if (client.socket?.readyState === 1) {
238
+ clearTimeout(timeout);
239
+ resolve(true);
240
+ return;
241
+ }
242
+
243
+ // 否则监听 open 事件
244
+ const onOpen = () => {
245
+ clearTimeout(timeout);
246
+ client.socket?.removeListener('open', onOpen);
247
+ client.socket?.removeListener('error', onError);
248
+ resolve(true);
249
+ };
250
+
251
+ const onError = (err: any) => {
252
+ clearTimeout(timeout);
253
+ client.socket?.removeListener('open', onOpen);
254
+ client.socket?.removeListener('error', onError);
255
+ logger.warn(`连接建立失败: ${err.message}`);
256
+ resolve(false);
257
+ };
258
+
259
+ client.socket?.once('open', onOpen);
260
+ client.socket?.once('error', onError);
261
+ });
262
+
263
+ if (!connectionEstablished) {
264
+ throw new Error(`连接建立超时或失败`);
234
265
  }
235
266
 
236
- // 4. 重置 socket 可用时间和重连计数
267
+ // 4. 重置 socket 可用时间、连接建立时间和重连计数
237
268
  lastSocketAvailableTime = Date.now();
269
+ connectionEstablishedTime = Date.now(); // 重置连接建立时间
238
270
  reconnectAttempts = 0; // 重连成功,重置计数
239
271
 
240
- logger.info(`✅ 重连成功 (socket 状态=${socketState})`);
272
+ logger.info(`✅ 重连成功 (socket 状态=${client.socket?.readyState})`);
241
273
  } catch (err: any) {
242
274
  reconnectAttempts++;
243
275
  log?.error?.(
@@ -329,11 +361,20 @@ export async function monitorSingleAccount(
329
361
 
330
362
  // 【心跳检测】检查 socket 状态
331
363
  const socketState = client.socket?.readyState;
364
+ const timeSinceConnection = Date.now() - connectionEstablishedTime;
332
365
  logger.debug(
333
- `🔍 心跳检测:socket 状态=${socketState}, elapsed=${Math.round(elapsed / 1000)}s`,
366
+ `🔍 心跳检测:socket 状态=${socketState}, elapsed=${Math.round(elapsed / 1000)}s, 连接已建立=${Math.round(timeSinceConnection / 1000)}s`,
334
367
  );
335
368
 
369
+ // 给新建立的连接 15 秒宽限期,避免在连接建立初期就触发重连
336
370
  if (socketState !== 1) {
371
+ if (timeSinceConnection < 15_000) {
372
+ logger.debug(
373
+ `⏳ 连接建立中(已 ${Math.round(timeSinceConnection / 1000)}s),跳过状态检查`,
374
+ );
375
+ return;
376
+ }
377
+
337
378
  logger.info(
338
379
  `⚠️ 心跳检测:socket 状态=${socketState},触发重连...`,
339
380
  );
@@ -519,8 +560,6 @@ export async function monitorSingleAccount(
519
560
 
520
561
  // ===== 第三步:开始处理消息 =====
521
562
  logger.info(`🚀 开始处理消息...`);
522
- logger.info(`AccountId: ${accountId}`);
523
- logger.info(`HasConfig: ${!!account.config}`);
524
563
 
525
564
  await messageHandler({
526
565
  accountId,
@@ -266,6 +266,11 @@ export async function downloadImageToFile(
266
266
  try {
267
267
  log?.info?.(`开始下载图片: ${downloadUrl.slice(0, 100)}...`);
268
268
  const resp = await dingtalkHttp.get(downloadUrl, {
269
+ proxy: false, // 禁用代理,避免 PAC 文件影响
270
+
271
+ headers: {
272
+ 'Content-Type': undefined, // 删除默认的 Content-Type 请求头,让 OSS 签名验证通过
273
+ },
269
274
  responseType: 'arraybuffer',
270
275
  timeout: 30_000,
271
276
  });
@@ -366,6 +371,10 @@ export async function downloadFileToLocal(
366
371
  try {
367
372
  log?.info?.(`开始下载文件: ${fileName}`);
368
373
  const resp = await dingtalkHttp.get(downloadUrl, {
374
+ proxy: false, // 禁用代理,避免 PAC 文件影响
375
+ headers: {
376
+ 'Content-Type': undefined, // 删除默认的 Content-Type 请求头,让 OSS 签名验证通过
377
+ },
369
378
  responseType: 'arraybuffer',
370
379
  timeout: 60_000, // 文件可能较大,增加超时时间
371
380
  });
@@ -407,7 +416,8 @@ export async function downloadFileToLocal(
407
416
  log?.info?.(`文件下载成功: ${fileName}, size=${buffer.length} bytes, path=${localPath}`);
408
417
  return localPath;
409
418
  } catch (err: any) {
410
- log?.error?.(`文件下载失败: ${fileName}, error=${err.message}`);
419
+ console.error(`[ERROR] downloadFileToLocal 异常: ${err.message}`);
420
+ console.error(`[ERROR] 异常堆栈:\n${err.stack}`);
411
421
  return null;
412
422
  }
413
423
  }
@@ -526,7 +536,7 @@ interface HandleMessageParams {
526
536
  /**
527
537
  * 内部消息处理函数(实际执行消息处理逻辑)
528
538
  */
529
- async function handleDingTalkMessageInternal(params: HandleMessageParams): Promise<void> {
539
+ export async function handleDingTalkMessageInternal(params: HandleMessageParams): Promise<void> {
530
540
  const { accountId, config, data, sessionWebhook, runtime, log, cfg } = params;
531
541
 
532
542
  const content = extractMessageContent(data);
@@ -825,7 +835,7 @@ async function handleDingTalkMessageInternal(params: HandleMessageParams): Promi
825
835
  const parseResult = await parseFileContent(localPath, fileName, log);
826
836
 
827
837
  if (parseResult.type === 'text' && parseResult.content) {
828
- // 文本类文件:将内容注入到上下文
838
+ // 文本类文件:将内容注入到上下文(即使解析成功也给出文件路径)
829
839
  const contentPreview = parseResult.content.length > 200
830
840
  ? parseResult.content.slice(0, 200) + '...'
831
841
  : parseResult.content;
@@ -833,6 +843,7 @@ async function handleDingTalkMessageInternal(params: HandleMessageParams): Promi
833
843
  fileContentParts.push(
834
844
  `📄 **${fileType}**: ${fileName}\n` +
835
845
  `✅ 已解析文件内容(${parseResult.content.length} 字符)\n` +
846
+ `💾 已保存到本地: ${localPath}\n` +
836
847
  `📝 内容预览:\n\`\`\`\n${contentPreview}\n\`\`\`\n\n` +
837
848
  `📋 完整内容:\n${parseResult.content}`
838
849
  );
@@ -1191,13 +1202,13 @@ async function handleDingTalkMessageInternal(params: HandleMessageParams): Promi
1191
1202
  */
1192
1203
  export async function handleDingTalkMessage(params: HandleMessageParams): Promise<void> {
1193
1204
  const { accountId, data, log, cfg } = params;
1194
-
1205
+
1195
1206
  // 构建会话标识(与会话上下文保持一致)
1196
1207
  const isDirect = data.conversationType === '1';
1197
1208
  const senderId = data.senderStaffId || data.senderId;
1198
1209
  const conversationId = data.conversationId;
1199
1210
  const baseSessionId = isDirect ? senderId : conversationId;
1200
-
1211
+
1201
1212
  if (!baseSessionId) {
1202
1213
  log?.warn?.('无法构建会话标识,跳过队列管理');
1203
1214
  return handleDingTalkMessageInternal(params);
@@ -1206,7 +1217,7 @@ export async function handleDingTalkMessage(params: HandleMessageParams): Promis
1206
1217
  // 解析 agentId(与消息处理逻辑保持一致)
1207
1218
  const chatType = isDirect ? "direct" : "group";
1208
1219
  const peerId = isDirect ? senderId : conversationId;
1209
-
1220
+
1210
1221
  let matchedAgentId: string | null = null;
1211
1222
  if (cfg.bindings && cfg.bindings.length > 0) {
1212
1223
  for (const binding of cfg.bindings) {
@@ -1228,38 +1239,46 @@ export async function handleDingTalkMessage(params: HandleMessageParams): Promis
1228
1239
  // 构建队列标识:会话 + agentId
1229
1240
  // 这样不同 agent 可以并发处理,同一 agent 的同一会话串行处理
1230
1241
  const queueKey = `${baseSessionId}:${matchedAgentId}`;
1231
-
1232
- // 更新会话活跃时间
1233
- sessionLastActivity.set(queueKey, Date.now());
1234
-
1235
- // 获取该会话+agent的上一个处理任务
1236
- const previousTask = sessionQueues.get(queueKey) || Promise.resolve();
1237
-
1238
- // 创建当前消息的处理任务
1239
- const currentTask = previousTask
1240
- .then(async () => {
1241
- log?.info?.(`[队列] 开始处理消息,queueKey=${queueKey}`);
1242
- await handleDingTalkMessageInternal(params);
1243
- log?.info?.(`[队列] 消息处理完成,queueKey=${queueKey}`);
1244
- })
1245
- .catch((err: any) => {
1246
- log?.error?.(`[队列] 消息处理异常,queueKey=${queueKey}, error=${err.message}`);
1247
- // 不抛出错误,避免阻塞后续消息
1248
- })
1249
- .finally(() => {
1250
- // 如果当前任务是队列中的最后一个任务,清理队列
1251
- if (sessionQueues.get(queueKey) === currentTask) {
1252
- sessionQueues.delete(queueKey);
1253
- log?.info?.(`[队列] 队列已清空,queueKey=${queueKey}`);
1254
- }
1255
- });
1256
-
1257
- // 更新队列
1258
- sessionQueues.set(queueKey, currentTask);
1259
- log?.info?.(`[队列] 消息已加入队列,queueKey=${queueKey}, 队列大小=${sessionQueues.size}`);
1260
-
1261
- // 不等待任务完成,让消息异步处理
1262
- // 这样可以立即返回,不阻塞 WebSocket 消息接收
1242
+
1243
+ try {
1244
+
1245
+ // 更新会话活跃时间
1246
+ sessionLastActivity.set(queueKey, Date.now());
1247
+
1248
+ // 获取该会话+agent的上一个处理任务
1249
+ const previousTask = sessionQueues.get(queueKey) || Promise.resolve();
1250
+
1251
+ // 创建当前消息的处理任务
1252
+ const currentTask = previousTask
1253
+ .then(async () => {
1254
+ log?.info?.(`[队列] 开始处理消息,queueKey=${queueKey}`);
1255
+ await handleDingTalkMessageInternal(params);
1256
+ log?.info?.(`[队列] 消息处理完成,queueKey=${queueKey}`);
1257
+ })
1258
+ .catch((err: any) => {
1259
+ log?.error?.(`[队列] 消息处理异常,queueKey=${queueKey}, error=${err.message}`);
1260
+ // 不抛出错误,避免阻塞后续消息
1261
+ })
1262
+ .finally(() => {
1263
+ // 如果当前任务是队列中的最后一个任务,清理队列
1264
+ if (sessionQueues.get(queueKey) === currentTask) {
1265
+ sessionQueues.delete(queueKey);
1266
+ log?.info?.(`[队列] 队列已清空,queueKey=${queueKey}`);
1267
+ }
1268
+ });
1269
+
1270
+ // 更新队列
1271
+ sessionQueues.set(queueKey, currentTask);
1272
+
1273
+ // 等待当前任务完成
1274
+ await currentTask;
1275
+ console.log(`[DEBUG] 任务执行完成`);
1276
+ } catch (err: any) {
1277
+ console.error(`[DEBUG] 队列管理异常: ${err.message}`);
1278
+ console.error(`[DEBUG] 队列管理异常堆栈: ${err.stack}`);
1279
+ // 如果队列管理失败,直接调用内部处理函数
1280
+ return handleDingTalkMessageInternal(params);
1281
+ }
1263
1282
  }
1264
1283
 
1265
1284
  // handleDingTalkMessage 已在函数定义处直接导出
@@ -276,6 +276,12 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
276
276
  // 处理媒体标记
277
277
  let finalText = accumulatedText;
278
278
 
279
+ // ✅ 如果累积的文本为空,使用默认提示文案
280
+ if (!finalText.trim()) {
281
+ finalText = '✅ 任务执行完成(无文本输出)';
282
+ log.info(`[DingTalk][closeStreaming] 累积文本为空,使用默认提示文案`);
283
+ }
284
+
279
285
  // 获取 oapiToken 用于媒体处理
280
286
  const oapiToken = await getOapiAccessToken(account.config as DingtalkConfig);
281
287
 
@@ -420,7 +426,14 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
420
426
  const hasText = Boolean(text.trim());
421
427
  const skipTextForDuplicateFinal =
422
428
  info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
423
- const shouldDeliverText = hasText && !skipTextForDuplicateFinal;
429
+
430
+ // ✅ 如果是 final 响应且没有文本,使用默认提示文案
431
+ if (info?.kind === "final" && !hasText) {
432
+ text = '✅ 任务执行完成(无文本输出)';
433
+ log.info(`[DingTalk][deliver] final 响应无文本,使用默认提示文案`);
434
+ }
435
+
436
+ const shouldDeliverText = Boolean(text.trim()) && !skipTextForDuplicateFinal;
424
437
 
425
438
  if (!shouldDeliverText) {
426
439
  log.info(`[DingTalk][deliver] 跳过发送:hasText=${hasText}, skipTextForDuplicateFinal=${skipTextForDuplicateFinal}`);
@@ -7,7 +7,7 @@ import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import type { DingtalkConfig } from '../types/index.ts';
9
9
  import { DINGTALK_OAPI, getOapiAccessToken } from '../utils/index.ts';
10
- import { dingtalkOapiHttp } from '../utils/http-client.ts';
10
+ import { dingtalkHttp, dingtalkOapiHttp } from '../utils/http-client.ts';
11
11
 
12
12
  // ============ 常量 ============
13
13
 
@@ -591,9 +591,10 @@ export async function sendMediaToDingTalk(params: {
591
591
  });
592
592
  }
593
593
 
594
- // 1. 先发送文本消息(如果有)
595
- if (text?.trim()) {
596
- log.info("先发送文本消息");
594
+ // 1. 先发送文本消息(如果有且不为空)
595
+ // 注意:只有在 text 有实际内容时才发送,避免发送空消息
596
+ if (text && text.trim().length > 0) {
597
+ log.info("先发送文本消息:", text);
597
598
  await sendProactive(config, targetParam, text, {
598
599
  msgType: "text",
599
600
  replyToId,
@@ -726,33 +727,28 @@ export async function sendMediaToDingTalk(params: {
726
727
  };
727
728
  }
728
729
 
729
- // 对于音频、文件,发送包含下载链接的文本消息
730
+ // 对于音频、文件,发送真正的文件消息
730
731
  const fs = await import("fs");
731
732
  const stats = fs.statSync(mediaUrl);
732
- const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
733
-
734
- // 使用上传结果中的下载链接
735
- const downloadUrl = uploadResult.downloadUrl;
736
-
737
- // 根据媒体类型选择图标和描述
738
- let icon = "📄";
739
- let typeLabel = "文件";
740
- if (mediaType === "voice") {
741
- icon = "🎵";
742
- typeLabel = "音频";
743
- }
744
-
745
- const message = `${icon} ${typeLabel}文件已上传\n\n文件: ${fileName}\n大小: ${fileSizeMB} MB\n\n下载链接: ${downloadUrl}`;
733
+
734
+ // 获取文件扩展名作为 fileType
735
+ const fileType = ext || "file";
736
+
737
+ // 构建文件信息
738
+ const fileInfo = {
739
+ fileName: fileName,
740
+ fileType: fileType,
741
+ };
746
742
 
747
- const result = await sendProactive(config, targetParam, message, {
748
- msgType: "text",
749
- replyToId,
750
- });
743
+ // 使用 sendFileProactive 发送文件消息
744
+ const { sendFileProactive } = await import("./media.ts");
745
+ await sendFileProactive(config, targetParam, fileInfo, uploadResult.mediaId, log);
751
746
 
752
- // 确保返回值中有 processQueryKey,告诉 SDK 消息已发送成功
747
+ // 返回成功结果
753
748
  return {
754
- ...result,
755
- processQueryKey: result.processQueryKey || "media-message-sent",
749
+ ok: true,
750
+ usedAICard: false,
751
+ processQueryKey: "file-message-sent",
756
752
  };
757
753
  } catch (err: any) {
758
754
  log.error("发送媒体消息失败:", err.message);
@@ -916,21 +912,19 @@ async function sendProactiveInternal(
916
912
  ? `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`
917
913
  : `${DINGTALK_API}/v1.0/robot/groupMessages/send`;
918
914
 
919
- // 构建消息体
915
+ // 使用 buildMsgPayload 构建消息体(支持所有消息类型)
916
+ const payload = buildMsgPayload(msgType, content, options.title);
917
+ if ("error" in payload) {
918
+ log.error("构建消息失败:", payload.error);
919
+ return { ok: false, error: payload.error, usedAICard: false };
920
+ }
921
+
920
922
  const body: any = {
921
923
  robotCode: config.clientId,
922
- msgKey: msgType === "markdown" ? "sampleMarkdown" : "sampleText",
924
+ msgKey: payload.msgKey,
925
+ msgParam: JSON.stringify(payload.msgParam),
923
926
  };
924
927
 
925
- if (msgType === "markdown") {
926
- body.msgParam = JSON.stringify({
927
- title: options.title || "Message",
928
- text: content,
929
- });
930
- } else {
931
- body.msgParam = JSON.stringify({ content });
932
- }
933
-
934
928
  // ✅ 根据目标类型设置不同的参数
935
929
  if (isUser) {
936
930
  body.userIds = [targetId];