@jeik/dingtalk-connector 0.8.21-fix47 → 0.8.21-fix49

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,31 @@ 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.21-fix49] - 2026-07-21
9
+
10
+ ### 变更 / Changed
11
+ - 🎨 **默认流式 AI Card 模板** — `cardTemplateId` 默认 `0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema`;`cardContentVar` 默认 `content`(与增强模板字段一致)。
12
+ - 📚 文档:图片发送全面修复叙述;钉钉专属配置最小/最大化(单·多 Agent);`messageAnswerCard` 等。
13
+
14
+ ### 安装 / Install
15
+ ```bash
16
+ npx -y @jeik/dingtalk-connector install --force
17
+ openclaw gateway restart
18
+ ```
19
+
20
+ ## [0.8.21-fix48] - 2026-07-21
21
+
22
+ ### 新增 / Added
23
+ - ✨ **`messageAnswerCard`(默认 `true`)** — message 工具正文默认走**答案静态卡**(`answerCardTemplateId` 或内置模板,`skipInputingWalk`);失败降级普通消息。显式 `false` 关闭。
24
+ 已注册:`schema.ts`、`openclaw.plugin.json`(channels + accounts + uiHints)。
25
+ 与会话流式 `answerCard` 独立;图/音/视/文件仍走普通通道。
26
+
27
+ ### 安装 / Install
28
+ ```bash
29
+ npx -y @jeik/dingtalk-connector install --force
30
+ openclaw gateway restart
31
+ ```
32
+
8
33
  ## [0.8.21-fix47] - 2026-07-20
9
34
 
10
35
  ### 修复 / Fixed
package/README.en.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <p>Community maintained fork of the official <strong>v0.8.20</strong> release, tracking and fixing bugs the official team hasn't addressed.<br/>
5
5
  Identical to the official release in functionality — only community-critical fixes applied.</p>
6
6
 
7
- <p><strong>Current published release: <a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-fix47</strong> (production-stable; install: <code>npx -y @jeik/dingtalk-connector install</code>).</p>
7
+ <p><strong>Current published release: <a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-fix49</strong> (production-stable; install: <code>npx -y @jeik/dingtalk-connector install</code>).</p>
8
8
 
9
9
  <p>
10
10
  <a href="https://www.npmjs.com/package/@jeik/dingtalk-connector"><img src="https://img.shields.io/npm/v/@jeik/dingtalk-connector.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="npm version" /></a>
@@ -23,18 +23,24 @@
23
23
 
24
24
  ## 🔧 Recent Updates
25
25
 
26
- ### 🚀 v0.8.21-fix47 · 2026-07-20 (current)
26
+ ### 🚀 v0.8.21-fix49 · 2026-07-21 (current)
27
27
 
28
- **Theme: fix `file://` misclassified as remote URL**
28
+ **Theme: message tool answer card by default (toggle)**
29
29
 
30
30
  | | Change |
31
31
  |--|--------|
32
- | 🐛 **`file://` grey images** | Treat `file://` / `MEDIA:` / `attachment://` as local upload, not `fetch` |
32
+ | ✨ **`messageAnswerCard`** | **Default `true`**: message body uses static answer card; set `false` for plain text/markdown |
33
+ | 🔧 **Registered** | `schema` + `openclaw.plugin.json` (channels / accounts / uiHints) |
34
+ | 📎 **Independent** | Separate from session-stream `answerCard`; media still uses normal APIs |
33
35
 
34
36
  ```bash
35
37
  npx -y @jeik/dingtalk-connector install --force && openclaw gateway restart
36
38
  ```
37
39
 
40
+ ### 📦 v0.8.21-fix47 · 2026-07-20
41
+
42
+ `file://` no longer misclassified as remote (grey-image fix).
43
+
38
44
  ### 📦 v0.8.21-fix46 · 2026-07-20
39
45
 
40
46
  Always-on local MD image diagnostics.
@@ -49,14 +55,7 @@ Remote `media` download-then-upload.
49
55
 
50
56
  ### 📦 v0.8.21-fix37 · 2026-07-20
51
57
 
52
- **Theme: local images (incl. `/mnt`) · message media layout · diagnostics**
53
-
54
- | | Change |
55
- |--|--------|
56
- | 📷 **Local MD images** | Any absolute path (incl. **`/mnt`**, Chinese paths); upload retries via `/tmp` copy |
57
- | 📦 **Code-block safe** | Paths inside fenced/inline code are **not** uploaded (param samples stay literal) |
58
- | ⚙️ **`messageImageMd`** | Default `false`: message tool sends **text then image** separately; `true` merges only multi-image + text into one markdown |
59
- | 🔍 **Diagnostics** | Prefix `[DingTalk][LocalImage]` — **no `debug` flag required** |
58
+ Local images (incl. `/mnt`) · `messageImageMd` · LocalImage diagnostics.
60
59
 
61
60
  ---
62
61
 
@@ -69,22 +68,69 @@ Remote `media` download-then-upload.
69
68
  | 2026-06-28 | npm `@jeik/dingtalk-connector`; premature finalization fix |
70
69
  | 2026-05 | MD images; multi-turn spam; 4.29 empty reply; WS phantom reconnect |
71
70
 
72
- Full log: [CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix38](docs/RELEASE_NOTES_V0.8.21-fix38.md)
71
+ Full log: [CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix48](docs/RELEASE_NOTES_V0.8.21-fix49.md)
73
72
 
74
73
  ---
75
74
 
76
75
  ## ✨ Enhanced Features
77
76
 
78
- - 🔧 Markdown image support for direct URLs and local paths, no download required:
79
- - Markdown syntax `![](direct-url)` or `![](local-path)` sends images directly
80
- - Compatible with mediaId format
81
- - ⚠️ This plugin supports image messages, but DingTalk side won't trigger this feature automatically. Use the following prompt to guide the Agent:
77
+ ### 🖼️ Image sending: full fix & upgrade
78
+
79
+ > 🔥 **No dead corners** · mixed send paths · far beyond the official’s weak image pipeline
80
+ > ✅ Chat · ✅ message direct · ✅ markdown embed · ✅ split or merge layout
81
+
82
+ Full-path image fixes — every channel below renders correctly:
83
+
84
+ | | Path | Capability |
85
+ |--|------|------------|
86
+ | 💬 | **Normal chat reply** | Agent embeds `![alt](…)`; upload to mediaId before finalize |
87
+ | 📤 | **message · mediaUrl** | Proactive direct send (local / LAN / public) |
88
+ | 📝 | **message · markdown body** | Nested `![](…)` (local / `file://` / URL) with text |
89
+ | ⚙️ | **Configurable layout** | Default 📎 **separate**; `messageImageMd: true` → 🧩 **merge** one markdown |
90
+
91
+ #### 🌐 Source types covered
82
92
 
83
- ```
84
- Please write a DingTalk image sending skill following this approach: use markdown to send images, with image captions for rich text; direct URLs or local paths can be embedded directly in markdown, and if local paths contain spaces, rename to remove spaces first before sending.
85
- ```
93
+ | | Type | Examples |
94
+ |--|------|----------|
95
+ | 🌍 | Public URL | `https://…` |
96
+ | 🏠 | Private / LAN URL | `http://intranet/…` |
97
+ | 📁 | Local absolute path | `/tmp/…` · `/root/…` |
98
+ | 💾 | **`/mnt` mounts** | Chinese paths · SMB shares |
99
+ | 🔗 | `file://` URI | `file:///mnt/…` · `file:///tmp/…` |
100
+ | 🆔 | Existing mediaId | `@lADP…` |
86
101
 
