@dingtalk-real-ai/dingtalk-connector 0.7.7 → 0.7.8

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
@@ -6,6 +6,22 @@
6
6
  This document records all significant changes. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
7
7
  and version numbers follow [Semantic Versioning](https://semver.org/).
8
8
 
9
+ ## [0.7.8] - 2026-03-13
10
+
11
+ ### 修复 / Fixes
12
+ - 🐛 **AI 卡片模版与渲染优化** - 更新 AI 卡片模版 ID,使卡片样式与最新官方规范保持一致,并提升多终端展示效果
13
+ **AI card template & rendering optimization** - Updated the AI card template ID to match the latest official standard and improved rendering across clients
14
+ - 🐛 **Markdown 表格渲染修复** - 在发送到钉钉前自动为 Markdown 表格头部补充必要空行,避免因缺少空行导致表格被当作普通文本渲染
15
+ **Markdown table rendering fix** - Automatically inserts required blank lines before Markdown table headers to prevent DingTalk from rendering tables as plain text
16
+ - 🐛 **消息去重逻辑优化** - 将消息去重维度从「账号 + 消息 ID」简化为单一「消息 ID」,避免多账号场景下的重复处理或误判
17
+ **Message de-duplication optimization** - Simplified de-duplication from `(accountId, messageId)` to `messageId` only, preventing duplicate handling or misjudgment in multi-account scenarios
18
+
19
+ ### 改进 / Improvements
20
+ - ✅ **统一 Markdown 修正管道** - 对 AI 卡片流式内容、最终内容、普通 Markdown 消息及 `sampleMarkdown` 卡片文本统一应用 Markdown 修正规则,确保表格等格式在各入口行为一致
21
+ **Unified Markdown normalization pipeline** - Applies the same Markdown normalization to streaming AI card content, final content, regular Markdown messages, and `sampleMarkdown` card text for consistent behavior
22
+ - ✅ **AI 卡片状态内容一致性** - 在完成 AI 卡片时,对展示内容和写入 `cardParamMap.msgContent` 的内容使用同一份 Markdown 修正结果,确保用户看到的内容与内部状态一致
23
+ **Consistent AI card status content** - Ensures the same normalized Markdown is used both for the visible content and `cardParamMap.msgContent` when finishing AI cards
24
+
9
25
  ## [0.7.7] - 2026-03-13
10
26
 
11
27
  ### 新增 / Added
@@ -0,0 +1,101 @@
1
+ # Release Notes - v0.7.8
2
+
3
+ ## ✨ 功能与体验改进 / Features & Improvements
4
+
5
+ - **AI 卡片模版更新与展示效果优化 / AI Card Template & Rendering Improvements**
6
+ 升级钉钉 AI 卡片模版 ID,使卡片样式与最新官方规范保持一致,并优化多终端的展示效果与兼容性。
7
+ Updated the DingTalk AI card template ID to align with the latest official template standard, improving visual consistency and compatibility across different clients.
8
+
9
+ - **Markdown 表格渲染修复与自动优化 / Markdown Table Rendering Fix & Auto-Adjustment**
10
+ 新增 Markdown 预处理逻辑,在发送到钉钉前自动为表格头部补充必要的空行,避免因缺少空行导致的表格无法正确渲染问题;支持缩进表格场景。
11
+ Added a Markdown preprocessing step that automatically inserts a blank line before table headers when needed, ensuring DingTalk renders tables correctly, including indented table cases.
12
+
13
+ - **统一的 Markdown 修正管道 / Unified Markdown Normalization Pipeline**
14
+ 对 AI 卡片流式更新、AI 卡片最终内容提交、普通 Markdown 消息发送以及卡片 `sampleMarkdown` 内容,统一通过同一套 Markdown 修正函数进行处理,确保所有下发到钉钉的文本在表格渲染等细节上行为一致。
15
+ Unified the Markdown normalization logic used for streaming AI card updates, final AI card content, regular Markdown messages, and `sampleMarkdown` card payloads, ensuring consistent behavior of table rendering and formatting in DingTalk.
16
+
17
+ - **AI 卡片状态信息更准确 / More Accurate AI Card Status Content**
18
+ 在完成 AI 卡片时,对用于展示的内容与写入 `cardParamMap` 中的 `msgContent` 同步应用 Markdown 修正逻辑,保证用户看到的内容与卡片内部状态字段保持完全一致。
19
+ When finalizing AI cards, the same Markdown fixes are now applied both to the displayed content and the `msgContent` stored in `cardParamMap`, keeping the visible card and its internal state in sync.
20
+
21
+ ## 🐛 修复 / Fixes
22
+
23
+ - **Markdown 表格无法正确显示的问题 / Incorrect Markdown Table Rendering**
24
+ 修复了部分场景下 Markdown 表格前缺少空行,导致钉钉不将其识别为表格而当作普通文本渲染的问题;现在会自动检测表头与分隔行模式并在需要时插入空行。
25
+ Fixed an issue where missing blank lines before Markdown tables caused DingTalk to render them as plain text; the connector now detects table headers and divider lines and inserts a blank line when necessary.
26
+
27
+ - **消息去重维度优化 / Message De-duplication Scope Optimization**
28
+ 优化消息去重逻辑,从「按账号+消息 ID」改为仅基于「消息 ID」维度标记与判断,避免在多账号场景中出现某些重复消息未被正确拦截或误判的情况。
29
+ Improved the message de-duplication mechanism by switching from an account-scoped `(accountId, messageId)` key to a global `messageId` key, preventing edge cases where duplicate messages across accounts might not be handled correctly.
30
+
31
+ ## 📋 技术细节 / Technical Details
32
+
33
+ ### AI 卡片模版 & 内容处理 / AI Card Template & Content Handling
34
+
35
+ - 更新 `AI_CARD_TEMPLATE_ID` 为新的模版 ID,以匹配最新的钉钉 AI 卡片样式规范。
36
+ - 新增 `ensureTableBlankLines(text: string)` 工具函数:
37
+ - 将文本按行拆分,识别包含竖线的表格行与 `---` 分隔行。
38
+ - 当前行看起来像表头、下一行是分隔行、且前一行既不是空行也不是表格行时,会在表头前插入一个空行。
39
+ - 支持带缩进的表格写法,保持原有内容顺序与缩进风格不变。
40
+ - 在以下路径中统一使用 `ensureTableBlankLines`:
41
+ - AI 卡片流式内容更新(`streamAICard`)中的 `content` 字段。
42
+ - AI 卡片结束时(`finishAICard`)的最终内容与日志长度统计。
43
+ - 普通 Markdown 消息发送(`sendMarkdownMessage`),在追加 `@user` 之前先做表格修正。
44
+ - `buildMsgPayload` 中 `sampleMarkdown` 类型的 `text` 字段。
45
+ - 为单元测试导出 `__testables.ensureTableBlankLines`,便于在不依赖具体业务逻辑的情况下验证 Markdown 修正规则。
46
+
47
+ ### 消息去重逻辑 / Message De-duplication Logic
48
+
49
+ - 去重检查由 `isMessageProcessed(accountId, messageId)` 简化为 `isMessageProcessed(messageId)`,对同一消息 ID 统一判重。
50
+ - 标记逻辑由 `markMessageProcessed(accountId, messageId)` 更新为 `markMessageProcessed(messageId)`,减少多账号场景下可能出现的重复处理路径。
51
+ - 保持原有日志信息与跳过处理分支不变,仅调整内部去重键值结构。
52
+
53
+ ## 📥 安装升级 / Installation & Upgrade
54
+
55
+ ```bash
56
+ # 通过 npm 安装最新版本 / Install latest version via npm
57
+ openclaw plugins install @dingtalk-real-ai/dingtalk-connector
58
+
59
+ # 或升级现有版本 / Or upgrade existing version
60
+ openclaw plugins update dingtalk-connector
61
+
62
+ # 通过 Git 安装 / Install via Git
63
+ openclaw plugins install https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector.git
64
+ ```
65
+
66
+ ## ⚠️ 升级注意事项 / Upgrade Notes
67
+
68
+ ### 兼容性说明 / Compatibility Notes
69
+
70
+ - **向下兼容 / Backward Compatible**:本次为小版本修复和体验优化更新,在保留 v0.7.x 既有行为的前提下增强了 Markdown 表格渲染与消息去重逻辑,对现有配置完全兼容。
71
+ - **Markdown 表格渲染更稳定 / More Robust Markdown Tables**:即便原始内容中未严格遵守表格前空行的写法,Connector 也会自动做最小化修正,以提高在钉钉中的可读性。
72
+ - **消息去重语义更清晰 / Clearer De-duplication Semantics**:以 `messageId` 为唯一维度进行去重,更贴合钉钉消息唯一标识的语义。
73
+
74
+ ### 验证步骤 / Verification Steps
75
+
76
+ 升级到此版本后,建议进行以下验证:
77
+
78
+ 1. **AI 卡片模版与渲染验证 / AI Card Template & Rendering Verification**
79
+ - 触发一次典型的 AI 卡片对话,观察新模版下的卡片布局与字段展示是否符合预期。
80
+ - 在含有多段文字与表格的回复中,确认卡片内 Markdown 表格渲染正常。
81
+
82
+ 2. **Markdown 表格兼容性验证 / Markdown Table Compatibility Verification**
83
+ - 通过机器人发送包含 Markdown 表格的消息(包含表头、分隔行与多列数据),且故意在表格前省略空行。
84
+ - 在移动端及 PC 端查看,确认钉钉能够正确以表格形式渲染内容。
85
+
86
+ 3. **消息去重行为验证 / Message De-duplication Behavior Verification**
87
+ - 在相同会话中模拟重复推送同一个 `messageId` 的回调(或快速重复发送同一条消息)。
88
+ - 确认日志中出现去重命中提示,并且业务处理逻辑只执行一次。
89
+
90
+ ## 🔗 相关链接 / Related Links
91
+
92
+ - [完整变更日志 / Full Changelog](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/CHANGELOG.md)
93
+ - [使用文档 / Documentation](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/README.md)
94
+ - [问题反馈 / Issue Feedback](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues)
95
+
96
+ ---
97
+
98
+ **发布日期 / Release Date**:2026-03-13
99
+ **版本号 / Version**:v0.7.8
100
+ **兼容性 / Compatibility**:OpenClaw Gateway 0.4.0+
101
+
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dingtalk-connector",
3
3
  "name": "DingTalk Channel",
4
- "version": "0.7.7",
4
+ "version": "0.7.8",
5
5
  "description": "DingTalk (钉钉) messaging channel via Stream mode with AI Card streaming",
6
6
  "author": "DingTalk Real Team",
7
7
  "channels": ["dingtalk-connector"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dingtalk-real-ai/dingtalk-connector",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "description": "DingTalk (钉钉) channel connector — Stream mode with AI Card streaming",
5
5
  "main": "plugin.ts",
6
6
  "type": "module",
package/plugin.ts CHANGED
@@ -1103,7 +1103,7 @@ async function processFileMarkers(
1103
1103
 
1104
1104
  const DINGTALK_API = 'https://api.dingtalk.com';
1105
1105
  const DINGTALK_OAPI = 'https://oapi.dingtalk.com';
1106
- const AI_CARD_TEMPLATE_ID = '382e4302-551d-4880-bf29-a30acfab2e71.schema';
1106
+ const AI_CARD_TEMPLATE_ID = '02fcf2f4-5e02-4a85-b672-46d1f715543e.schema';
1107
1107
 
1108
1108
  // flowStatus 值与 Python SDK AICardStatus 一致(cardParamMap 的值必须是字符串)
1109
1109
  const AICardStatus = {
@@ -1141,6 +1141,45 @@ async function createAICard(
1141
1141
  return createAICardForTarget(config, target, log);
1142
1142
  }
1143
1143
 
1144
+ /**
1145
+ * 确保 Markdown 表格前有空行,否则钉钉无法正确渲染表格。
1146
+ *
1147
+ * 逐行向前看:当前行像表头(含 `|`)且下一行是分隔行时,
1148
+ * 若前一行非空且非表格行,则在表头前插入空行。
1149
+ * 支持缩进表格(行首有空白字符)。
1150
+ */
1151
+ function ensureTableBlankLines(text: string): string {
1152
+ const lines = text.split('\n');
1153
+ const result: string[] = [];
1154
+
1155
+ // 匹配表格分隔行 (例如 | --- | --- | 或 --- | ---)
1156
+ const tableDividerRegex = /^\s*\|?\s*:?-+:?\s*(\|?\s*:?-+:?\s*)+\|?\s*$/;
1157
+ // 匹配包含竖线的表格行
1158
+ const tableRowRegex = /^\s*\|?.*\|.*\|?\s*$/;
1159
+
1160
+ const isDivider = (line: string) => line.includes('|') && tableDividerRegex.test(line);
1161
+
1162
+ for (let i = 0; i < lines.length; i++) {
1163
+ const currentLine = lines[i];
1164
+ const nextLine = lines[i + 1] ?? '';
1165
+
1166
+ // 逻辑:
1167
+ // 1. 当前行看起来像表头(包含 |)
1168
+ // 2. 下一行是分隔行(---)
1169
+ // 3. 前一行不是空行且不是表格行
1170
+ if (
1171
+ tableRowRegex.test(currentLine) &&
1172
+ isDivider(nextLine) &&
1173
+ i > 0 && lines[i - 1].trim() !== '' && !tableRowRegex.test(lines[i - 1])
1174
+ ) {
1175
+ result.push('');
1176
+ }
1177
+
1178
+ result.push(currentLine);
1179
+ }
1180
+ return result.join('\n');
1181
+ }
1182
+
1144
1183
  // 流式更新 AI Card 内容
1145
1184
  async function streamAICard(
1146
1185
  card: AICardInstance,
@@ -1177,11 +1216,12 @@ async function streamAICard(
1177
1216
  }
1178
1217
 
1179
1218
  // 调用 streaming API 更新内容
1219
+ const fixedContent = ensureTableBlankLines(content);
1180
1220
  const body = {
1181
1221
  outTrackId: card.cardInstanceId,
1182
1222
  guid: `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
1183
1223
  key: 'msgContent',
1184
- content: content,
1224
+ content: fixedContent,
1185
1225
  isFull: true, // 全量替换
1186
1226
  isFinalize: finished,
1187
1227
  isError: false,
@@ -1205,10 +1245,11 @@ async function finishAICard(
1205
1245
  content: string,
1206
1246
  log?: any,
1207
1247
  ): Promise<void> {
1208
- log?.info?.(`[DingTalk][AICard] 开始 finish,最终内容长度=${content.length}`);
1248
+ const fixedContent = ensureTableBlankLines(content);
1249
+ log?.info?.(`[DingTalk][AICard] 开始 finish,最终内容长度=${fixedContent.length}`);
1209
1250
 
1210
1251
  // 1. 先用最终内容关闭流式通道(isFinalize=true),确保卡片显示替换后的内容
1211
- await streamAICard(card, content, true, log);
1252
+ await streamAICard(card, fixedContent, true, log);
1212
1253
 
1213
1254
  // 2. 更新卡片状态为 FINISHED
1214
1255
  const body = {
@@ -1216,7 +1257,7 @@ async function finishAICard(
1216
1257
  cardData: {
1217
1258
  cardParamMap: {
1218
1259
  flowStatus: AICardStatus.FINISHED,
1219
- msgContent: content,
1260
+ msgContent: fixedContent,
1220
1261
  staticMsgContent: '',
1221
1262
  sys_full_json_obj: JSON.stringify({
1222
1263
  order: ['msgContent'], // 只声明实际使用的字段,避免部分客户端显示空占位
@@ -1698,7 +1739,7 @@ async function sendMarkdownMessage(
1698
1739
  options: any = {},
1699
1740
  ): Promise<any> {
1700
1741
  const token = await getAccessToken(config);
1701
- let text = markdown;
1742
+ let text = ensureTableBlankLines(markdown);
1702
1743
  if (options.atUserId) text = `${text} @${options.atUserId}`;
1703
1744
 
1704
1745
  const body: any = {
@@ -2235,7 +2276,7 @@ function buildMsgPayload(
2235
2276
  msgKey: 'sampleMarkdown',
2236
2277
  msgParam: {
2237
2278
  title: title || content.split('\n')[0].replace(/^[#*\s\->]+/, '').slice(0, 20) || 'Message',
2238
- text: content,
2279
+ text: ensureTableBlankLines(content),
2239
2280
  },
2240
2281
  };
2241
2282
  case 'link':
@@ -3532,15 +3573,15 @@ const dingtalkPlugin = {
3532
3573
  ctx.log?.info?.(`[DingTalk] 已立即确认回调: messageId=${messageId}`);
3533
3574
  }
3534
3575
 
3535
- // 【消息去重】检查是否已处理过该消息(按账号维度隔离)
3536
- if (messageId && isMessageProcessed(account.accountId, messageId)) {
3576
+ // 【消息去重】检查是否已处理过该消息
3577
+ if (messageId && isMessageProcessed(messageId)) {
3537
3578
  ctx.log?.warn?.(`[DingTalk][${account.accountId}] 检测到重复消息,跳过处理: messageId=${messageId}`);
3538
3579
  return;
3539
3580
  }
3540
3581
 
3541
- // 标记消息为已处理(按账号维度隔离)
3582
+ // 标记消息为已处理
3542
3583
  if (messageId) {
3543
- markMessageProcessed(account.accountId, messageId);
3584
+ markMessageProcessed(messageId);
3544
3585
  }
3545
3586
 
3546
3587
  // 异步处理消息(不阻塞回调确认)
@@ -3939,6 +3980,8 @@ export {
3939
3980
  // ============ 测试辅助导出 ============
3940
3981
  // 仅用于单元测试,避免在业务代码中直接依赖内部实现细节
3941
3982
  export const __testables = {
3983
+ // Markdown 修正
3984
+ ensureTableBlankLines,
3942
3985
  // 会话 & 去重
3943
3986
  normalizeSlashCommand,
3944
3987
  buildSessionContext,