@yaoyuanchao/dingtalk 1.3.2 → 1.3.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaoyuanchao/dingtalk",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "type": "module",
5
5
  "description": "DingTalk channel plugin for Clawdbot with Stream Mode support",
6
6
  "license": "MIT",
package/src/api.ts CHANGED
@@ -93,12 +93,16 @@ export async function getDingTalkAccessToken(clientId: string, clientSecret: str
93
93
  export async function sendViaSessionWebhook(
94
94
  sessionWebhook: string,
95
95
  text: string,
96
- ): Promise<{ ok: boolean }> {
96
+ ): Promise<{ ok: boolean; errcode?: number; errmsg?: string }> {
97
97
  const res = await jsonPost(sessionWebhook, {
98
98
  msgtype: "text",
99
99
  text: { content: text },
100
100
  });
101
- return { ok: res?.errcode === 0 || !res?.errcode };
101
+ const ok = res?.errcode === 0 || !res?.errcode;
102
+ if (!ok) {
103
+ console.warn(`[dingtalk] SessionWebhook text error: errcode=${res?.errcode}, errmsg=${res?.errmsg}`);
104
+ }
105
+ return { ok, errcode: res?.errcode, errmsg: res?.errmsg };
102
106
  }
103
107
 
104
108
  /** Send markdown via sessionWebhook */
@@ -106,12 +110,16 @@ export async function sendMarkdownViaSessionWebhook(
106
110
  sessionWebhook: string,
107
111
  title: string,
108
112
  text: string,
109
- ): Promise<{ ok: boolean }> {
113
+ ): Promise<{ ok: boolean; errcode?: number; errmsg?: string }> {
110
114
  const res = await jsonPost(sessionWebhook, {
111
115
  msgtype: "markdown",
112
116
  markdown: { title, text },
113
117
  });
114
- return { ok: res?.errcode === 0 || !res?.errcode };
118
+ const ok = res?.errcode === 0 || !res?.errcode;
119
+ if (!ok) {
120
+ console.warn(`[dingtalk] SessionWebhook markdown error: errcode=${res?.errcode}, errmsg=${res?.errmsg}`);
121
+ }
122
+ return { ok, errcode: res?.errcode, errmsg: res?.errmsg };
115
123
  }
116
124
 
117
125
  /** Send image via sessionWebhook using markdown format */
package/src/channel.ts CHANGED
@@ -4,6 +4,18 @@ import { startDingTalkMonitor } from './monitor.js';
4
4
  import { sendDingTalkRestMessage } from './api.js';
5
5
  import { probeDingTalk } from './probe.js';
6
6
 
7
+ /**
8
+ * Parse outbound `to` address, stripping optional channel prefix.
9
+ * Handles both "dm:id" and "dingtalk:dm:id" formats.
10
+ */
11
+ function parseOutboundTo(to: string): { type: string; id: string } {
12
+ const parts = to.split(':');
13
+ if (parts[0] === 'dingtalk' && parts.length > 2) {
14
+ parts.shift();
15
+ }
16
+ return { type: parts[0], id: parts.slice(1).join(':') };
17
+ }
18
+
7
19
  export const dingtalkPlugin = {
8
20
  id: 'dingtalk',
9
21
 
@@ -166,7 +178,7 @@ export const dingtalkPlugin = {
166
178
 
167
179
  async sendText({ to, text, accountId, cfg }) {
168
180
  const account = resolveDingTalkAccount({ cfg, accountId });
169
- const [type, id] = to.split(':');
181
+ const { type, id } = parseOutboundTo(to);
170
182
 
171
183
  if (type === 'dm') {
172
184
  await sendDingTalkRestMessage({
@@ -204,7 +216,7 @@ export const dingtalkPlugin = {
204
216
  }
205
217
 
206
218
  const account = resolveDingTalkAccount({ cfg, accountId });
207
- const [type, id] = to.split(':');
219
+ const { type, id } = parseOutboundTo(to);
208
220
 
209
221
  // Build text message with image URL as markdown image
210
222
  const imageMarkdown = `![image](${mediaUrl})`;
package/src/monitor.ts CHANGED
@@ -52,6 +52,11 @@ export async function startDingTalkMonitor(ctx: DingTalkMonitorContext): Promise
52
52
  });
53
53
 
54
54
  client.registerCallbackListener(TOPIC_ROBOT, async (downstream: any) => {
55
+ // Immediately ACK to prevent DingTalk from retrying (60s timeout)
56
+ try {
57
+ client.socketResponse(downstream.headers.messageId, { status: 'SUCCESS' });
58
+ } catch (_) { /* best-effort ACK */ }
59
+
55
60
  try {
56
61
  const data: DingTalkRobotMessage = typeof downstream.data === "string"
57
62
  ? JSON.parse(downstream.data) : downstream.data;
@@ -577,10 +582,13 @@ async function processInboundMessage(
577
582
  cfg: actualCfg,
578
583
  dispatcherOptions: {
579
584
  deliver: async (payload: any) => {
585
+ log?.info?.("[dingtalk] Deliver payload keys: " + Object.keys(payload || {}).join(',') + " text?=" + (typeof payload?.text) + " markdown?=" + (typeof payload?.markdown));
580
586
  const textToSend = resolveDeliverText(payload, log);
581
587
  if (textToSend) {
582
588
  await deliverReply(replyTarget, textToSend, log);
583
589
  setStatus?.({ lastOutboundAt: Date.now() });
590
+ } else {
591
+ log?.info?.("[dingtalk] Deliver: no text resolved from payload");
584
592
  }
585
593
  },
586
594
  onError: (err: any) => {
@@ -680,8 +688,12 @@ async function dispatchWithFullPipeline(params: {
680
688
  responsePrefix: '',
681
689
  deliver: async (payload: any) => {
682
690
  try {
691
+ log?.info?.("[dingtalk] Pipeline deliver payload keys: " + Object.keys(payload || {}).join(',') + " text?=" + (typeof payload?.text) + " markdown?=" + (typeof payload?.markdown));
683
692
  const textToSend = resolveDeliverText(payload, log);
684
- if (!textToSend) return { ok: true };
693
+ if (!textToSend) {
694
+ log?.info?.("[dingtalk] Pipeline deliver: no text resolved from payload");
695
+ return { ok: true };
696
+ }
685
697
  await deliverReply(replyTarget, textToSend, log);
686
698
  setStatus?.({ lastOutboundAt: Date.now() });
687
699
  return { ok: true };
@@ -709,7 +721,15 @@ async function dispatchWithFullPipeline(params: {
709
721
  * We merge them into the text as markdown image syntax so DingTalk can render them.
710
722
  */
711
723
  function resolveDeliverText(payload: any, log?: any): string | undefined {
712
- let text = payload.markdown || payload.text;
724
+ // payload.markdown may be a boolean flag (not the actual text), so check type
725
+ let text = (typeof payload.markdown === 'string' && payload.markdown) || payload.text;
726
+
727
+ // Guard: ensure text is a string (platform might send unexpected types)
728
+ if (text != null && typeof text !== 'string') {
729
+ log?.info?.("[dingtalk] Deliver payload has non-string text type=" + typeof text + ", payload keys=" + Object.keys(payload).join(','));
730
+ text = String(text);
731
+ }
732
+
713
733
  const mediaUrl = payload.mediaUrl || payload.media || payload.imageUrl || payload.image;
714
734
 
715
735
  if (mediaUrl && typeof mediaUrl === 'string' && mediaUrl.startsWith('http')) {
@@ -762,13 +782,17 @@ async function deliverReply(target: any, text: string, log?: any): Promise<void>
762
782
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
763
783
  try {
764
784
  log?.info?.("[dingtalk] Using sessionWebhook (attempt " + attempt + "/" + maxRetries + "), format=" + messageFormat);
765
- log?.info?.("[dingtalk] Sending text: " + chunk.substring(0, 200));
785
+ log?.info?.("[dingtalk] Sending text (" + chunk.length + " chars): " + chunk.substring(0, 200));
786
+ let sendResult: { ok: boolean; errcode?: number; errmsg?: string };
766
787
  if (isMarkdown) {
767
- await sendMarkdownViaSessionWebhook(target.sessionWebhook, "Reply", chunk);
788
+ sendResult = await sendMarkdownViaSessionWebhook(target.sessionWebhook, "Reply", chunk);
768
789
  } else {
769
- await sendViaSessionWebhook(target.sessionWebhook, chunk);
790
+ sendResult = await sendViaSessionWebhook(target.sessionWebhook, chunk);
791
+ }
792
+ if (!sendResult.ok) {
793
+ throw new Error(`SessionWebhook rejected: errcode=${sendResult.errcode}, errmsg=${sendResult.errmsg}`);
770
794
  }
771
- log?.info?.("[dingtalk] SessionWebhook send OK");
795
+ log?.info?.("[dingtalk] SessionWebhook send OK (errcode=" + (sendResult.errcode ?? 0) + ")");
772
796
  webhookSuccess = true;
773
797
  break;
774
798
  } catch (err) {