87
- - 🎨 Custom AI Card template support for user-prebuilt cards (with copy button), uses official default card if not set:
102
+ #### 🛡️ Extra safeguards
103
+
104
+ - 📦 Code-block / inline-code paths are **not** uploaded
105
+ - ⬇️ Remote: **download then upload**; local: **`/tmp` retry** on failure
106
+ - 🫧 Image + download link in **one bubble**: `![]` → mediaId, URL kept as-is
107
+
108
+ ### 🎨 AI Card template
109
+
110
+ - ✨ Custom stream cards; **if `cardTemplateId` is omitted**, defaults to
111
+ `0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema` (📋 copy button, etc.)
112
+
113
+ ### ⚙️ Minimal config
114
+
115
+ ```json
116
+ "channels": {
117
+ "dingtalk-connector": {
118
+ "enabled": true,
119
+ "clientId": "your-clientId",
120
+ "clientSecret": "your-clientSecret"
121
+ }
122
+ }
123
+ ```
124
+
125
+ Defaults apply: stream card `0d2c84b3-…schema`, session answer card on, message answer card on, `messageImageMd=false`.
126
+
127
+ > **Maximal config ≡ minimal config.** The blocks below only spell out defaults so the fields are easier to understand. Behavior is the same as the credential-only minimal config — use minimal in production.
128
+
129
+ ### Maximal config (for illustration; defaults written out)
130
+
131
+ **Single Agent** and **multi Agent** (all values are defaults — **not extra features**).
132
+
133
+ **Single Agent (top-level credentials):**
88
134
 
89
135
  ```json
90
136
  "channels": {
@@ -92,21 +138,74 @@ Full log: [CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix38
92
138
  "enabled": true,
93
139
  "clientId": "your-clientId",
94
140
  "clientSecret": "your-clientSecret",
95
- "cardTemplateId": "your-card-template-id.schema",
96
- "cardContentVar": "content"
141
+ "cardTemplateId": "0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema",
142
+ "cardContentVar": "content",
143
+ "answerCard": true,
144
+ "answerActToken": 500,
145
+ "answerCardTemplateId": "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema",
146
+ "messageAnswerCard": true,
147
+ "messageImageMd": false
97
148
  }
98
149
  }
99
150
  ```
100
151
 
101
- | Parameter | Description |
102
- |-----------|-------------|
103
- | `cardTemplateId` | AI Card template ID, uses official default if not set |
104
- | `cardContentVar` | Card content variable (process / tool line / final all write here), defaults to `msgContent` |
105
- | `answerCard` | Answer-card mode switch, **on by default**; set `false` to disable |
106
- | `answerActToken` | Answer-card trigger threshold (tokens), default `500`; final answer ≤ this stays on the original card, > this opens a separate answer card |
107
- | `answerCardTemplateId` | Answer-card template ID, uses the built-in default if not set (must contain a `content` variable) |
152
+ **Multi Agent (`accounts`, one credential/card set per bot):**
153
+
154
+ ```json
155
+ "channels": {
156
+ "dingtalk-connector": {
157
+ "enabled": true,
158
+ "defaultAccount": "main-bot",
159
+ "accounts": {
160
+ "main-bot": {
161
+ "enabled": true,
162
+ "name": "main-bot",
163
+ "clientId": "main-bot-clientId",
164
+ "clientSecret": "main-bot-clientSecret",
165
+ "cardTemplateId": "0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema",
166
+ "cardContentVar": "content",
167
+ "answerCard": true,
168
+ "answerActToken": 500,
169
+ "answerCardTemplateId": "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema",
170
+ "messageAnswerCard": true,
171
+ "messageImageMd": false
172
+ },
173
+ "guide-bot": {
174
+ "enabled": true,
175
+ "name": "guide-bot",
176
+ "clientId": "guide-bot-clientId",
177
+ "clientSecret": "guide-bot-clientSecret",
178
+ "cardTemplateId": "0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema",
179
+ "cardContentVar": "content",
180
+ "answerCard": true,
181
+ "answerActToken": 500,
182
+ "answerCardTemplateId": "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema",
183
+ "messageAnswerCard": true,
184
+ "messageImageMd": false
185
+ }
186
+ }
187
+ }
188
+ }
189
+ ```
108
190
 
