@jeik/dingtalk-connector 0.8.21-fix46 → 0.8.21-fix48
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 +25 -0
- package/README.en.md +102 -24
- package/README.md +101 -29
- package/dist/entry-bundled.mjs +1 -1
- package/dist/{gateway-methods-CkBI4__L.mjs → gateway-methods-CuqlMAXE.mjs} +1 -1
- package/dist/{gateway-methods-BTByJFxo.mjs → gateway-methods-DtzGBBiy.mjs} +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2 -2
- package/dist/{media-9LvaywGZ.mjs → media-BqpUGCni.mjs} +1 -1
- package/dist/{media-BMNRjseJ.mjs → media-DnQB2BrL.mjs} +10 -2
- package/dist/{message-handler-CytB34ZJ.mjs → message-handler-DKPtnK0x.mjs} +7 -7
- package/dist/{messaging-ngF20yqM.mjs → messaging-DOEBZgXQ.mjs} +41 -20
- package/dist/{runtime-I36vhOzx.mjs → runtime-hVrm4xYI.mjs} +11 -4
- package/docs/RELEASE_NOTES_V0.8.21-fix47.md +32 -0
- package/docs/RELEASE_NOTES_V0.8.21-fix48.md +73 -0
- package/openclaw.plugin.json +45 -0
- package/package.json +1 -1
- package/src/config/schema.ts +8 -1
- package/src/services/media/common.ts +5 -6
- package/src/services/media.ts +18 -4
- package/src/services/messaging/index.ts +2 -0
- package/src/services/messaging.ts +83 -15
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-fix48] - 2026-07-21
|
|
9
|
+
|
|
10
|
+
### 新增 / Added
|
|
11
|
+
- ✨ **`messageAnswerCard`(默认 `true`)** — message 工具正文默认走**答案静态卡**(`answerCardTemplateId` 或内置模板,`skipInputingWalk`);失败降级普通消息。显式 `false` 关闭。
|
|
12
|
+
已注册:`schema.ts`、`openclaw.plugin.json`(channels + accounts + uiHints)。
|
|
13
|
+
与会话流式 `answerCard` 独立;图/音/视/文件仍走普通通道。
|
|
14
|
+
|
|
15
|
+
### 安装 / Install
|
|
16
|
+
```bash
|
|
17
|
+
npx -y @jeik/dingtalk-connector install --force
|
|
18
|
+
openclaw gateway restart
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## [0.8.21-fix47] - 2026-07-20
|
|
22
|
+
|
|
23
|
+
### 修复 / Fixed
|
|
24
|
+
- 🐛 **`file://` 本地图被误判为远程** — `looksLikeRemoteUrl` 对含 `://` 一律当远程,导致 `` / `file:///root/...` 走 `fetch` → `fetch failed`,原样发出灰图。现:`file://` / `MEDIA:` / `attachment://` **优先按本地上传**(`toLocalPath` 后 oapi)。
|
|
25
|
+
日志特征(fix46):`kind=http path=file://...` + `远程图下载异常 | fetch failed` + `localTried=0`。
|
|
26
|
+
|
|
27
|
+
### 安装 / Install
|
|
28
|
+
```bash
|
|
29
|
+
npx -y @jeik/dingtalk-connector install --force
|
|
30
|
+
openclaw gateway restart
|
|
31
|
+
```
|
|
32
|
+
|
|
8
33
|
## [0.8.21-fix46] - 2026-07-20
|
|
9
34
|
|
|
10
35
|
### 诊断 / Diagnostics
|
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-
|
|
7
|
+
<p><strong>Current published release: <a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-fix48</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,19 +23,28 @@
|
|
|
23
23
|
|
|
24
24
|
## 🔧 Recent Updates
|
|
25
25
|
|
|
26
|
-
### 🚀 v0.8.21-
|
|
26
|
+
### 🚀 v0.8.21-fix48 · 2026-07-21 (current)
|
|
27
27
|
|
|
28
|
-
**Theme:
|
|
28
|
+
**Theme: message tool answer card by default (toggle)**
|
|
29
29
|
|
|
30
30
|
| | Change |
|
|
31
31
|
|--|--------|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
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 |
|
|
34
35
|
|
|
35
36
|
```bash
|
|
36
37
|
npx -y @jeik/dingtalk-connector install --force && openclaw gateway restart
|
|
37
38
|
```
|
|
38
39
|
|
|
40
|
+
### 📦 v0.8.21-fix47 · 2026-07-20
|
|
41
|
+
|
|
42
|
+
`file://` no longer misclassified as remote (grey-image fix).
|
|
43
|
+
|
|
44
|
+
### 📦 v0.8.21-fix46 · 2026-07-20
|
|
45
|
+
|
|
46
|
+
Always-on local MD image diagnostics.
|
|
47
|
+
|
|
39
48
|
### 📦 v0.8.21-fix45 · 2026-07-20
|
|
40
49
|
|
|
41
50
|
Image + download link one bubble · only `![]` · quote cache · residual false-positive fix.
|
|
@@ -46,14 +55,7 @@ Remote `media` download-then-upload.
|
|
|
46
55
|
|
|
47
56
|
### 📦 v0.8.21-fix37 · 2026-07-20
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| | Change |
|
|
52
|
-
|--|--------|
|
|
53
|
-
| 📷 **Local MD images** | Any absolute path (incl. **`/mnt`**, Chinese paths); upload retries via `/tmp` copy |
|
|
54
|
-
| 📦 **Code-block safe** | Paths inside fenced/inline code are **not** uploaded (param samples stay literal) |
|
|
55
|
-
| ⚙️ **`messageImageMd`** | Default `false`: message tool sends **text then image** separately; `true` merges only multi-image + text into one markdown |
|
|
56
|
-
| 🔍 **Diagnostics** | Prefix `[DingTalk][LocalImage]` — **no `debug` flag required** |
|
|
58
|
+
Local images (incl. `/mnt`) · `messageImageMd` · LocalImage diagnostics.
|
|
57
59
|
|
|
58
60
|
---
|
|
59
61
|
|
|
@@ -66,7 +68,7 @@ Remote `media` download-then-upload.
|
|
|
66
68
|
| 2026-06-28 | npm `@jeik/dingtalk-connector`; premature finalization fix |
|
|
67
69
|
| 2026-05 | MD images; multi-turn spam; 4.29 empty reply; WS phantom reconnect |
|
|
68
70
|
|
|
69
|
-
Full log: [CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release
|
|
71
|
+
Full log: [CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix48](docs/RELEASE_NOTES_V0.8.21-fix48.md)
|
|
70
72
|
|
|
71
73
|
---
|
|
72
74
|
|
|
@@ -90,20 +92,96 @@ Full log: [CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix38
|
|
|
90
92
|
"clientId": "your-clientId",
|
|
91
93
|
"clientSecret": "your-clientSecret",
|
|
92
94
|
"cardTemplateId": "your-card-template-id.schema",
|
|
93
|
-
"cardContentVar": "content"
|
|
95
|
+
"cardContentVar": "content",
|
|
96
|
+
"messageAnswerCard": true
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
99
|
```
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
### Full config reference (`channels.dingtalk-connector`)
|
|
102
|
+
|
|
103
|
+
Source of truth: `src/config/schema.ts` + `openclaw.plugin.json`.
|
|
104
|
+
Top-level single-bot fields and `accounts.<id>` share the same **shared shape**; missing account fields inherit top-level.
|
|
105
|
+
|
|
106
|
+
#### 1. Enable / credentials / multi-account
|
|
107
|
+
|
|
108
|
+
| Field | Type | Default | Description |
|
|
109
|
+
|-------|------|---------|-------------|
|
|
110
|
+
| `enabled` | boolean | — | Enable this channel |
|
|
111
|
+
| `defaultAccount` | string | — | Default account key (must exist under `accounts`) |
|
|
112
|
+
| `clientId` | string \| number | — | AppKey / Client ID (single-bot top-level) |
|
|
113
|
+
| `clientSecret` | string \| SecretRef | — | AppSecret; plain string or `{ source, provider, id }` |
|
|
114
|
+
| `accounts` | object | — | Multi-bot map; keys are account IDs |
|
|
115
|
+
| `accounts.*.enabled` | boolean | — | Enable this account |
|
|
116
|
+
| `accounts.*.name` | string | — | Display name |
|
|
117
|
+
| `accounts.*.clientId` | string \| number | — | Account Client ID |
|
|
118
|
+
| `accounts.*.clientSecret` | string \| SecretRef | — | Account secret |
|
|
119
|
+
| `accounts.*.chatbotUserId` | string | — | Encrypted bot id for @-mentioning bots in groups (see `[BotIdentity]` logs) |
|
|
120
|
+
| `accounts.*.chatbotCorpId` | string | — | Bot corp id |
|
|
121
|
+
|
|
122
|
+
#### 2. Access policy
|
|
123
|
+
|
|
124
|
+
| Field | Type | Default | Description |
|
|
125
|
+
|-------|------|---------|-------------|
|
|
126
|
+
| `dmPolicy` | `"open"` \| `"pairing"` \| `"allowlist"` | `"open"` | DM policy |
|
|
127
|
+
| `allowFrom` | (string\|number)[] | — | DM allowlist; **required (≥1)** when `dmPolicy=allowlist` |
|
|
128
|
+
| `groupPolicy` | `"open"` \| `"allowlist"` \| `"disabled"` | `"open"` | Group policy |
|
|
129
|
+
| `groupAllowFrom` | (string\|number)[] | — | Group allowlist; **required (≥1)** when `groupPolicy=allowlist` |
|
|
130
|
+
| `requireMention` | boolean | `true` | Require @bot in groups |
|
|
131
|
+
| `groups` | object | — | Per-group overrides (below) |
|
|
132
|
+
|
|
133
|
+
**`groups.<openConversationId>`:**
|
|
134
|
+
|
|
135
|
+
| Field | Type | Description |
|
|
136
|
+
|-------|------|-------------|
|
|
137
|
+
| `enabled` | boolean | Enable this group |
|
|
138
|
+
| `requireMention` | boolean | Override @ requirement |
|
|
139
|
+
| `allowFrom` | (string\|number)[] | Per-group sender allowlist |
|
|
140
|
+
| `systemPrompt` | string | Extra system prompt for this group |
|
|
141
|
+
| `groupSessionScope` | `"group"` \| `"group_sender"` | Session scope for this group |
|
|
142
|
+
| `tools.allow` / `tools.deny` | string[] | Per-group tool policy |
|
|
143
|
+
|
|
144
|
+
#### 3. Session / behavior
|
|
145
|
+
|
|
146
|
+
| Field | Type | Default | Description |
|
|
147
|
+
|-------|------|---------|-------------|
|
|
148
|
+
| `groupSessionScope` | `"group"` \| `"group_sender"` | `"group"` | One session per group vs per group+sender |
|
|
149
|
+
| `separateSessionByConversation` | boolean | `true` | Isolate sessions by conversation |
|
|
150
|
+
| `sharedMemoryAcrossConversations` | boolean | `false` | Share memory across conversations |
|
|
151
|
+
| `historyLimit` | int ≥0 | — | History length cap |
|
|
152
|
+
| `textChunkLimit` | int >0 | — | Text chunk size cap |
|
|
153
|
+
| `mediaMaxMb` | number >0 | — | Media size cap (MB) |
|
|
154
|
+
| `typingIndicator` | boolean | — | Typing indicator |
|
|
155
|
+
| `resolveSenderNames` | boolean | — | Resolve sender display names |
|
|
156
|
+
| `asyncMode` | boolean | — | Async mode |
|
|
157
|
+
| `ackText` | string | — | Inbound ACK text |
|
|
158
|
+
| `systemPrompt` | string | — | Channel-level extra system prompt |
|
|
159
|
+
| `enableMediaUpload` | boolean | — | Enable media upload features |
|
|
160
|
+
| `endpoint` | string | — | Custom DWClient gateway (advanced) |
|
|
161
|
+
| `debug` | boolean | — | Debug logging |
|
|
162
|
+
|
|
163
|
+
#### 4. Tools
|
|
164
|
+
|
|
165
|
+
| Field | Type | Default | Description |
|
|
166
|
+
|-------|------|---------|-------------|
|
|
167
|
+
| `tools.docs` | boolean | true (semantic) | Document tools |
|
|
168
|
+
| `tools.media` | boolean | true (semantic) | Media upload tools |
|
|
169
|
+
|
|
170
|
+
#### 5. Group reply / AI card / answer card / message tool
|
|
171
|
+
|
|
172
|
+
| Field | Type | Default | Description |
|
|
173
|
+
|-------|------|---------|-------------|
|
|
174
|
+
| `groupReplyMode` | `"aicard"` \| `"text"` \| `"markdown"` | `"aicard"` | Group reply style; `text`/`markdown` disables streaming AI card in groups (multi-bot @) |
|
|
175
|
+
| `cardTemplateId` | string | official stream template | Streaming AI Card template id |
|
|
176
|
+
| `cardContentVar` | string | `"msgContent"` | Stream card content variable (process/tool/final) |
|
|
177
|
+
| `answerCard` | boolean | **true** (unless `false`) | **Session-stream** answer card: long answers on a static card; original card freezes at “thinking done” |
|
|
178
|
+
| `answerActToken` | int >0 | **500** | Token threshold: ≤ stay on stream card; > spawn answer card |
|
|
179
|
+
| `answerCardTemplateId` | string | built-in answer template | Static answer card template id |
|
|
180
|
+
| `messageAnswerCard` | boolean | **true** (unless `false`) | **message tool** body via static answer card; `false` = plain text/markdown. **Independent** of `answerCard`; media still normal APIs |
|
|
181
|
+
| `messageImageMd` | boolean | **false** | message tool layout: `false` text then image; `true` merge multi-image+text markdown |
|
|
182
|
+
|
|
183
|
+
> Create/publish card templates in [DingTalk Open Platform](https://open.dingtalk.com/).
|
|
184
|
+
> `answerCard` = conversational stream finalize; `messageAnswerCard` = Agent `message` tool outbound. Do not conflate them.
|
|
107
185
|
|
|
108
186
|
---
|
|
109
187
|
|
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-
|
|
7
|
+
<p><strong>当前发布版:<a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-fix48</strong>(稳定生产可用;一键安装:`npx -y @jeik/dingtalk-connector install`;本地 tgz:`openclaw plugins install ./jeik-dingtalk-connector-0.8.21-fix48.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,19 +23,28 @@
|
|
|
23
23
|
|
|
24
24
|
## 🔧 最近更新
|
|
25
25
|
|
|
26
|
-
### 🚀 v0.8.21-
|
|
26
|
+
### 🚀 v0.8.21-fix48 · 2026-07-21(当前)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
**主题:message 工具默认答案卡(可关)**
|
|
29
29
|
|
|
30
30
|
| | 改动 |
|
|
31
31
|
|--|------|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
32
|
+
| ✨ **`messageAnswerCard`** | **默认 `true`**:message 正文走答案静态卡;`false` 恢复普通 text/markdown |
|
|
33
|
+
| 🔧 **注册** | `schema` + `openclaw.plugin.json`(channels / accounts / uiHints) |
|
|
34
|
+
| 📎 **独立** | 与会话流式 `answerCard` 分开;媒体消息仍普通通道 |
|
|
34
35
|
|
|
35
36
|
```bash
|
|
36
37
|
npx -y @jeik/dingtalk-connector install --force && openclaw gateway restart
|
|
37
38
|
```
|
|
38
39
|
|
|
40
|
+
### 📦 v0.8.21-fix47 · 2026-07-20
|
|
41
|
+
|
|
42
|
+
`file://` 本地图误判远程修复(不再 `fetch failed` 灰图)。
|
|
43
|
+
|
|
44
|
+
### 📦 v0.8.21-fix46 · 2026-07-20
|
|
45
|
+
|
|
46
|
+
本地 MD 图灰图诊断日志(LocalImage / MediaIdTrace)。
|
|
47
|
+
|
|
39
48
|
### 📦 v0.8.21-fix45 · 2026-07-20
|
|
40
49
|
|
|
41
50
|
图 + 下载链接单气泡 · 只认 `![]` · 引用卡片缓存 · residual 误报修复。
|
|
@@ -46,14 +55,7 @@ message 远程 `media` 下载上传。
|
|
|
46
55
|
|
|
47
56
|
### 📦 v0.8.21-fix37 · 2026-07-20
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| | 改动 |
|
|
52
|
-
|--|------|
|
|
53
|
-
| 📷 **MD 本地图** | 支持任意绝对路径(含 **`/mnt` 共享盘**、中文目录);上传失败会拷到 `/tmp` 再试 |
|
|
54
|
-
| 📦 **代码块保护** | 围栏 / 行内 code 里的路径**不会上传**,参数示例不会被改成 `mediaId` |
|
|
55
|
-
| ⚙️ **`messageImageMd`** | 默认 `false`:message 工具**文图分开**;`true` 时仅「多图 + 文字」合并为一条 markdown |
|
|
56
|
-
| 🔍 **诊断日志** | 前缀 `[DingTalk][LocalImage]`,**无需开 debug** 也能看到匹配 / 上传 / 失败原因 |
|
|
58
|
+
本地图(含 `/mnt`)· `messageImageMd` · LocalImage 诊断日志。
|
|
57
59
|
|
|
58
60
|
---
|
|
59
61
|
|
|
@@ -66,7 +68,7 @@ message 远程 `media` 下载上传。
|
|
|
66
68
|
| 2026-06-28 | 上线 npm `@jeik/dingtalk-connector`;过程消息提前定稿修复 |
|
|
67
69
|
| 2026-05 | MD 直链/本地图;多轮刷屏;4.29+ 无文本输出;WebSocket 幻影重连 |
|
|
68
70
|
|
|
69
|
-
完整说明:[CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release
|
|
71
|
+
完整说明:[CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix48](docs/RELEASE_NOTES_V0.8.21-fix48.md)
|
|
70
72
|
|
|
71
73
|
---
|
|
72
74
|
|
|
@@ -96,6 +98,7 @@ message 远程 `media` 下载上传。
|
|
|
96
98
|
"cardTemplateId": "你的卡片模板ID.schema",
|
|
97
99
|
"cardContentVar": "content",
|
|
98
100
|
"messageImageMd": false,
|
|
101
|
+
"messageAnswerCard": true,
|
|
99
102
|
"debug": false
|
|
100
103
|
}
|
|
101
104
|
}
|
|
@@ -129,21 +132,90 @@ message 远程 `media` 下载上传。
|
|
|
129
132
|
}
|
|
130
133
|
```
|
|
131
134
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
|
140
|
-
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
|
|
146
|
-
|
|
135
|
+
### 完整配置字段(`channels.dingtalk-connector`)
|
|
136
|
+
|
|
137
|
+
源码权威:`src/config/schema.ts` + `openclaw.plugin.json`。
|
|
138
|
+
**顶层单账号**与 **`accounts.<id>`** 共用同一批「共享字段」;账号级缺省时继承顶层。
|
|
139
|
+
|
|
140
|
+
#### 1. 启用 / 凭证 / 多账号
|
|
141
|
+
|
|
142
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
143
|
+
|------|------|------|------|
|
|
144
|
+
| `enabled` | boolean | — | 是否启用本渠道 |
|
|
145
|
+
| `defaultAccount` | string | — | 默认账号 key;须存在于 `accounts` |
|
|
146
|
+
| `clientId` | string \| number | — | AppKey / Client ID(单账号顶层) |
|
|
147
|
+
| `clientSecret` | string \| SecretRef | — | AppSecret;支持明文或 `{ source, provider, id }` |
|
|
148
|
+
| `accounts` | object | — | 多账号;key 为账号 ID(如 `main-bot`) |
|
|
149
|
+
| `accounts.*.enabled` | boolean | — | 是否启用该账号 |
|
|
150
|
+
| `accounts.*.name` | string | — | 显示名 |
|
|
151
|
+
| `accounts.*.clientId` | string \| number | — | 该账号 Client ID |
|
|
152
|
+
| `accounts.*.clientSecret` | string \| SecretRef | — | 该账号 Secret |
|
|
153
|
+
| `accounts.*.chatbotUserId` | string | — | 机器人加密 ID(群内 @ 其它 bot 用);见日志 `[BotIdentity] chatbotUserId=...` |
|
|
154
|
+
| `accounts.*.chatbotCorpId` | string | — | 机器人所属 corpId(身份日志) |
|
|
155
|
+
|
|
156
|
+
#### 2. 访问策略
|
|
157
|
+
|
|
158
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
159
|
+
|------|------|------|------|
|
|
160
|
+
| `dmPolicy` | `"open"` \| `"pairing"` \| `"allowlist"` | `"open"` | 单聊策略 |
|
|
161
|
+
| `allowFrom` | (string\|number)[] | — | 单聊白名单;`dmPolicy=allowlist` 时**必填至少 1 项** |
|
|
162
|
+
| `groupPolicy` | `"open"` \| `"allowlist"` \| `"disabled"` | `"open"` | 群聊策略 |
|
|
163
|
+
| `groupAllowFrom` | (string\|number)[] | — | 群白名单;`groupPolicy=allowlist` 时**必填至少 1 项** |
|
|
164
|
+
| `requireMention` | boolean | `true` | 群聊是否需 @机器人 才响应 |
|
|
165
|
+
| `groups` | object | — | 按群 ID 覆盖策略(见下表) |
|
|
166
|
+
|
|
167
|
+
**`groups.<openConversationId>`:**
|
|
168
|
+
|
|
169
|
+
| 字段 | 类型 | 说明 |
|
|
170
|
+
|------|------|------|
|
|
171
|
+
| `enabled` | boolean | 该群是否启用 |
|
|
172
|
+
| `requireMention` | boolean | 覆盖全局 @ 要求 |
|
|
173
|
+
| `allowFrom` | (string\|number)[] | 该群发送者白名单 |
|
|
174
|
+
| `systemPrompt` | string | 该群额外 system 提示 |
|
|
175
|
+
| `groupSessionScope` | `"group"` \| `"group_sender"` | 该群会话维度 |
|
|
176
|
+
| `tools.allow` / `tools.deny` | string[] | 该群工具策略 |
|
|
177
|
+
|
|
178
|
+
#### 3. 会话 / 行为
|
|
179
|
+
|
|
180
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
181
|
+
|------|------|------|------|
|
|
182
|
+
| `groupSessionScope` | `"group"` \| `"group_sender"` | `"group"` | 群会话:整群一份 / 群+发送者一份 |
|
|
183
|
+
| `separateSessionByConversation` | boolean | `true` | 按会话隔离 |
|
|
184
|
+
| `sharedMemoryAcrossConversations` | boolean | `false` | 跨会话共享记忆 |
|
|
185
|
+
| `historyLimit` | int ≥0 | — | 历史条数上限 |
|
|
186
|
+
| `textChunkLimit` | int >0 | — | 文本分片上限 |
|
|
187
|
+
| `mediaMaxMb` | number >0 | — | 媒体大小上限(MB) |
|
|
188
|
+
| `typingIndicator` | boolean | — | 输入中指示 |
|
|
189
|
+
| `resolveSenderNames` | boolean | — | 解析发送者昵称 |
|
|
190
|
+
| `asyncMode` | boolean | — | 异步模式 |
|
|
191
|
+
| `ackText` | string | — | 收到消息时的 ACK 文案 |
|
|
192
|
+
| `systemPrompt` | string | — | 渠道级附加 system 提示 |
|
|
193
|
+
| `enableMediaUpload` | boolean | — | 是否启用媒体上传能力 |
|
|
194
|
+
| `endpoint` | string | — | 自定义 DWClient 网关(高级) |
|
|
195
|
+
| `debug` | boolean | — | 调试日志 |
|
|
196
|
+
|
|
197
|
+
#### 4. 工具
|
|
198
|
+
|
|
199
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
200
|
+
|------|------|------|------|
|
|
201
|
+
| `tools.docs` | boolean | true(语义) | 文档类工具 |
|
|
202
|
+
| `tools.media` | boolean | true(语义) | 媒体上传类工具 |
|
|
203
|
+
|
|
204
|
+
#### 5. 群回复形态 / AI 卡 / 答案卡 / message 工具
|
|
205
|
+
|
|
206
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
207
|
+
|------|------|------|------|
|
|
208
|
+
| `groupReplyMode` | `"aicard"` \| `"text"` \| `"markdown"` | `"aicard"` | 群回复形态;`text`/`markdown` 时群聊不用 AI 流式卡(便于多 bot @) |
|
|
209
|
+
| `cardTemplateId` | string | 官方默认流式模板 | 会话流式 AI Card 模板 ID |
|
|
210
|
+
| `cardContentVar` | string | `"msgContent"` | 流式卡内容变量名(过程/工具/终稿写入此字段) |
|
|
211
|
+
| `answerCard` | boolean | **true**(未配或非 false) | **会话流式**答案卡:长答案另开静态卡,原卡定格「思考完成」 |
|
|
212
|
+
| `answerActToken` | int >0 | **500** | 会话答案卡阈值:估算 token ≤ 此值原卡定稿;> 此值才新建答案卡 |
|
|
213
|
+
| `answerCardTemplateId` | string | 内置答案模板 | 答案静态卡模板 ID(需含内容变量) |
|
|
214
|
+
| `messageAnswerCard` | boolean | **true**(未配或非 false) | **message 工具**正文是否走答案静态卡;`false`=普通 text/markdown。与 `answerCard` **独立**;图/音/视/文件仍普通通道 |
|
|
215
|
+
| `messageImageMd` | boolean | **false** | message 工具图文:`false` 文图分开;`true` 仅多图+文字时合并一条 markdown |
|
|
216
|
+
|
|
217
|
+
> 卡片模板需在[钉钉开放平台](https://open.dingtalk.com/)创建并发布。
|
|
218
|
+
> `answerCard` = 对话流式收尾;`messageAnswerCard` = Agent 调 message 工具外发。不要混为一谈。
|
|
147
219
|
|
|
148
220
|
---
|
|
149
221
|
|
package/dist/entry-bundled.mjs
CHANGED
|
@@ -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-
|
|
26
|
+
const { registerGatewayMethods } = await import("./gateway-methods-DtzGBBiy.mjs");
|
|
27
27
|
registerGatewayMethods(api);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -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-
|
|
4
|
+
import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-DOEBZgXQ.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 {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as registerGatewayMethods } from "./gateway-methods-
|
|
1
|
+
import { t as registerGatewayMethods } from "./gateway-methods-CuqlMAXE.mjs";
|
|
2
2
|
export { registerGatewayMethods };
|
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-
|
|
2
|
-
import { t as registerGatewayMethods } from "./gateway-methods-
|
|
1
|
+
import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-hVrm4xYI.mjs";
|
|
2
|
+
import { t as registerGatewayMethods } from "./gateway-methods-CuqlMAXE.mjs";
|
|
3
3
|
//#region index.ts
|
|
4
4
|
/**
|
|
5
5
|
* 检测同一 plugin id 在多个路径被加载的情况。
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as toLocalPath, d as processLocalImages, f as processRawMediaPaths, h as sendFileProactive, o as hasResidualLocalMdImagesOutsideCode, p as processVideoMarkers } from "./media-
|
|
1
|
+
import { _ as toLocalPath, d as processLocalImages, f as processRawMediaPaths, h as sendFileProactive, o as hasResidualLocalMdImagesOutsideCode, p as processVideoMarkers } from "./media-DnQB2BrL.mjs";
|
|
2
2
|
export { hasResidualLocalMdImagesOutsideCode, processLocalImages, processRawMediaPaths, processVideoMarkers, sendFileProactive, toLocalPath };
|
|
@@ -30,10 +30,18 @@ const IMAGE_EXTENSIONS = /\.(png|jpg|jpeg|gif|bmp|webp|tiff|svg)$/i;
|
|
|
30
30
|
* 注意:旧版只匹配 /tmp|/home|/root 等前缀,**漏掉 /mnt 共享盘**,导致 message 工具 MD 灰图。
|
|
31
31
|
*/
|
|
32
32
|
const LOCAL_IMAGE_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
33
|
-
/**
|
|
33
|
+
/** 本地文件 URI / OpenClaw 媒体前缀(不是 http 远程) */
|
|
34
|
+
function isLocalFileScheme(p) {
|
|
35
|
+
return p.startsWith("file://") || p.startsWith("MEDIA:") || p.startsWith("attachment://");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 是否像远程 URL(禁止当本地路径上传)。
|
|
39
|
+
* **不含** file:// / MEDIA: / attachment://(那些是本地引用)。
|
|
40
|
+
*/
|
|
34
41
|
function looksLikeRemoteUrl(raw) {
|
|
35
42
|
const p = (raw || "").trim();
|
|
36
43
|
if (!p) return false;
|
|
44
|
+
if (isLocalFileScheme(p)) return false;
|
|
37
45
|
if (/^https?:\/\//i.test(p)) return true;
|
|
38
46
|
if (/^[a-z]:\/\//i.test(p) && !/^[a-z]:[\\/][^/\\]/i.test(p)) return true;
|
|
39
47
|
if (p.includes("://")) return true;
|
|
@@ -43,9 +51,9 @@ function looksLikeRemoteUrl(raw) {
|
|
|
43
51
|
function isLocalImageRef(rawPath) {
|
|
44
52
|
const p = (rawPath || "").trim();
|
|
45
53
|
if (!p) return false;
|
|
54
|
+
if (isLocalFileScheme(p)) return true;
|
|
46
55
|
if (looksLikeRemoteUrl(p) || /^https?:\/\//i.test(p)) return false;
|
|
47
56
|
if (p.startsWith("@") && !p.includes("/") && !p.includes("\\")) return false;
|
|
48
|
-
if (p.startsWith("file://") || p.startsWith("MEDIA:") || p.startsWith("attachment://")) return true;
|
|
49
57
|
if (p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p)) {
|
|
50
58
|
if (IMAGE_EXTENSIONS.test(p.split("?")[0] || p)) return true;
|
|
51
59
|
return !p.includes("://");
|
|
@@ -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-
|
|
2
|
+
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-hVrm4xYI.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-
|
|
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-DOEBZgXQ.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";
|
|
@@ -62,7 +62,7 @@ function resolveAgentWorkspaceDir(cfg, agentId) {
|
|
|
62
62
|
* 直接复用 media.ts 权威实现,避免 reply 路径与 message 路径两套逻辑分叉。
|
|
63
63
|
*/
|
|
64
64
|
async function processLocalImages(content, oapiToken, log) {
|
|
65
|
-
const { processLocalImages: impl } = await import("./media-
|
|
65
|
+
const { processLocalImages: impl } = await import("./media-BqpUGCni.mjs");
|
|
66
66
|
return impl(content, oapiToken, log);
|
|
67
67
|
}
|
|
68
68
|
//#endregion
|
|
@@ -581,14 +581,14 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
581
581
|
finalText = await processLocalImages(finalText, oapiToken, log);
|
|
582
582
|
if (beforeImg !== finalText) console.log(`[DingTalk][LocalImage] closeStreaming 图片处理 | ${beforeImg.length}→${finalText.length}`);
|
|
583
583
|
try {
|
|
584
|
-
const { hasResidualLocalMdImagesOutsideCode } = await import("./media-
|
|
584
|
+
const { hasResidualLocalMdImagesOutsideCode } = await import("./media-BqpUGCni.mjs");
|
|
585
585
|
if (hasResidualLocalMdImagesOutsideCode(finalText)) console.warn(`[DingTalk][LocalImage] closeStreaming 定稿后仍有代码块外的本地 MD 图未上传,钉钉可能灰图`);
|
|
586
586
|
} catch {}
|
|
587
587
|
finalText = await processVideoMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
588
588
|
finalText = await processAudioMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
589
589
|
finalText = await uploadAndReplaceFileMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
590
590
|
log.info(`[DingTalk][closeStreaming] 准备调用 processRawMediaPaths`);
|
|
591
|
-
const { processRawMediaPaths } = await import("./media-
|
|
591
|
+
const { processRawMediaPaths } = await import("./media-BqpUGCni.mjs");
|
|
592
592
|
finalText = await processRawMediaPaths(finalText, account.config, oapiToken, log, target);
|
|
593
593
|
log.info(`[DingTalk][closeStreaming] processRawMediaPaths 处理完成`);
|
|
594
594
|
} else log.warn(`[DingTalk][closeStreaming] oapiToken 为空,跳过媒体处理`);
|
|
@@ -744,7 +744,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
744
744
|
const oapiToken = await getOapiAccessToken(account.config);
|
|
745
745
|
if (oapiToken) {
|
|
746
746
|
log.info(`[DingTalk][deliver] 检测到 final 响应,准备处理裸露文件路径`);
|
|
747
|
-
const { processRawMediaPaths } = await import("./media-
|
|
747
|
+
const { processRawMediaPaths } = await import("./media-BqpUGCni.mjs");
|
|
748
748
|
text = await processRawMediaPaths(text, account.config, oapiToken, log, target);
|
|
749
749
|
log.info(`[DingTalk][deliver] 裸露文件路径处理完成`);
|
|
750
750
|
}
|
|
@@ -2184,7 +2184,7 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2184
2184
|
finalText = await processVideoMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2185
2185
|
finalText = await processAudioMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2186
2186
|
finalText = await uploadAndReplaceFileMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2187
|
-
const { processRawMediaPaths } = await import("./media-
|
|
2187
|
+
const { processRawMediaPaths } = await import("./media-BqpUGCni.mjs");
|
|
2188
2188
|
finalText = await processRawMediaPaths(finalText, config, oapiToken, log, mediaTarget);
|
|
2189
2189
|
}
|
|
2190
2190
|
const textToSend = finalText.trim() || "✅ 任务执行完成(无文本输出)";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as logMediaIdTrace, d as processLocalImages, u as processImagesForOutbound, y as uploadMediaToDingTalk } from "./media-
|
|
1
|
+
import { c as logMediaIdTrace, d as processLocalImages, u as processImagesForOutbound, y as uploadMediaToDingTalk } from "./media-DnQB2BrL.mjs";
|
|
2
2
|
import { n as createLoggerFromConfig } from "./logger-mZ9OSbmD.mjs";
|
|
3
3
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
4
4
|
import { i as getOapiAccessToken, r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
|
|
@@ -582,6 +582,28 @@ function prepareMultiBotMentions(params) {
|
|
|
582
582
|
//#endregion
|
|
583
583
|
//#region src/services/messaging.ts
|
|
584
584
|
/**
|
|
585
|
+
* message 工具是否用答案卡(默认开;显式 false 才关)。
|
|
586
|
+
* 与会话流式 answerCard 独立。
|
|
587
|
+
*/
|
|
588
|
+
function isMessageAnswerCardEnabled(config) {
|
|
589
|
+
return config?.messageAnswerCard !== false;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* message 工具正文发送的卡片选项:答案卡模板 + 静态 FINISHED,失败降级普通消息。
|
|
593
|
+
*/
|
|
594
|
+
function messageToolProactiveCardOptions(config) {
|
|
595
|
+
if (!isMessageAnswerCardEnabled(config)) return {
|
|
596
|
+
useAICard: false,
|
|
597
|
+
fallbackToNormal: true
|
|
598
|
+
};
|
|
599
|
+
return {
|
|
600
|
+
useAICard: true,
|
|
601
|
+
fallbackToNormal: true,
|
|
602
|
+
cardTemplateId: typeof config.answerCardTemplateId === "string" && config.answerCardTemplateId.trim() || "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema",
|
|
603
|
+
skipInputingWalk: true
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
585
607
|
* 发送 Markdown 消息
|
|
586
608
|
* 支持 @用户(atUserId)和 @机器人(atDingtalkIds)
|
|
587
609
|
*/
|
|
@@ -848,7 +870,7 @@ async function resolveMediaSourceToLocalFile(mediaUrl, mediaLocalRoots, log) {
|
|
|
848
870
|
cleanup: tmp
|
|
849
871
|
};
|
|
850
872
|
}
|
|
851
|
-
const { toLocalPath } = await import("./media-
|
|
873
|
+
const { toLocalPath } = await import("./media-BqpUGCni.mjs");
|
|
852
874
|
const _fs = await import("fs");
|
|
853
875
|
const _path = await import("path");
|
|
854
876
|
let resolved = toLocalPath(mediaUrl);
|
|
@@ -898,12 +920,13 @@ async function sendTextToDingTalk(params) {
|
|
|
898
920
|
console.warn(`[DingTalk][LocalImage] sendTextToDingTalk 图片处理失败: ${err?.message || err}`);
|
|
899
921
|
}
|
|
900
922
|
const msgType = shouldSendAsMarkdown(text) ? "markdown" : "text";
|
|
901
|
-
|
|
923
|
+
const cardOpts = messageToolProactiveCardOptions(config);
|
|
924
|
+
logMediaIdTrace("sendTextToDingTalk:API前", text, `msgType=${msgType} messageAnswerCard=${isMessageAnswerCardEnabled(config)} useAICard=${cardOpts.useAICard}`, true);
|
|
925
|
+
console.log(`[DingTalk][MessageCard] sendTextToDingTalk | messageAnswerCard=${isMessageAnswerCardEnabled(config)} useAICard=${cardOpts.useAICard} tpl=${cardOpts.cardTemplateId || "-"}`);
|
|
902
926
|
return sendProactive(config, targetParam, text, {
|
|
903
927
|
msgType,
|
|
904
928
|
replyToId,
|
|
905
|
-
|
|
906
|
-
fallbackToNormal: true,
|
|
929
|
+
...cardOpts,
|
|
907
930
|
skipProcessLocalImages: true
|
|
908
931
|
});
|
|
909
932
|
}
|
|
@@ -1000,8 +1023,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1000
1023
|
if (text?.trim()) await sendProactive(config, targetParam, text.trim(), {
|
|
1001
1024
|
msgType: shouldSendAsMarkdown(text) ? "markdown" : "text",
|
|
1002
1025
|
replyToId,
|
|
1003
|
-
|
|
1004
|
-
fallbackToNormal: true
|
|
1026
|
+
...messageToolProactiveCardOptions(config)
|
|
1005
1027
|
});
|
|
1006
1028
|
if (isRemoteHttpUrl(mediaUrl) && mediaType === "image") {
|
|
1007
1029
|
console.log(`[DingTalk][RemoteMedia] 下载失败,尝试 photoURL 直链发送 | ${mediaUrl}`);
|
|
@@ -1041,8 +1063,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1041
1063
|
if (caption) await sendProactive(config, targetParam, caption, {
|
|
1042
1064
|
msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
|
|
1043
1065
|
replyToId,
|
|
1044
|
-
|
|
1045
|
-
fallbackToNormal: true
|
|
1066
|
+
...messageToolProactiveCardOptions(config)
|
|
1046
1067
|
});
|
|
1047
1068
|
if (isRemoteHttpUrl(mediaUrl)) {
|
|
1048
1069
|
console.log(`[DingTalk][RemoteMedia] 再上传失败,photoURL 直链兜底 | ${mediaUrl}`);
|
|
@@ -1068,8 +1089,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1068
1089
|
const result = await sendProactive(config, targetParam, `${caption}\n\n`, {
|
|
1069
1090
|
msgType: "markdown",
|
|
1070
1091
|
replyToId,
|
|
1071
|
-
|
|
1072
|
-
fallbackToNormal: true
|
|
1092
|
+
...messageToolProactiveCardOptions(config)
|
|
1073
1093
|
});
|
|
1074
1094
|
return {
|
|
1075
1095
|
...result,
|
|
@@ -1079,8 +1099,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1079
1099
|
if (caption) await sendProactive(config, targetParam, caption, {
|
|
1080
1100
|
msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
|
|
1081
1101
|
replyToId,
|
|
1082
|
-
|
|
1083
|
-
fallbackToNormal: true
|
|
1102
|
+
...messageToolProactiveCardOptions(config)
|
|
1084
1103
|
});
|
|
1085
1104
|
const result = await sendProactive(config, targetParam, mediaId, {
|
|
1086
1105
|
msgType: "image",
|
|
@@ -1102,8 +1121,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1102
1121
|
await sendProactive(config, targetParam, caption, {
|
|
1103
1122
|
msgType: shouldSendAsMarkdown(caption) ? "markdown" : "text",
|
|
1104
1123
|
replyToId,
|
|
1105
|
-
|
|
1106
|
-
fallbackToNormal: true
|
|
1124
|
+
...messageToolProactiveCardOptions(config)
|
|
1107
1125
|
});
|
|
1108
1126
|
}
|
|
1109
1127
|
const uploadResult = await uploadMediaToDingTalk(resolvedMediaUrl, mediaType, oapiToken, maxSize, log);
|
|
@@ -1121,7 +1139,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1121
1139
|
const fileName = mediaUrl.split("/").filter(Boolean).pop()?.split("?")[0] || "file";
|
|
1122
1140
|
if (mediaType === "video") {
|
|
1123
1141
|
const videoMarker = `[DINGTALK_VIDEO]{"path":"${resolvedMediaUrl}"}[/DINGTALK_VIDEO]`;
|
|
1124
|
-
const { processVideoMarkers } = await import("./media-
|
|
1142
|
+
const { processVideoMarkers } = await import("./media-BqpUGCni.mjs");
|
|
1125
1143
|
await processVideoMarkers(videoMarker, "", config, oapiToken, console, true, targetParam);
|
|
1126
1144
|
return {
|
|
1127
1145
|
ok: true,
|
|
@@ -1140,7 +1158,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1140
1158
|
fileName,
|
|
1141
1159
|
fileType: ext && ext.length < 12 ? ext : "file"
|
|
1142
1160
|
};
|
|
1143
|
-
const { sendFileProactive } = await import("./media-
|
|
1161
|
+
const { sendFileProactive } = await import("./media-BqpUGCni.mjs");
|
|
1144
1162
|
await sendFileProactive(config, targetParam, fileInfo, uploadResult.mediaId, log);
|
|
1145
1163
|
return {
|
|
1146
1164
|
ok: true,
|
|
@@ -1219,12 +1237,13 @@ async function sendProactiveInternal(config, target, content, options) {
|
|
|
1219
1237
|
usedAICard: false
|
|
1220
1238
|
};
|
|
1221
1239
|
}
|
|
1222
|
-
const { msgType = "text", useAICard = true, fallbackToNormal = true, log: externalLog } = options;
|
|
1240
|
+
const { msgType = "text", useAICard = true, fallbackToNormal = true, cardTemplateId, skipInputingWalk = false, log: externalLog } = options;
|
|
1223
1241
|
const isMediaMessage = MEDIA_MSG_TYPES.has(msgType);
|
|
1224
1242
|
if (useAICard && !isMediaMessage) try {
|
|
1225
|
-
const card = await createAICardForTarget(config, target, externalLog);
|
|
1243
|
+
const card = await createAICardForTarget(config, target, externalLog, cardTemplateId);
|
|
1226
1244
|
if (card) {
|
|
1227
|
-
await finishAICard(card, content, config, externalLog);
|
|
1245
|
+
await finishAICard(card, content, config, externalLog, void 0, skipInputingWalk);
|
|
1246
|
+
console.log(`[DingTalk][MessageCard] AI Card 发送成功 | outTrack=${card.cardInstanceId} tpl=${cardTemplateId || "default"} skipInputing=${skipInputingWalk}`);
|
|
1228
1247
|
return {
|
|
1229
1248
|
ok: true,
|
|
1230
1249
|
cardInstanceId: card.cardInstanceId,
|
|
@@ -1236,8 +1255,10 @@ async function sendProactiveInternal(config, target, content, options) {
|
|
|
1236
1255
|
error: "Failed to create AI Card",
|
|
1237
1256
|
usedAICard: false
|
|
1238
1257
|
};
|
|
1258
|
+
console.warn(`[DingTalk][MessageCard] AI Card 创建失败,fallbackToNormal=${fallbackToNormal}`);
|
|
1239
1259
|
} catch (err) {
|
|
1240
1260
|
externalLog?.error?.(`AI Card 发送失败: ${err.message}`);
|
|
1261
|
+
console.warn(`[DingTalk][MessageCard] AI Card 异常: ${err?.message || err},fallbackToNormal=${fallbackToNormal}`);
|
|
1241
1262
|
if (!fallbackToNormal) return {
|
|
1242
1263
|
ok: false,
|
|
1243
1264
|
error: err.message,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { b as __exportAll } from "./media-
|
|
1
|
+
import { b as __exportAll } from "./media-DnQB2BrL.mjs";
|
|
2
2
|
import { a as resolveDingtalkAccount, c as addWildcardAllowFrom, d as hasConfiguredSecretInput, f as normalizeAccountId, l as createDefaultChannelRuntimeState, m as resolveDefaultGroupPolicy, o as resolveDingtalkCredentials, p as resolveAllowlistProviderRuntimeGroupPolicy, r as resolveDefaultDingtalkAccountId, s as DEFAULT_ACCOUNT_ID, t as listDingtalkAccountIds, u as formatDocsLink } from "./accounts-BAzdqkAV.mjs";
|
|
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-
|
|
6
|
+
import { S as finalClean, f as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-DOEBZgXQ.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,6 +102,13 @@ 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(),
|
|
@@ -111,7 +118,7 @@ const DingtalkSharedConfigShape = {
|
|
|
111
118
|
* 规避钉钉流式卡 FINISHED 后仍抖动的官方 bug。默认开启(显式设 false 才关)。 */
|
|
112
119
|
answerCard: z$1.boolean().optional(),
|
|
113
120
|
/** 答案卡触发阈值(token):最终答案估算 token ≤ 此值时直接在原卡定稿(不另建卡),
|
|
114
|
-
* > 此值才新建答案卡。避免简单任务也多一张卡。默认
|
|
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-
|
|
940
|
+
import("./message-handler-DKPtnK0x.mjs"),
|
|
934
941
|
import("./connection-DHHFFNQJ.mjs")
|
|
935
942
|
]);
|
|
936
943
|
const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Release Notes — v0.8.21-fix47(2026-07-20)
|
|
2
|
+
|
|
3
|
+
## 安装 / Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx -y @jeik/dingtalk-connector install --force
|
|
7
|
+
openclaw gateway restart
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## 修复 / Fixed
|
|
11
|
+
|
|
12
|
+
### `file://` MD 本地图灰图
|
|
13
|
+
|
|
14
|
+
**现象(fix46 日志):**
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
扫描 ![]() | #2 kind=http path=file:///mnt/smb/...
|
|
18
|
+
processImagesForOutbound 远程图 | file:///mnt/...
|
|
19
|
+
远程图下载异常 | fetch failed
|
|
20
|
+
localTried=0 remoteTried=3
|
|
21
|
+
API前 mdImgs 仍含 http:file:///...
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**原因:** `looksLikeRemoteUrl` 把任意含 `://` 的字符串当远程;`isLocalImageRef` 又先调用它,`file://` 永远进不了本地上传。
|
|
25
|
+
|
|
26
|
+
**修复:** `file://` / `MEDIA:` / `attachment://` 优先判本地 → `toLocalPath` → 上传 mediaId。
|
|
27
|
+
|
|
28
|
+
裸绝对路径 `/mnt/...`、`/tmp/...` 本来就正常;本版修的是 **带 `file://` 前缀** 的写法。
|
|
29
|
+
|
|
30
|
+
## 升级
|
|
31
|
+
|
|
32
|
+
从 fix46 直接 `--force` 安装。
|
|
@@ -0,0 +1,73 @@
|
|
|
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「完整配置字段」章节(中文 `README.md` / 英文 `README.en.md`),与 `src/config/schema.ts`、`openclaw.plugin.json` 对齐,含:
|
|
50
|
+
|
|
51
|
+
- 凭证 / `accounts` / `chatbotUserId`
|
|
52
|
+
- `dmPolicy` / `groupPolicy` / `requireMention` / `groups.*`
|
|
53
|
+
- 会话:`groupSessionScope` / `separateSessionByConversation` / …
|
|
54
|
+
- 卡片:`groupReplyMode` / `cardTemplateId` / `cardContentVar`
|
|
55
|
+
- 答案卡:`answerCard` / `answerActToken`(默认 500)/ `answerCardTemplateId`
|
|
56
|
+
- message:`messageAnswerCard`(默认 true)/ `messageImageMd`(默认 false)
|
|
57
|
+
|
|
58
|
+
## 日志
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
[DingTalk][MessageCard] sendTextToDingTalk | messageAnswerCard=true useAICard=true tpl=...
|
|
62
|
+
[DingTalk][MessageCard] AI Card 发送成功 | outTrack=card_... skipInputing=true
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 含此前修复
|
|
66
|
+
|
|
67
|
+
- fix47:`file://` 本地图不再误判远程
|
|
68
|
+
- fix46:LocalImage / MediaIdTrace 诊断日志
|
|
69
|
+
- fix45:图+下载同泡、只认 `![]`、CardCache、residual 误报
|
|
70
|
+
|
|
71
|
+
## 升级
|
|
72
|
+
|
|
73
|
+
从任意 fix37+ 直接 `--force` 安装即可。
|
package/openclaw.plugin.json
CHANGED
|
@@ -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": [
|
|
@@ -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": [
|
|
@@ -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;不填用官方默认流式模板。",
|
|
583
|
+
"advanced": true
|
|
584
|
+
},
|
|
585
|
+
"channels.dingtalk-connector.cardContentVar": {
|
|
586
|
+
"label": "Card Content Variable",
|
|
587
|
+
"help": "流式卡内容变量名,默认 msgContent(过程/工具/终稿写入此字段)。",
|
|
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
package/src/config/schema.ts
CHANGED
|
@@ -91,6 +91,13 @@ 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(),
|
|
@@ -100,7 +107,7 @@ const DingtalkSharedConfigShape = {
|
|
|
100
107
|
* 规避钉钉流式卡 FINISHED 后仍抖动的官方 bug。默认开启(显式设 false 才关)。 */
|
|
101
108
|
answerCard: z.boolean().optional(),
|
|
102
109
|
/** 答案卡触发阈值(token):最终答案估算 token ≤ 此值时直接在原卡定稿(不另建卡),
|
|
103
|
-
* > 此值才新建答案卡。避免简单任务也多一张卡。默认
|
|
110
|
+
* > 此值才新建答案卡。避免简单任务也多一张卡。默认 500(见 reply-dispatcher)。 */
|
|
104
111
|
answerActToken: z.number().int().positive().optional(),
|
|
105
112
|
/** 答案专用卡模板 ID:不填用内置默认模板。需是钉钉后台已发布、含 content 变量的卡片模板。 */
|
|
106
113
|
answerCardTemplateId: z.string().optional(),
|
|
@@ -28,18 +28,17 @@ export const IMAGE_EXTENSIONS = /\.(png|jpg|jpeg|gif|bmp|webp|tiff|svg)$/i;
|
|
|
28
28
|
*/
|
|
29
29
|
export const LOCAL_IMAGE_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
30
30
|
|
|
31
|
-
/**
|
|
31
|
+
/** 是否为需上传的本地图引用(与 media.ts 权威实现一致:file:// 必须算本地) */
|
|
32
32
|
export function isLocalImageRef(rawPath: string): boolean {
|
|
33
33
|
const p = (rawPath || "").trim();
|
|
34
34
|
if (!p) return false;
|
|
35
|
-
|
|
36
|
-
if (p.includes("://") && !p.startsWith("file://") && !p.startsWith("MEDIA:") && !p.startsWith("attachment://")) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
if (p.startsWith("@") && !p.includes("/") && !p.includes("\\")) return false;
|
|
35
|
+
// file:// 等本地 scheme 优先(勿被 includes("://") 挡掉)
|
|
40
36
|
if (p.startsWith("file://") || p.startsWith("MEDIA:") || p.startsWith("attachment://")) {
|
|
41
37
|
return true;
|
|
42
38
|
}
|
|
39
|
+
if (/^https?:\/\//i.test(p)) return false;
|
|
40
|
+
if (p.includes("://")) return false;
|
|
41
|
+
if (p.startsWith("@") && !p.includes("/") && !p.includes("\\")) return false;
|
|
43
42
|
if (p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p)) {
|
|
44
43
|
if (IMAGE_EXTENSIONS.test(p.split("?")[0] || p)) return true;
|
|
45
44
|
return !p.includes("://");
|
package/src/services/media.ts
CHANGED
|
@@ -45,11 +45,26 @@ export const IMAGE_EXTENSIONS = /\.(png|jpg|jpeg|gif|bmp|webp|tiff|svg)$/i;
|
|
|
45
45
|
*/
|
|
46
46
|
export const LOCAL_IMAGE_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
47
47
|
|
|
48
|
-
/**
|
|
48
|
+
/** 本地文件 URI / OpenClaw 媒体前缀(不是 http 远程) */
|
|
49
|
+
function isLocalFileScheme(p: string): boolean {
|
|
50
|
+
return (
|
|
51
|
+
p.startsWith("file://") ||
|
|
52
|
+
p.startsWith("MEDIA:") ||
|
|
53
|
+
p.startsWith("attachment://")
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 是否像远程 URL(禁止当本地路径上传)。
|
|
59
|
+
* **不含** file:// / MEDIA: / attachment://(那些是本地引用)。
|
|
60
|
+
*/
|
|
49
61
|
export function looksLikeRemoteUrl(raw: string): boolean {
|
|
50
62
|
const p = (raw || "").trim();
|
|
51
63
|
if (!p) return false;
|
|
64
|
+
// 本地 scheme 优先:否则 file:// 会被 includes("://") 误判成远程 → fetch 失败 → 灰图
|
|
65
|
+
if (isLocalFileScheme(p)) return false;
|
|
52
66
|
if (/^https?:\/\//i.test(p)) return true;
|
|
67
|
+
// 伪协议截断如 p://(Windows 路径被当 URL 的历史坑)
|
|
53
68
|
if (/^[a-z]:\/\//i.test(p) && !/^[a-z]:[\\/][^/\\]/i.test(p)) return true;
|
|
54
69
|
if (p.includes("://")) return true;
|
|
55
70
|
return false;
|
|
@@ -156,12 +171,11 @@ export function protectNonImageOccurrencesOfTargets(
|
|
|
156
171
|
export function isLocalImageRef(rawPath: string): boolean {
|
|
157
172
|
const p = (rawPath || "").trim();
|
|
158
173
|
if (!p) return false;
|
|
174
|
+
// file:// / MEDIA: / attachment:// 一律本地(必须先于 looksLikeRemoteUrl)
|
|
175
|
+
if (isLocalFileScheme(p)) return true;
|
|
159
176
|
// 已是远程 URL 或钉钉 mediaId(@ 开头)→ 不上传
|
|
160
177
|
if (looksLikeRemoteUrl(p) || /^https?:\/\//i.test(p)) return false;
|
|
161
178
|
if (p.startsWith("@") && !p.includes("/") && !p.includes("\\")) return false;
|
|
162
|
-
if (p.startsWith("file://") || p.startsWith("MEDIA:") || p.startsWith("attachment://")) {
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
179
|
// Unix 绝对路径 / Windows 盘符
|
|
166
180
|
if (p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p)) {
|
|
167
181
|
// 有图片后缀,或路径中明显是文件(含扩展名)
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1379
|
+
const card = await createAICardForTarget(
|
|
1380
|
+
config,
|
|
1381
|
+
target,
|
|
1382
|
+
externalLog,
|
|
1383
|
+
cardTemplateId,
|
|
1384
|
+
);
|
|
1334
1385
|
if (card) {
|
|
1335
|
-
|
|
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
|
}
|