@yaoyuanchao/dingtalk 1.3.4 → 1.3.6

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,46 @@ 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
+ ## [1.3.6] - 2026-01-28
9
+
10
+ ### Fixed
11
+
12
+ - **Stream ACK method name** — corrected `socketResponse()` to `socketCallBackResponse()` (the actual SDK method); previous typo caused ACK to silently fail, triggering DingTalk 60-second retry
13
+ - **Audio message handling** — skip .amr file download when DingTalk ASR recognition text is available; prevents agent from being confused by audio attachment and trying Whisper instead of reading the already-transcribed text
14
+
15
+ ## [1.3.5] - 2026-01-28
16
+
17
+ ### Fixed
18
+
19
+ - **Outbound `to` parameter parsing** — bare userId (no `dm:` prefix) now correctly treated as DM target; previously silently dropped with ok:true
20
+ - **SessionWebhook response validation** — `sendViaSessionWebhook()` and `sendMarkdownViaSessionWebhook()` now return errcode/errmsg and check `.ok`; failures trigger REST API fallback instead of being silently ignored
21
+ - **Stream ACK timing** — immediately call `socketResponse()` on message receipt to prevent DingTalk 60-second retry timeout; previously awaited full AI processing before ACK
22
+ - **`resolveDeliverText()` type safety** — check `typeof payload.markdown === 'string'` to avoid treating boolean flags as text content
23
+
24
+ ### Changed
25
+
26
+ - **`parseOutboundTo()` enhanced** — handles `"dm:id"`, `"group:id"`, `"dingtalk:dm:id"`, `"dingtalk:group:id"`, and bare `"id"` (defaults to DM)
27
+ - **`deliverReply()` error propagation** — throws on sessionWebhook rejection to trigger retry + REST API fallback
28
+ - **Media URL merging** — `resolveDeliverText()` merges `payload.mediaUrl`/`payload.imageUrl` into text as markdown image syntax
29
+ - **Webhook functions** return `{ ok, errcode, errmsg }` for proper error inspection
30
+
31
+ ## [1.3.0] - 2026-01-28
32
+
33
+ ### Added
34
+
35
+ - **Full SDK Pipeline** — runtime feature detection for `dispatchReplyFromConfig` with 9-step SDK integration (routing, session, envelope, dispatch)
36
+ - **Media support** — image download via `downloadPicture()`, audio/video/file recognition via `downloadMediaFile()`
37
+ - **Smart Markdown detection** — `messageFormat: 'auto'` option with regex-based content detection
38
+ - **Thinking indicator** — `showThinking` config option sends "正在思考..." before AI processing
39
+ - **Activity recording** — `runtime.channel.activity.record()` calls for start/stop/message events
40
+ - **`cleanupOldMedia()`** — generalized media cleanup (replaces `cleanupOldPictures`)
41
+
42
+ ### Changed
43
+
44
+ - **Message extraction refactored** — `extractMessageContent()` switch-case structure for text/richText/picture/audio/video/file
45
+ - **Config schema** — added `showThinking`, `messageFormat: 'auto'` option
46
+ - **`sendMedia()` outbound** — uses markdown image syntax instead of plain URL text
47
+
8
48
  ## [1.2.0] - 2026-01-28
9
49
 
10
50
  ### 🎉 Major Features - Official Plugin Release
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaoyuanchao/dingtalk",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "type": "module",
5
5
  "description": "DingTalk channel plugin for Clawdbot with Stream Mode support",
6
6
  "license": "MIT",
package/src/channel.ts CHANGED
@@ -6,14 +6,21 @@ import { probeDingTalk } from './probe.js';
6
6
 
7
7
  /**
8
8
  * Parse outbound `to` address, stripping optional channel prefix.
9
- * Handles both "dm:id" and "dingtalk:dm:id" formats.
9
+ * Handles: "dm:id", "group:id", "dingtalk:dm:id", "dingtalk:group:id",
10
+ * and bare "id" (treated as DM userId).
10
11
  */
11
12
  function parseOutboundTo(to: string): { type: string; id: string } {
12
13
  const parts = to.split(':');
14
+ // Strip channel prefix: "dingtalk:dm:id" → "dm:id"
13
15
  if (parts[0] === 'dingtalk' && parts.length > 2) {
14
16
  parts.shift();
15
17
  }
16
- return { type: parts[0], id: parts.slice(1).join(':') };
18
+ // Known types
19
+ if (parts[0] === 'dm' || parts[0] === 'group') {
20
+ return { type: parts[0], id: parts.slice(1).join(':') };
21
+ }
22
+ // Bare ID (no type prefix) — treat as DM userId
23
+ return { type: 'dm', id: to };
17
24
  }
18
25
 
19
26
  export const dingtalkPlugin = {
package/src/monitor.ts CHANGED
@@ -53,8 +53,9 @@ export async function startDingTalkMonitor(ctx: DingTalkMonitorContext): Promise
53
53
 
54
54
  client.registerCallbackListener(TOPIC_ROBOT, async (downstream: any) => {
55
55
  // Immediately ACK to prevent DingTalk from retrying (60s timeout)
56
+ // SDK method is socketCallBackResponse, not socketResponse
56
57
  try {
57
- client.socketResponse(downstream.headers.messageId, { status: 'SUCCESS' });
58
+ client.socketCallBackResponse(downstream.headers.messageId, { status: 'SUCCESS' });
58
59
  } catch (_) { /* best-effort ACK */ }
59
60
 
60
61
  try {
@@ -318,11 +319,16 @@ async function processInboundMessage(
318
319
  // Extract message content using structured extractor
319
320
  const extracted = await extractMessageContent(msg, account, log);
320
321
 
321
- // Download media if present (picture/audio/video/file)
322
+ // Download media if present (picture/video/file — but skip audio when ASR text exists)
322
323
  let mediaPath: string | undefined;
323
324
  let mediaType: string | undefined;
324
325
 
325
- if (extracted.mediaDownloadCode && account.clientId && account.clientSecret) {
326
+ // For audio messages with successful ASR recognition, use the text directly
327
+ // and skip downloading the .amr file (which would confuse the agent into
328
+ // trying Whisper instead of reading the already-transcribed text).
329
+ const skipMediaDownload = extracted.messageType === 'audio' && !!extracted.text;
330
+
331
+ if (!skipMediaDownload && extracted.mediaDownloadCode && account.clientId && account.clientSecret) {
326
332
  const robotCode = account.robotCode || account.clientId;
327
333
  try {
328
334
  const result = await downloadMediaFile(
@@ -342,6 +348,8 @@ async function processInboundMessage(
342
348
  } catch (err) {
343
349
  log?.warn?.(`[dingtalk] Media download error: ${err}`);
344
350
  }
351
+ } else if (skipMediaDownload) {
352
+ log?.info?.("[dingtalk] Audio ASR text available, skipping .amr download");
345
353
  }
346
354
 
347
355
  let rawBody = extracted.text;