109
- > Card template must be created in [DingTalk Open Platform](https://open.dingtalk.com/) with matching variable fields.
191
+ ### DingTalk-specific fields
192
+
193
+ | Field | Type | Default | Description |
194
+ |-------|------|---------|-------------|
195
+ | `clientId` | string \| number | — | AppKey / Client ID |
196
+ | `clientSecret` | string \| SecretRef | — | AppSecret |
197
+ | `accounts` | object | — | Multi-bot map |
198
+ | `accounts.*.name` | string | — | Display name |
199
+ | `accounts.*.clientId` / `clientSecret` | — | — | Per-bot credentials |
200
+ | `cardTemplateId` | string | `0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema` | Streaming AI Card template |
201
+ | `cardContentVar` | string | `"content"` | Stream card content variable |
202
+ | `answerCard` | boolean | **true** | Session-stream answer card; `false` disables |
203
+ | `answerActToken` | int | **500** | Token threshold for spawning answer card |
204
+ | `answerCardTemplateId` | string | `d246b7f5-1783-4e9b-bb46-bef52d63050e.schema` | Static answer card template |
205
+ | `messageAnswerCard` | boolean | **true** | message-tool body via answer card; `false` = plain message |
206
+ | `messageImageMd` | boolean | **false** | message images: separate vs merge markdown |
207
+
208
+ > `answerCard` = conversation finalize; `messageAnswerCard` = message-tool outbound.
110
209
 
111
210
  ---
112
211
 
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <p>基于官方 <strong>v0.8.20</strong> 的社区维护版本,由社区持续跟进修复官方无暇处理的 Bug。<br/>
5
5
  功能与官方完全一致,拥有最快的修复速度,及时合并官方pr和个人发现的bug和社区急需的 Bug。</p>
6
6
 
7
- <p><strong>当前发布版:<a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-fix47</strong>(稳定生产可用;一键安装:`npx -y @jeik/dingtalk-connector install`;本地 tgz:`openclaw plugins install ./jeik-dingtalk-connector-0.8.21-fix47.tgz --force`)</p>
7
+ <p><strong>当前发布版:<a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-fix49</strong>(稳定生产可用;一键安装:`npx -y @jeik/dingtalk-connector install`;本地 tgz:`openclaw plugins install ./jeik-dingtalk-connector-0.8.21-fix49.tgz --force`)</p>
8
8
 
9
9
  <p>
10
10
  <a href="https://www.npmjs.com/package/@jeik/dingtalk-connector"><img src="https://img.shields.io/npm/v/@jeik/dingtalk-connector.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="npm version" /></a>
@@ -23,18 +23,24 @@
23
23
 
24
24
  ## 🔧 最近更新
25
25
 
26
- ### 🚀 v0.8.21-fix47 · 2026-07-20(当前)
26
+ ### 🚀 v0.8.21-fix49 · 2026-07-21(当前)
27
27
 
28
- **主题:`file://` 本地图误判远程修复**
28
+ **主题:message 工具默认答案卡(可关)**
29
29
 
30
30
  | | 改动 |
31
31
  |--|------|
32
- | 🐛 **`file://` 灰图** | `![x](file:///mnt/...)` / `file:///root/...` 按本地上传,不再 `fetch failed` |
32
+ | ✨ **`messageAnswerCard`** | **默认 `true`**:message 正文走答案静态卡;`false` 恢复普通 text/markdown |
33
+ | 🔧 **注册** | `schema` + `openclaw.plugin.json`(channels / accounts / uiHints) |
34
+ | 📎 **独立** | 与会话流式 `answerCard` 分开;媒体消息仍普通通道 |
33
35
 
34
36
  ```bash
35
37
  npx -y @jeik/dingtalk-connector install --force && openclaw gateway restart
36
38
  ```
37
39
 
40
+ ### 📦 v0.8.21-fix47 · 2026-07-20
41
+
42
+ `file://` 本地图误判远程修复(不再 `fetch failed` 灰图)。
43
+
38
44
  ### 📦 v0.8.21-fix46 · 2026-07-20
39
45
 
40
46
  本地 MD 图灰图诊断日志(LocalImage / MediaIdTrace)。
@@ -49,14 +55,7 @@ message 远程 `media` 下载上传。
49
55
 
50
56
  ### 📦 v0.8.21-fix37 · 2026-07-20
51
57
 
52
- **主题:本地图(含 `/mnt`)· message 文图策略 · 可观测日志**
53
-
54
- | | 改动 |
55
- |--|------|
56
- | 📷 **MD 本地图** | 支持任意绝对路径(含 **`/mnt` 共享盘**、中文目录);上传失败会拷到 `/tmp` 再试 |
57
- | 📦 **代码块保护** | 围栏 / 行内 code 里的路径**不会上传**,参数示例不会被改成 `mediaId` |
58
- | ⚙️ **`messageImageMd`** | 默认 `false`:message 工具**文图分开**;`true` 时仅「多图 + 文字」合并为一条 markdown |
59
- | 🔍 **诊断日志** | 前缀 `[DingTalk][LocalImage]`,**无需开 debug** 也能看到匹配 / 上传 / 失败原因 |
58
+ 本地图(含 `/mnt`)· `messageImageMd` · LocalImage 诊断日志。
60
59
 
61
60
  ---
62
61
 
@@ -69,26 +68,69 @@ message 远程 `media` 下载上传。
69
68
  | 2026-06-28 | 上线 npm `@jeik/dingtalk-connector`;过程消息提前定稿修复 |
70
69
  | 2026-05 | MD 直链/本地图;多轮刷屏;4.29+ 无文本输出;WebSocket 幻影重连 |
71
70
 
72
- 完整说明:[CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix47](docs/RELEASE_NOTES_V0.8.21-fix47.md)
71
+ 完整说明:[CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix48](docs/RELEASE_NOTES_V0.8.21-fix49.md)
73
72
 
74
73
  ---
75
74
 
76
75
  ## ✨ 增强功能
77
76
 
78
- - 🔧 Markdown 图片发送支持直链和本地路径(含 **`/mnt` 共享盘**、中文路径):
79
- - 语法 `![注释](直链URL)` 或 `![注释](/绝对/本地路径)` → 自动上传 mediaId 后发出
80
- - **代码块 / 行内 code 内的路径不会上传**(参数说明原文保留)
81
- - 兼容已有 mediaId 格式
82
- - message 工具默认**文图分开**;可选 `messageImageMd: true` 在多图+文字时合并 markdown
83
- - ⚠️ 需引导 Agent 使用 markdown 发图时可用:
77
+ ### 🖼️ 图片发送:全面修复与增强
78
+
79
+ > 🔥 **毫无死角** · 混合发图 · 全面碾压官方薄弱的图片机制
80
+ > ✅ 对话发图 · ✅ message 直发 · ✅ markdown 嵌图 · ✅ 文图可分可合
81
+
82
+ 对官方图片发送做了**全路径修复增强**,多通道混合发图,全部显示正常:
84
83
 
85
- ```
86
- 请你把以下发送图片的方式写成你的钉钉图片发送skill,当涉及到图片发送,则调用该技能:用markdown语法发送图片,支持添加图片注释实现图文并茂;直链图片或本地路径文件均可直接嵌入markdown发送,如本地路径含空格请先重命名去除空格再发送。展示工具参数时请放在代码块中,避免被当作待发送图片。
87
- ```
84
+ | | 发送方式 | 能力 |
85
+ |--|----------|------|
86
+ | 💬 | **普通对话回复** | Agent 用 `![注释](…)` 嵌图,定稿前自动上传 mediaId |
87
+ | 📤 | **message · mediaUrl** | 机器人主动触发直发(本地 / 内网 / 公网) |
88
+ | 📝 | **message · markdown 正文** | 正文嵌 `![](…)`(本地 / `file://` / 直链)与文字同条 |
89
+ | ⚙️ | **文图策略可配** | 默认 📎 **分开**;`messageImageMd: true` → 🧩 **合并**一条 markdown |
88
90
 
89
- - 🎨 支持自定义 AI Card 模板,可使用本人预制的卡片(含内容复制按钮),不填则使用官方默认卡片。
91
+ #### 🌐 路径与协议全覆盖
90
92
 
91
- **单机器人:**
93
+ | | 类型 | 示例 |
94
+ |--|------|------|
95
+ | 🌍 | 公网直链 | `https://…` |
96
+ | 🏠 | 内网直链 | `http://内网主机/…` |
97
+ | 📁 | 本地绝对路径 | `/tmp/…` · `/root/…` |
98
+ | 💾 | **`/mnt` 挂载盘** | 中文目录 · 共享盘 · SMB |
99
+ | 🔗 | `file://` URI | `file:///mnt/…` · `file:///tmp/…` |
100
+ | 🆔 | 已有 mediaId | `@lADP…` |
101
+
102
+ #### 🛡️ 细节兜底
103
+
104
+ - 📦 代码块 / 行内 code 路径 **不会误上传**(参数说明原文保留)
105
+ - ⬇️ 远程图 **先下载再上传**;本地失败会 **`/tmp` 重试**
106
+ - 🫧 图 + 下载链接 **同泡共存**:`![]` → mediaId,下载 URL 仍是原链
107
+
108
+ ### 🎨 AI Card 模板
109
+
110
+ - ✨ 支持自定义流式卡;**不填 `cardTemplateId` 默认**
111
+ `0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema`(含 📋 复制按钮等)
112
+
113
+ ### ⚙️ 最小配置(够跑)
114
+
115
+ ```json
116
+ "channels": {
117
+ "dingtalk-connector": {
118
+ "enabled": true,
119
+ "clientId": "你的clientId",
120
+ "clientSecret": "你的clientSecret"
121
+ }
122
+ }
123
+ ```
124
+
125
+ 未写的项走默认:流式卡 `0d2c84b3-…schema`、会话答案卡开、message 答案卡开、`messageImageMd=false`。
126
+
127
+ > **最大化配置 ≡ 最小化配置。** 下面只是把默认值显式写出来,方便对照字段含义;效果与只填凭证的最小配置相同,生产环境直接用最小配置即可。
128
+
129
+ ### 最大化配置(展示用,默认值写全)
130
+
131
+ 按单 Agent / 多 Agent 两种写法(值均为默认,**不等于额外能力**)。
132
+
133
+ **单 Agent(顶层凭证):**
92
134
 
93
135
  ```json
94
136
  "channels": {
@@ -96,57 +138,74 @@ message 远程 `media` 下载上传。
96
138
  "enabled": true,
97
139
  "clientId": "你的clientId",
98
140
  "clientSecret": "你的clientSecret",
99
- "cardTemplateId": "你的卡片模板ID.schema",
141
+ "cardTemplateId": "0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema",
100
142
  "cardContentVar": "content",
101
- "messageImageMd": false,
102
- "debug": false
143
+ "answerCard": true,
144
+ "answerActToken": 500,
145
+ "answerCardTemplateId": "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema",
146
+ "messageAnswerCard": true,
147
+ "messageImageMd": false
103
148
  }
104
149
  }
105
150
  ```
106
151
 
107
- **多机器人(多 Agent):** 每个账号可绑定不同机器人
152
+ **多 Agent(`accounts`,每个机器人一套凭证与卡片选项):**
108
153
 
109
154
  ```json
110
155
  "channels": {
111
156
  "dingtalk-connector": {
112
157
  "enabled": true,
158
+ "defaultAccount": "main-bot",
113
159
  "accounts": {
114
160
  "main-bot": {
115
161
  "enabled": true,
116
- "name": "工作流机器人",
117
- "clientId": "你的clientId",
118
- "clientSecret": "你的clientSecret",
119
- "cardTemplateId": "f9b75aac-713c-40e8-a17f-e236d7b5422b.schema",
120
- "cardContentVar": "content"
162
+ "name": "主机器人",
163
+ "clientId": "主机器人clientId",
164
+ "clientSecret": "主机器人clientSecret",
165
+ "cardTemplateId": "0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema",
166
+ "cardContentVar": "content",
167
+ "answerCard": true,
168
+ "answerActToken": 500,
169
+ "answerCardTemplateId": "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema",
170
+ "messageAnswerCard": true,
171
+ "messageImageMd": false
121
172
  },
122
- "another-bot": {
173
+ "guide-bot": {
123
174
  "enabled": true,
124
- "name": "另一个机器人",
125
- "clientId": "另一个clientId",
126
- "clientSecret": "另一个clientSecret",
127
- "cardTemplateId": "f9b75aac-713c-40e8-a17f-e236d7b5422b.schema",
128
- "cardContentVar": "content"
175
+ "name": "引导机器人",
176
+ "clientId": "引导机器人clientId",
177
+ "clientSecret": "引导机器人clientSecret",
178
+ "cardTemplateId": "0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema",
179
+ "cardContentVar": "content",
180
+ "answerCard": true,
181
+ "answerActToken": 500,
182
+ "answerCardTemplateId": "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema",
183
+ "messageAnswerCard": true,
184
+ "messageImageMd": false
129
185
  }
130
186
  }
131
187
  }
132
188
  }
133
189
  ```
134
190
 
135
- | 参数 | 说明 |
136
- |------|------|
137
- | `clientId` / `clientSecret` | 单机器人模式直接填在顶层 |
138
- | `accounts` | 多机器人模式,key 为账号标识名(可任意命名) |
139
- | `accounts.*.enabled` | 是否启用该账号 |
140
- | `accounts.*.name` | 账号显示名称(仅用于标识) |
141
- | `accounts.*.clientId` | 钉钉应用 ClientId |
142
- | `accounts.*.clientSecret` | 钉钉应用 ClientSecret |
143
- | `cardTemplateId` | AI Card 模板 ID,不填则使用官方默认模板 |
144
- | `cardContentVar` | 卡片内容变量名,不填默认 `msgContent`(过程/工具/终稿统一写入此字段) |
145
- | `answerCard` | 答案卡模式开关,**默认开启**;显式设 `false` 关闭 |
146
- | `answerActToken` | 答案卡触发阈值(token),默认 `500`;最终答案 ≤ 此值直接在原卡定稿,> 此值才另开答案卡 |
147
- | `answerCardTemplateId` | 答案卡模板 ID,不填用内置默认模板(需含 `content` 变量) |
148
-
149
- > 卡片模板需在[钉钉开放平台](https://open.dingtalk.com/)创建,并添加对应的变量字段。
191
+ ### 钉钉专属配置字段
192
+
193
+ | 字段 | 类型 | 默认 | 说明 |
194
+ |------|------|------|------|
195
+ | `clientId` | string \| number | — | 钉钉 AppKey / Client ID |
196
+ | `clientSecret` | string \| SecretRef | — | AppSecret |
197
+ | `accounts` | object | — | 多机器人;key 为账号 ID |
198
+ | `accounts.*.name` | string | — | 账号显示名 |
199
+ | `accounts.*.clientId` / `clientSecret` | — | — | 该机器人凭证 |
200
+ | `cardTemplateId` | string | `0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema` | 会话流式 AI Card 模板 |
201
+ | `cardContentVar` | string | `"content"` | 流式卡内容变量名 |
202
+ | `answerCard` | boolean | **true** | 会话流式答案卡;`false` 关闭 |
203
+ | `answerActToken` | int | **500** | 答案卡 token 阈值:≤ 原卡定稿,> 另开答案卡 |
204
+ | `answerCardTemplateId` | string | `d246b7f5-1783-4e9b-bb46-bef52d63050e.schema` | 答案静态卡模板 |
205
+ | `messageAnswerCard` | boolean | **true** | message 工具正文走答案静态卡;`false`=普通消息 |
206
+ | `messageImageMd` | boolean | **false** | message 图文:`false` 文图分开;`true` 可合并 markdown |
207
+
208
+ > `answerCard` = 对话流式收尾;`messageAnswerCard` = message 工具外发。
150
209
 
151
210
  ---
152
211
 
@@ -23,7 +23,7 @@ var entry_bundled_default = defineBundledChannelEntry({
23
23
  exportName: "setDingtalkRuntime"
24
24
  },
25
25
  async registerFull(api) {
26
- const { registerGatewayMethods } = await import("./gateway-methods-DHMdV0k8.mjs");
26
+ const { registerGatewayMethods } = await import("./gateway-methods-BnjDzM0H.mjs");
27
27
  registerGatewayMethods(api);
28
28
  }
29
29
  });
@@ -0,0 +1,2 @@
1
+ import { t as registerGatewayMethods } from "./gateway-methods-KiwXV8w1.mjs";
2
+ export { registerGatewayMethods };
@@ -1,7 +1,7 @@
1
1
  import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-BAzdqkAV.mjs";
2
2
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
3
3
  import { r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
4
- import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-msXFq5IJ.mjs";
4
+ import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-CgX-cHZa.mjs";
5
5
  import { c as getUnionId, d as recallEmotionReply } from "./utils-legacy-CALCPP1t.mjs";
6
6
  //#region src/docs.ts
7
7
  var DingtalkDocsClient = class {
package/dist/index.d.mts CHANGED
@@ -70,6 +70,7 @@ declare const DingtalkConfigSchema: z$1.ZodObject<{
70
70
  enableMediaUpload: z$1.ZodOptional<z$1.ZodBoolean>;
71
71
  systemPrompt: z$1.ZodOptional<z$1.ZodString>;
72
72
  messageImageMd: z$1.ZodOptional<z$1.ZodBoolean>;
73
+ messageAnswerCard: z$1.ZodOptional<z$1.ZodBoolean>;
73
74
  groupReplyMode: z$1.ZodOptional<z$1.ZodEnum<{
74
75
  aicard: "aicard";
75
76
  text: "text";
@@ -127,6 +128,7 @@ declare const DingtalkConfigSchema: z$1.ZodObject<{
127
128
  enableMediaUpload: z$1.ZodOptional<z$1.ZodBoolean>;
128
129
  systemPrompt: z$1.ZodOptional<z$1.ZodString>;
129
130
  messageImageMd: z$1.ZodOptional<z$1.ZodBoolean>;
131
+ messageAnswerCard: z$1.ZodOptional<z$1.ZodBoolean>;
130
132
  groupReplyMode: z$1.ZodOptional<z$1.ZodEnum<{
131
133
  aicard: "aicard";
132
134
  text: "text";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-f78QdJj5.mjs";
2
- import { t as registerGatewayMethods } from "./gateway-methods-DNvtOYEG.mjs";
1
+ import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-DOR9z5p2.mjs";
2
+ import { t as registerGatewayMethods } from "./gateway-methods-KiwXV8w1.mjs";
3
3
  //#region index.ts
4
4
  /**
5
5
  * 检测同一 plugin id 在多个路径被加载的情况。
@@ -1,9 +1,9 @@
1
1
  import { a as resolveDingtalkAccount } from "./accounts-BAzdqkAV.mjs";
2
- import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-f78QdJj5.mjs";
2
+ import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-DOR9z5p2.mjs";
3
3
  import { n as createLoggerFromConfig } from "./logger-mZ9OSbmD.mjs";
4
4
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
5
5
  import { i as getOapiAccessToken } from "./utils-CIfI_3Jh.mjs";
6
- import { S as finalClean, _ as FINAL_TAG, a as sendTextMessage, b as estimateTokens, d as finishAICard, g as unregisterActiveCard, h as streamAICard, i as sendProactive, m as registerActiveCard, p as isQpsLimitError, r as sendMessage, t as sendMarkdownMessage, u as createAICardForTarget, v as PROCESS_TAG, x as extractFinal, y as displayClean } from "./messaging-msXFq5IJ.mjs";
6
+ import { S as finalClean, _ as FINAL_TAG, a as sendTextMessage, b as estimateTokens, d as finishAICard, g as unregisterActiveCard, h as streamAICard, i as sendProactive, m as registerActiveCard, p as isQpsLimitError, r as sendMessage, t as sendMarkdownMessage, u as createAICardForTarget, v as PROCESS_TAG, x as extractFinal, y as displayClean } from "./messaging-CgX-cHZa.mjs";
7
7
  import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
8
8
  import { d as recallEmotionReply, o as getAccessToken, r as addEmotionReply, s as getOapiAccessToken$1, t as DINGTALK_API } from "./utils-legacy-CALCPP1t.mjs";
9
9
  import "./chunk-upload-BBQgGtcZ.mjs";
@@ -53,8 +53,10 @@ function unregisterActiveCard(openConversationId) {
53
53
  function getActiveCardForConversation(openConversationId) {
54
54
  return _activeCardRegistry.get(openConversationId) ?? null;
55
55
  }
56
- const DEFAULT_CARD_TEMPLATE_ID = "02fcf2f4-5e02-4a85-b672-46d1f715543e.schema";
57
- const DEFAULT_CARD_CONTENT_VAR = "msgContent";
56
+ /** 社区增强版流式 AI Card 默认模板(含复制按钮等) */
57
+ const DEFAULT_CARD_TEMPLATE_ID = "0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema";
58
+ /** 与默认增强模板 0d2c84b3… 字段一致 */
59
+ const DEFAULT_CARD_CONTENT_VAR = "content";
58
60
  /**
59
61
  * 钉钉卡片 API 的最大 QPS(官方限制约 40 次/秒)。
60
62
  * 保守取 20,为 createAICardForTarget / finishAICard 等非流式调用留余量。
@@ -582,6 +584,28 @@ function prepareMultiBotMentions(params) {
582
584
  //#endregion
583
585
  //#region src/services/messaging.ts
584
586
  /**
587
+ * message 工具是否用答案卡(默认开;显式 false 才关)。
588
+ * 与会话流式 answerCard 独立。
589
+ */
590
+ function isMessageAnswerCardEnabled(config) {
591
+ return config?.messageAnswerCard !== false;
592
+ }
593
+ /**
594
+ * message 工具正文发送的卡片选项:答案卡模板 + 静态 FINISHED,失败降级普通消息。
595
+ */
596
+ function messageToolProactiveCardOptions(config) {
597
+ if (!isMessageAnswerCardEnabled(config)) return {
598
+ useAICard: false,
599
+ fallbackToNormal: true
600
+ };
601
+ return {
602
+ useAICard: true,
603
+ fallbackToNormal: true,
604
+ cardTemplateId: typeof config.answerCardTemplateId === "string" && config.answerCardTemplateId.trim() || "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema",
605
+ skipInputingWalk: true
606
+ };
607
+ }
608
+ /**
585
609
  * 发送 Markdown 消息
586
610
  * 支持 @用户(atUserId)和 @机器人(atDingtalkIds)
587
611
  */
@@ -898,12 +922,13 @@ async function sendTextToDingTalk(params) {
898
922
  console.warn(`[DingTalk][LocalImage] sendTextToDingTalk 图片处理失败: ${err?.message || err}`);
899
923
  }
900
924
  const msgType = shouldSendAsMarkdown(text) ? "markdown" : "text";
901
- logMediaIdTrace("sendTextToDingTalk:API前", text, `msgType=${msgType}`, true);
925
+ const cardOpts = messageToolProactiveCardOptions(config);
926
+ logMediaIdTrace("sendTextToDingTalk:API前", text, `msgType=${msgType} messageAnswerCard=${isMessageAnswerCardEnabled(config)} useAICard=${cardOpts.useAICard}`, true);
927
+ console.log(`[DingTalk][MessageCard] sendTextToDingTalk | messageAnswerCard=${isMessageAnswerCardEnabled(config)} useAICard=${cardOpts.useAICard} tpl=${cardOpts.cardTemplateId || "-"}`);
902
928
  return sendProactive(config, targetParam, text, {
903
929
  msgType,
904
930
  replyToId,
905
- useAICard: false,
906
- fallbackToNormal: true,
931
+ ...cardOpts,
907
932
  skipProcessLocalImages: true
908
933
  });
909
934
  }
@@ -1000,8 +1025,7 @@ async function sendMediaToDingTalk(params) {
1000
1025
  if (text?.trim()) await sendProactive(config, targetParam, text.trim(), {
1001
1026
  msgType: shouldSendAsMarkdown(text) ? "markdown" : "text",
1002
1027
  replyToId,
1003
- useAICard: false,
1004
- fallbackToNormal: true
1028
+ ...messageToolProactiveCardOptions(config)
1005
1029
  });
1006
1030
  if (isRemoteHttpUrl(mediaUrl) && mediaType === "image") {
1007
1031
  console.log(`[DingTalk][RemoteMedia] 下载失败,尝试 photoURL 直链发送 | ${mediaUrl}`);
@@ -1041,8 +1065,7 @@ async function sendMediaToDingTalk(params) {
1041
1065
  if (caption) await sendProactive(config, targetParam, caption, {
1042
1066
  msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
1043
1067
  replyToId,
1044
- useAICard: false,
1045
- fallbackToNormal: true
1068
+ ...messageToolProactiveCardOptions(config)
1046
1069
  });
1047
1070
  if (isRemoteHttpUrl(mediaUrl)) {
1048
1071
  console.log(`[DingTalk][RemoteMedia] 再上传失败,photoURL 直链兜底 | ${mediaUrl}`);
@@ -1068,8 +1091,7 @@ async function sendMediaToDingTalk(params) {
1068
1091
  const result = await sendProactive(config, targetParam, `${caption}\n\n![](${mediaId})`, {
1069
1092
  msgType: "markdown",
1070
1093
  replyToId,
1071
- useAICard: false,
1072
- fallbackToNormal: true
1094
+ ...messageToolProactiveCardOptions(config)
1073
1095
  });
1074
1096
  return {
1075
1097
  ...result,
@@ -1079,8 +1101,7 @@ async function sendMediaToDingTalk(params) {
1079
1101
  if (caption) await sendProactive(config, targetParam, caption, {
1080
1102
  msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
1081
1103
  replyToId,
1082
- useAICard: false,
1083
- fallbackToNormal: true
1104
+ ...messageToolProactiveCardOptions(config)
1084
1105
  });
1085
1106
  const result = await sendProactive(config, targetParam, mediaId, {
1086
1107
  msgType: "image",
@@ -1102,8 +1123,7 @@ async function sendMediaToDingTalk(params) {
1102
1123
  await sendProactive(config, targetParam, caption, {
1103
1124
  msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
1104
1125
  replyToId,
1105
- useAICard: false,
1106
- fallbackToNormal: true
1126
+ ...messageToolProactiveCardOptions(config)
1107
1127
  });
1108
1128
  }
1109
1129
  const uploadResult = await uploadMediaToDingTalk(resolvedMediaUrl, mediaType, oapiToken, maxSize, log);
@@ -1219,12 +1239,13 @@ async function sendProactiveInternal(config, target, content, options) {
1219
1239
  usedAICard: false
1220
1240
  };
1221
1241
  }
1222
- const { msgType = "text", useAICard = true, fallbackToNormal = true, log: externalLog } = options;
1242
+ const { msgType = "text", useAICard = true, fallbackToNormal = true, cardTemplateId, skipInputingWalk = false, log: externalLog } = options;
1223
1243
  const isMediaMessage = MEDIA_MSG_TYPES.has(msgType);
1224
1244
  if (useAICard && !isMediaMessage) try {
1225
- const card = await createAICardForTarget(config, target, externalLog);
1245
+ const card = await createAICardForTarget(config, target, externalLog, cardTemplateId);
1226
1246
  if (card) {
1227
- await finishAICard(card, content, config, externalLog);
1247
+ await finishAICard(card, content, config, externalLog, void 0, skipInputingWalk);
1248
+ console.log(`[DingTalk][MessageCard] AI Card 发送成功 | outTrack=${card.cardInstanceId} tpl=${cardTemplateId || "default"} skipInputing=${skipInputingWalk}`);
1228
1249
  return {
1229
1250
  ok: true,
1230
1251
  cardInstanceId: card.cardInstanceId,
@@ -1236,8 +1257,10 @@ async function sendProactiveInternal(config, target, content, options) {
1236
1257
  error: "Failed to create AI Card",
1237
1258
  usedAICard: false
1238
1259
  };
1260
+ console.warn(`[DingTalk][MessageCard] AI Card 创建失败,fallbackToNormal=${fallbackToNormal}`);
1239
1261
  } catch (err) {
1240
1262
  externalLog?.error?.(`AI Card 发送失败: ${err.message}`);
1263
+ console.warn(`[DingTalk][MessageCard] AI Card 异常: ${err?.message || err},fallbackToNormal=${fallbackToNormal}`);
1241
1264
  if (!fallbackToNormal) return {
1242
1265
  ok: false,
1243
1266
  error: err.message,
@@ -3,7 +3,7 @@ import { a as resolveDingtalkAccount, c as addWildcardAllowFrom, d as hasConfigu
3
3
  import { t as createLogger } from "./logger-mZ9OSbmD.mjs";
4
4
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
5
5
  import "./utils-CIfI_3Jh.mjs";
6
- import { S as finalClean, f as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-msXFq5IJ.mjs";
6
+ import { S as finalClean, f as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-CgX-cHZa.mjs";
7
7
  import { createRequire } from "node:module";
8
8
  import { z, z as z$1 } from "zod";
9
9
  //#region src/secret-input.ts
@@ -102,16 +102,23 @@ const DingtalkSharedConfigShape = {
102
102
  * 单张图文、纯媒体文件仍分开发送
103
103
  */
104
104
  messageImageMd: z$1.boolean().optional(),
105
+ /**
106
+ * message 工具是否用答案卡发送正文(默认 true,显式 false 才关):
107
+ * - true / 未配:走答案专用静态卡(answerCardTemplateId 或内置模板),失败降级普通消息
108
+ * - false:直接普通 text/markdown(旧行为)
109
+ * 与会话流式的 answerCard 独立;媒体消息(图/音/视/文件)仍走普通通道。
110
+ */
111
+ messageAnswerCard: z$1.boolean().optional(),
105
112
  groupReplyMode: GroupReplyModeSchema,
106
113
  /** AI Card 模板 ID,不填则使用官方默认模板 */
107
114
  cardTemplateId: z$1.string().optional(),
108
- /** AI Card 内容变量名,对应卡片模板中的变量字段,不填默认 msgContent */
109
- cardContentVar: z$1.string().optional().default("msgContent"),
115
+ /** AI Card 内容变量名,对应卡片模板中的变量字段,不填默认 content(与默认增强模板一致) */
116
+ cardContentVar: z$1.string().optional().default("content"),
110
117
  /** 答案专用卡模式:超过 answerActToken 的最终答案投到一张新的静态文本卡,原流式卡定格"思考完成",
111
118
  * 规避钉钉流式卡 FINISHED 后仍抖动的官方 bug。默认开启(显式设 false 才关)。 */
112
119
  answerCard: z$1.boolean().optional(),
113
120
  /** 答案卡触发阈值(token):最终答案估算 token ≤ 此值时直接在原卡定稿(不另建卡),
114
- * > 此值才新建答案卡。避免简单任务也多一张卡。默认 600。 */
121
+ * > 此值才新建答案卡。避免简单任务也多一张卡。默认 500(见 reply-dispatcher)。 */
115
122
  answerActToken: z$1.number().int().positive().optional(),
116
123
  /** 答案专用卡模板 ID:不填用内置默认模板。需是钉钉后台已发布、含 content 变量的卡片模板。 */
117
124
  answerCardTemplateId: z$1.string().optional()
@@ -930,7 +937,7 @@ async function monitorDingtalkProvider(opts = {}) {
930
937
  const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
931
938
  const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
932
939
  import("./accounts-BQptOmgB.mjs"),
933
- import("./message-handler-DVXM5olz.mjs"),
940
+ import("./message-handler-BpyhuanG.mjs"),
934
941
  import("./connection-DHHFFNQJ.mjs")
935
942
  ]);
936
943
  const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
@@ -0,0 +1,71 @@
1
+ # Release Notes — v0.8.21-fix48(2026-07-21)
2
+
3
+ 生产稳定版 / Production-stable community build of `@jeik/dingtalk-connector`。
4
+
5
+ ## 安装 / Install
6
+
7
+ ```bash
8
+ npx -y @jeik/dingtalk-connector install --force
9
+ openclaw gateway restart
10
+ ```
11
+
12
+ 本地 tgz:
13
+
14
+ ```bash
15
+ openclaw plugins install ./jeik-dingtalk-connector-0.8.21-fix48.tgz --force
16
+ openclaw gateway restart
17
+ ```
18
+
19
+ ## 本版要点 / Highlights
20
+
21
+ | 领域 | 说明 |
22
+ |------|------|
23
+ | **message 答案卡** | 新增配置 **`messageAnswerCard`(默认 `true`)**:message 工具正文默认走**答案静态卡** |
24
+ | 模板 | 使用 `answerCardTemplateId` 或内置答案卡模板;`skipInputingWalk` 直接 FINISHED |
25
+ | 降级 | 建卡失败 → 自动普通 text/markdown |
26
+ | 关闭 | `"messageAnswerCard": false` 恢复旧行为(普通消息) |
27
+ | 注册 | `schema.ts` + `openclaw.plugin.json`(channels / accounts / uiHints) |
28
+ | 独立 | 与会话流式 **`answerCard`** 互不影响;图/音/视/文件仍走普通通道 |
29
+
30
+ ## 配置示例
31
+
32
+ ```json
33
+ "channels": {
34
+ "dingtalk-connector": {
35
+ "messageAnswerCard": true,
36
+ "answerCardTemplateId": "可选-自定义答案卡模板.schema"
37
+ }
38
+ }
39
+ ```
40
+
41
+ 关闭 message 答案卡:
42
+
43
+ ```json
44
+ "messageAnswerCard": false
45
+ ```
46
+
47
+ ### 钉钉专属字段(文档)
48
+
49
+ 见 README 最小/最大化配置与字段表:
50
+
51
+ - 凭证 / `accounts`(name、clientId、clientSecret)
52
+ - `cardTemplateId`(默认 `0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema`)/ `cardContentVar`(默认 `content`)
53
+ - `answerCard` / `answerActToken`(500)/ `answerCardTemplateId`
54
+ - `messageAnswerCard`(true)/ `messageImageMd`(false)
55
+
56
+ ## 日志
57
+
58
+ ```text
59
+ [DingTalk][MessageCard] sendTextToDingTalk | messageAnswerCard=true useAICard=true tpl=...
60
+ [DingTalk][MessageCard] AI Card 发送成功 | outTrack=card_... skipInputing=true
61
+ ```
62
+
63
+ ## 含此前修复
64
+
65
+ - fix47:`file://` 本地图不再误判远程
66
+ - fix46:LocalImage / MediaIdTrace 诊断日志
67
+ - fix45:图+下载同泡、只认 `![]`、CardCache、residual 误报
68
+
69
+ ## 升级
70
+
71
+ 从任意 fix37+ 直接 `--force` 安装即可。
@@ -0,0 +1,16 @@
1
+ # Release Notes — v0.8.21-fix49(2026-07-21)
2
+
3
+ ## 安装
4
+
5
+ ```bash
6
+ npx -y @jeik/dingtalk-connector install --force
7
+ openclaw gateway restart
8
+ ```
9
+
10
+ ## 本版要点
11
+
12
+ - 默认流式卡:`0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema`,`cardContentVar=content`
13
+ - 含 fix48:`messageAnswerCard`(默认开)、`file://` 本地图修复、图片发送全路径增强
14
+ - 文档:最小/最大化配置、图片能力说明
15
+
16
+ 从 fix48 直接 `--force` 安装即可。
@@ -14,7 +14,7 @@
14
14
  "additionalProperties": false,
15
15
  "properties": {
16
16
  "cardTemplateId": { "type": "string" },
17
- "cardContentVar": { "type": "string", "default": "msgContent" }
17
+ "cardContentVar": { "type": "string", "default": "content" }
18
18
  }
19
19
  },
20
20
  "channelConfigs": {
@@ -247,6 +247,11 @@
247
247
  "default": false,
248
248
  "description": "message 工具图文策略:false=文图分开(默认);true=多图+文字时才合并为 markdown"
249
249
  },
250
+ "messageAnswerCard": {
251
+ "type": "boolean",
252
+ "default": true,
253
+ "description": "message 工具是否用答案卡发送正文:true(默认)= 答案静态卡;false= 普通 text/markdown。媒体消息仍走普通通道。"
254
+ },
250
255
  "groupReplyMode": {
251
256
  "type": "string",
252
257
  "enum": [
@@ -260,7 +265,7 @@
260
265
  },
261
266
  "cardContentVar": {
262
267
  "type": "string",
263
- "default": "msgContent"
268
+ "default": "content"
264
269
  },
265
270
  "answerCard": {
266
271
  "type": "boolean"
@@ -493,6 +498,11 @@
493
498
  "default": false,
494
499
  "description": "message 工具图文策略:false=文图分开(默认);true=多图+文字时才合并为 markdown"
495
500
  },
501
+ "messageAnswerCard": {
502
+ "type": "boolean",
503
+ "default": true,
504
+ "description": "message 工具是否用答案卡发送正文:true(默认)= 答案静态卡;false= 普通 text/markdown。媒体消息仍走普通通道。"
505
+ },
496
506
  "groupReplyMode": {
497
507
  "type": "string",
498
508
  "enum": [
@@ -506,7 +516,7 @@
506
516
  },
507
517
  "cardContentVar": {
508
518
  "type": "string",
509
- "default": "msgContent"
519
+ "default": "content"
510
520
  },
511
521
  "answerCard": {
512
522
  "type": "boolean"
@@ -557,6 +567,41 @@
557
567
  "help": "message 工具图文策略。false(默认):文字与图片分开发送,阅读体验更好。true:仅当多图与文字结合时合并为一条 markdown;单张图文、纯媒体仍分开。",
558
568
  "advanced": true
559
569
  },
570
+ "channels.dingtalk-connector.messageAnswerCard": {
571
+ "label": "Message Answer Card",
572
+ "help": "message 工具是否用答案卡发正文。默认开启(答案静态卡,失败降级普通消息);设为 false 则直接发 text/markdown。与会话流式的 answerCard 独立。",
573
+ "advanced": true
574
+ },
575
+ "channels.dingtalk-connector.groupReplyMode": {
576
+ "label": "Group Reply Mode",
577
+ "help": "群回复形态:aicard(默认,流式 AI 卡)/ text / markdown。text、markdown 时群聊不用 AI 卡,便于多机器人互相 @。",
578
+ "advanced": true
579
+ },
580
+ "channels.dingtalk-connector.cardTemplateId": {
581
+ "label": "AI Card Template ID",
582
+ "help": "会话流式 AI Card 模板 ID;不填默认 0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema。",
583
+ "advanced": true
584
+ },
585
+ "channels.dingtalk-connector.cardContentVar": {
586
+ "label": "Card Content Variable",
587
+ "help": "流式卡内容变量名,默认 content(过程/工具/终稿写入此字段)。",
588
+ "advanced": true
589
+ },
590
+ "channels.dingtalk-connector.answerCard": {
591
+ "label": "Session Answer Card",
592
+ "help": "会话流式答案卡开关,默认开启。长答案另开静态卡,原卡定格思考完成;显式 false 关闭。",
593
+ "advanced": true
594
+ },
595
+ "channels.dingtalk-connector.answerActToken": {
596
+ "label": "Answer Card Token Threshold",
597
+ "help": "会话答案卡触发阈值(token),默认 500。≤ 阈值在原卡定稿;> 阈值才新建答案卡。",
598
+ "advanced": true
599
+ },
600
+ "channels.dingtalk-connector.answerCardTemplateId": {
601
+ "label": "Answer Card Template ID",
602
+ "help": "答案静态卡模板 ID;message 答案卡与会话答案卡共用。不填用内置默认模板。",
603
+ "advanced": true
604
+ },
560
605
  "channels.dingtalk-connector.endpoint": {
561
606
  "label": "Gateway Endpoint",
562
607
  "help": "自定义 DWClient 网关地址(高级)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeik/dingtalk-connector",
3
- "version": "0.8.21-fix47",
3
+ "version": "0.8.21-fix49",
4
4
  "description": "OpenClaw DingTalk channel plugin (fork) | 钉钉 OpenClaw 渠道插件(fork 自官方,含标记/卡片渲染修复)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -91,16 +91,23 @@ const DingtalkSharedConfigShape = {
91
91
  * 单张图文、纯媒体文件仍分开发送
92
92
  */
93
93
  messageImageMd: z.boolean().optional(),
94
+ /**
95
+ * message 工具是否用答案卡发送正文(默认 true,显式 false 才关):
96
+ * - true / 未配:走答案专用静态卡(answerCardTemplateId 或内置模板),失败降级普通消息
97
+ * - false:直接普通 text/markdown(旧行为)
98
+ * 与会话流式的 answerCard 独立;媒体消息(图/音/视/文件)仍走普通通道。
99
+ */
100
+ messageAnswerCard: z.boolean().optional(),
94
101
  groupReplyMode: GroupReplyModeSchema,
95
102
  /** AI Card 模板 ID,不填则使用官方默认模板 */
96
103
  cardTemplateId: z.string().optional(),
97
- /** AI Card 内容变量名,对应卡片模板中的变量字段,不填默认 msgContent */
98
- cardContentVar: z.string().optional().default("msgContent"),
104
+ /** AI Card 内容变量名,对应卡片模板中的变量字段,不填默认 content(与默认增强模板一致) */
105
+ cardContentVar: z.string().optional().default("content"),
99
106
  /** 答案专用卡模式:超过 answerActToken 的最终答案投到一张新的静态文本卡,原流式卡定格"思考完成",
100
107
  * 规避钉钉流式卡 FINISHED 后仍抖动的官方 bug。默认开启(显式设 false 才关)。 */
101
108
  answerCard: z.boolean().optional(),
102
109
  /** 答案卡触发阈值(token):最终答案估算 token ≤ 此值时直接在原卡定稿(不另建卡),
103
- * > 此值才新建答案卡。避免简单任务也多一张卡。默认 600。 */
110
+ * > 此值才新建答案卡。避免简单任务也多一张卡。默认 500(见 reply-dispatcher)。 */
104
111
  answerActToken: z.number().int().positive().optional(),
105
112
  /** 答案专用卡模板 ID:不填用内置默认模板。需是钉钉后台已发布、含 content 变量的卡片模板。 */
106
113
  answerCardTemplateId: z.string().optional(),
@@ -28,8 +28,10 @@ export function getActiveCardForConversation(openConversationId: string): AICard
28
28
 
29
29
  // ============ 常量 ============
30
30
 
31
- const DEFAULT_CARD_TEMPLATE_ID = "02fcf2f4-5e02-4a85-b672-46d1f715543e.schema";
32
- const DEFAULT_CARD_CONTENT_VAR = "msgContent";
31
+ /** 社区增强版流式 AI Card 默认模板(含复制按钮等) */
32
+ const DEFAULT_CARD_TEMPLATE_ID = "0d2c84b3-12c1-473b-b14a-f329a7a102cd.schema";
33
+ /** 与默认增强模板 0d2c84b3… 字段一致 */
34
+ const DEFAULT_CARD_CONTENT_VAR = "content";
33
35
 
34
36
  /**
35
37
  * 钉钉卡片 API 的最大 QPS(官方限制约 40 次/秒)。
@@ -15,4 +15,6 @@ export {
15
15
  sendToGroup,
16
16
  sendTextToDingTalk,
17
17
  sendMediaToDingTalk,
18
+ isMessageAnswerCardEnabled,
19
+ messageToolProactiveCardOptions,
18
20
  } from '../messaging.ts';
@@ -22,11 +22,43 @@ import {
22
22
  createAICardForTarget,
23
23
  streamAICard,
24
24
  finishAICard,
25
+ ANSWER_CARD_TEMPLATE_ID,
25
26
  type AICardInstance,
26
27
  type AICardTarget,
27
28
  } from "./messaging/card.ts";
28
29
  import { substituteBotMentions } from "./messaging/mentions.ts";
29
30
 
31
+ /**
32
+ * message 工具是否用答案卡(默认开;显式 false 才关)。
33
+ * 与会话流式 answerCard 独立。
34
+ */
35
+ export function isMessageAnswerCardEnabled(config: DingtalkConfig | null | undefined): boolean {
36
+ return (config as any)?.messageAnswerCard !== false;
37
+ }
38
+
39
+ /**
40
+ * message 工具正文发送的卡片选项:答案卡模板 + 静态 FINISHED,失败降级普通消息。
41
+ */
42
+ export function messageToolProactiveCardOptions(
43
+ config: DingtalkConfig,
44
+ ): Pick<
45
+ ProactiveSendOptions,
46
+ "useAICard" | "fallbackToNormal" | "cardTemplateId" | "skipInputingWalk"
47
+ > {
48
+ if (!isMessageAnswerCardEnabled(config)) {
49
+ return { useAICard: false, fallbackToNormal: true };
50
+ }
51
+ const tpl =
52
+ (typeof config.answerCardTemplateId === "string" && config.answerCardTemplateId.trim()) ||
53
+ ANSWER_CARD_TEMPLATE_ID;
54
+ return {
55
+ useAICard: true,
56
+ fallbackToNormal: true,
57
+ cardTemplateId: tpl,
58
+ skipInputingWalk: true,
59
+ };
60
+ }
61
+
30
62
  // ============ 常量 ============
31
63
  // 注意:AI Card 相关的类型和函数已移至 ./messaging/card.ts,通过上方 import 引入
32
64
 
@@ -55,6 +87,15 @@ export interface ProactiveSendOptions {
55
87
  log?: any;
56
88
  useAICard?: boolean;
57
89
  fallbackToNormal?: boolean;
90
+ /**
91
+ * 覆盖 AI Card / 答案卡模板 ID(如 message 工具答案卡)。
92
+ * 不传则 createAICardForTarget 用 config.cardTemplateId / 默认流式模板。
93
+ */
94
+ cardTemplateId?: string;
95
+ /**
96
+ * 答案静态卡:finish 时跳过 INPUTING 过渡,直接 FINISHED(与 closeStreaming 答案卡一致)。
97
+ */
98
+ skipInputingWalk?: boolean;
58
99
  /**
59
100
  * 已在上层 processLocalImages 过则跳过,避免二次处理干扰 MediaIdTrace。
60
101
  */
@@ -873,13 +914,21 @@ export async function sendTextToDingTalk(params: {
873
914
  }
874
915
 
875
916
  const msgType: DingTalkMsgType = shouldSendAsMarkdown(text) ? "markdown" : "text";
876
- logMediaIdTrace("sendTextToDingTalk:API前", text, `msgType=${msgType}`, true);
917
+ const cardOpts = messageToolProactiveCardOptions(config);
918
+ logMediaIdTrace(
919
+ "sendTextToDingTalk:API前",
920
+ text,
921
+ `msgType=${msgType} messageAnswerCard=${isMessageAnswerCardEnabled(config)} useAICard=${cardOpts.useAICard}`,
922
+ true,
923
+ );
924
+ console.log(
925
+ `[DingTalk][MessageCard] sendTextToDingTalk | messageAnswerCard=${isMessageAnswerCardEnabled(config)} useAICard=${cardOpts.useAICard} tpl=${cardOpts.cardTemplateId || "-"}`,
926
+ );
877
927
 
878
928
  return sendProactive(config, targetParam, text, {
879
929
  msgType,
880
930
  replyToId,
881
- useAICard: false,
882
- fallbackToNormal: true,
931
+ ...cardOpts,
883
932
  skipProcessLocalImages: true,
884
933
  });
885
934
  }
@@ -992,8 +1041,7 @@ export async function sendMediaToDingTalk(params: {
992
1041
  await sendProactive(config, targetParam, text.trim(), {
993
1042
  msgType: shouldSendAsMarkdown(text) ? "markdown" : "text",
994
1043
  replyToId,
995
- useAICard: false,
996
- fallbackToNormal: true,
1044
+ ...messageToolProactiveCardOptions(config),
997
1045
  });
998
1046
  }
999
1047
  // 远程图可再尝试 photoURL 直链(部分环境可用)
@@ -1052,8 +1100,7 @@ export async function sendMediaToDingTalk(params: {
1052
1100
  await sendProactive(config, targetParam, caption, {
1053
1101
  msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
1054
1102
  replyToId,
1055
- useAICard: false,
1056
- fallbackToNormal: true,
1103
+ ...messageToolProactiveCardOptions(config),
1057
1104
  });
1058
1105
  }
1059
1106
  if (isRemoteHttpUrl(mediaUrl)) {
@@ -1089,8 +1136,7 @@ export async function sendMediaToDingTalk(params: {
1089
1136
  const result = await sendProactive(config, targetParam, combined, {
1090
1137
  msgType: "markdown",
1091
1138
  replyToId,
1092
- useAICard: false,
1093
- fallbackToNormal: true,
1139
+ ...messageToolProactiveCardOptions(config),
1094
1140
  });
1095
1141
  return {
1096
1142
  ...result,
@@ -1102,8 +1148,7 @@ export async function sendMediaToDingTalk(params: {
1102
1148
  await sendProactive(config, targetParam, caption, {
1103
1149
  msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
1104
1150
  replyToId,
1105
- useAICard: false,
1106
- fallbackToNormal: true,
1151
+ ...messageToolProactiveCardOptions(config),
1107
1152
  });
1108
1153
  }
1109
1154
  const result = await sendProactive(config, targetParam, mediaId, {
@@ -1130,8 +1175,7 @@ export async function sendMediaToDingTalk(params: {
1130
1175
  await sendProactive(config, targetParam, caption, {
1131
1176
  msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
1132
1177
  replyToId,
1133
- useAICard: false,
1134
- fallbackToNormal: true,
1178
+ ...messageToolProactiveCardOptions(config),
1135
1179
  });
1136
1180
  }
1137
1181
 
@@ -1321,6 +1365,8 @@ async function sendProactiveInternal(
1321
1365
  msgType = "text",
1322
1366
  useAICard = true, // 默认启用 AI Card,让主动发送消息优先使用卡片形式
1323
1367
  fallbackToNormal = true, // 默认降级,AI Card 失败时自动回退到普通消息
1368
+ cardTemplateId,
1369
+ skipInputingWalk = false,
1324
1370
  log: externalLog,
1325
1371
  } = options;
1326
1372
 
@@ -1330,9 +1376,25 @@ async function sendProactiveInternal(
1330
1376
  // 如果启用 AI Card(媒体消息强制跳过)
1331
1377
  if (useAICard && !isMediaMessage) {
1332
1378
  try {
1333
- const card = await createAICardForTarget(config, target, externalLog);
1379
+ const card = await createAICardForTarget(
1380
+ config,
1381
+ target,
1382
+ externalLog,
1383
+ cardTemplateId,
1384
+ );
1334
1385
  if (card) {
1335
- await finishAICard(card, content, config, externalLog);
1386
+ // 答案静态卡:skipInputingWalk=true 直接 FINISHED;普通卡仍走 INPUTING 再定稿
1387
+ await finishAICard(
1388
+ card,
1389
+ content,
1390
+ config,
1391
+ externalLog,
1392
+ undefined,
1393
+ skipInputingWalk,
1394
+ );
1395
+ console.log(
1396
+ `[DingTalk][MessageCard] AI Card 发送成功 | outTrack=${card.cardInstanceId} tpl=${cardTemplateId || "default"} skipInputing=${skipInputingWalk}`,
1397
+ );
1336
1398
  return {
1337
1399
  ok: true,
1338
1400
  cardInstanceId: card.cardInstanceId,
@@ -1346,8 +1408,14 @@ async function sendProactiveInternal(
1346
1408
  usedAICard: false,
1347
1409
  };
1348
1410
  }
1411
+ console.warn(
1412
+ `[DingTalk][MessageCard] AI Card 创建失败,fallbackToNormal=${fallbackToNormal}`,
1413
+ );
1349
1414
  } catch (err: any) {
1350
1415
  externalLog?.error?.(`AI Card 发送失败: ${err.message}`);
1416
+ console.warn(
1417
+ `[DingTalk][MessageCard] AI Card 异常: ${err?.message || err},fallbackToNormal=${fallbackToNormal}`,
1418
+ );
1351
1419
  if (!fallbackToNormal) {
1352
1420
  return { ok: false, error: err.message, usedAICard: false };
1353
1421
  }
@@ -1,2 +0,0 @@
1
- import { t as registerGatewayMethods } from "./gateway-methods-DNvtOYEG.mjs";
2
- export { registerGatewayMethods };