@jeik/dingtalk-connector 0.8.27 → 0.8.28
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 +49 -0
- package/README.en.md +28 -35
- package/README.md +28 -43
- package/dist/background-work-Cxb4b-91.mjs +42 -0
- package/dist/card-content-cache-c4Ls96eJ.mjs +2 -0
- package/dist/chunk-upload-PwhogwfE.mjs +2 -0
- package/dist/{common-DE0gd0Jg.mjs → common-BZiVgd3A.mjs} +2 -2
- package/dist/{common-B_A78n8u.mjs → common-DgL28gCZ.mjs} +1 -1
- package/dist/{connection-fc6B4z4G.mjs → connection-CZ7ZoN5_.mjs} +313 -86
- package/dist/entry-bundled.mjs +1 -1
- package/dist/gateway-methods-BkI1gKaC.mjs +2 -0
- package/dist/{gateway-methods--aKsyMs-.mjs → gateway-methods-kNX3qvqw.mjs} +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{message-handler-DQZiKx3i.mjs → message-handler-B1usz1wb.mjs} +238 -122
- package/dist/{messaging-WleKf54h.mjs → messaging-DVS3UKOp.mjs} +6 -2
- package/dist/{runtime-DzZo6Gvk.mjs → runtime-A6G7WAWn.mjs} +3 -3
- package/docs/RELEASE_NOTES_V0.8.28.md +56 -0
- package/package.json +2 -2
- package/src/core/connection.ts +465 -158
- package/src/core/message-handler.ts +173 -86
- package/src/reply-dispatcher.ts +234 -77
- package/src/services/messaging/card.ts +2 -0
- package/src/utils/background-work.ts +63 -0
- package/dist/card-content-cache-Dwd8HICP.mjs +0 -2
- package/dist/chunk-upload-DaLXXZH3.mjs +0 -2
- package/dist/gateway-methods-BQd4E0Sk.mjs +0 -2
- /package/dist/{card-content-cache-CYUzCaQJ.mjs → card-content-cache-bDQFz2u8.mjs} +0 -0
- /package/dist/{chunk-upload-BBQgGtcZ.mjs → chunk-upload-BE71drPg.mjs} +0 -0
- /package/dist/{game-xiyou-CAT0ilSj.mjs → game-xiyou-uwsv23AC.mjs} +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
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-A6G7WAWn.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-DVS3UKOp.mjs";
|
|
7
7
|
import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
|
|
8
8
|
import { c as formatSenderRoles, f as getUserProfile, h as recallEmotionReply, l as getAccessToken, o as formatHiredDateShanghai, r as addEmotionReply, s as formatSenderDisplayLabel, t as DINGTALK_API, u as getOapiAccessToken$1 } from "./utils-legacy-DQ3HeGHh.mjs";
|
|
9
|
-
import "./
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
9
|
+
import { r as trackBackgroundWork } from "./background-work-Cxb4b-91.mjs";
|
|
10
|
+
import "./chunk-upload-BE71drPg.mjs";
|
|
11
|
+
import { a as toLocalPath, i as VIDEO_MARKER_PATTERN, o as uploadMediaToDingTalk, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-BZiVgd3A.mjs";
|
|
12
|
+
import { n as lookupCardContent, t as collectCardLookupIds } from "./card-content-cache-bDQFz2u8.mjs";
|
|
12
13
|
import * as fs from "fs";
|
|
13
14
|
import * as path from "path";
|
|
14
15
|
import * as os from "node:os";
|
|
@@ -261,10 +262,36 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
261
262
|
accountId
|
|
262
263
|
});
|
|
263
264
|
const log = createLoggerFromConfig(account.config, `DingTalk:${accountId}`);
|
|
264
|
-
let currentCardTarget = null;
|
|
265
|
+
let currentCardTarget = preCreatedCard ?? null;
|
|
265
266
|
let accumulatedText = "";
|
|
266
267
|
const deliveredFinalTexts = /* @__PURE__ */ new Set();
|
|
267
268
|
let sessionClosed = false;
|
|
269
|
+
/**
|
|
270
|
+
* 群聊 fail-before-reply 时 OpenClaw 会把用户可见文案收成 NO_REPLY(静默),
|
|
271
|
+
* 但 payload 仍带 rawError/isError。normalize 会在 deliver 前丢掉整包,
|
|
272
|
+
* 导致 closeStreaming 空文本 → answerCard 误定格「思考完成」。
|
|
273
|
+
* 用 onSkip 捕获后写入此处,供 closeStreaming 定稿错误卡。
|
|
274
|
+
*/
|
|
275
|
+
let pendingErrorText = null;
|
|
276
|
+
if (preCreatedCard && !isDirect) registerActiveCard(conversationId, preCreatedCard);
|
|
277
|
+
if (preCreatedCard) log.info(`[DingTalk] 启动即挂接即时建卡 currentCardTarget=${preCreatedCard.cardInstanceId} isDirect=${isDirect}`);
|
|
278
|
+
/** 从 isError/rawError payload 提取中文错误文案(含被静默丢弃的群聊失败) */
|
|
279
|
+
const captureErrorFromPayload = (payload, source) => {
|
|
280
|
+
const p = payload;
|
|
281
|
+
if (!p) return null;
|
|
282
|
+
const raw = readPayloadRawError(p);
|
|
283
|
+
const text = typeof p.text === "string" ? p.text.trim() : "";
|
|
284
|
+
const looksSilent = !text || /^NO_REPLY$/i.test(text) || text.split(/\s+/).every((t) => /^NO_REPLY$/i.test(t));
|
|
285
|
+
let errorText = null;
|
|
286
|
+
if (raw) errorText = matchModelErrorText(raw, { includeCatchAll: true });
|
|
287
|
+
if (!errorText && p.isError && text && !looksSilent) errorText = matchModelErrorText(text, { includeCatchAll: true }) ?? text;
|
|
288
|
+
if (!errorText && (raw || p.isError)) errorText = MODEL_ERROR_GENERIC;
|
|
289
|
+
if (!errorText) return null;
|
|
290
|
+
pendingErrorText = errorText;
|
|
291
|
+
if (!accumulatedText.trim() || looksSilent) accumulatedText = errorText;
|
|
292
|
+
log.info(`[DingTalk][errorCapture] source=${source} len=${errorText.length} preview=${errorText.slice(0, 60)}`);
|
|
293
|
+
return errorText;
|
|
294
|
+
};
|
|
268
295
|
let asyncModeFullResponse = "";
|
|
269
296
|
let currentToolLine = "";
|
|
270
297
|
/**
|
|
@@ -415,10 +442,11 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
415
442
|
}
|
|
416
443
|
}
|
|
417
444
|
};
|
|
418
|
-
|
|
445
|
+
const writePromise = streamWriteChain.then(() => runWrite(force));
|
|
446
|
+
streamWriteChain = writePromise.catch((e) => {
|
|
419
447
|
log.warn(`[DingTalk][stream] 队列任务失败(不中断后续):${e?.message || e}`);
|
|
420
448
|
});
|
|
421
|
-
return
|
|
449
|
+
return writePromise;
|
|
422
450
|
};
|
|
423
451
|
/** 定稿前:取消尾随定时器,强制推送全文并等待队列排空 */
|
|
424
452
|
const flushCardStream = async (content, card) => {
|
|
@@ -559,8 +587,13 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
559
587
|
clearTrailingFlush();
|
|
560
588
|
await streamWriteChain;
|
|
561
589
|
let finalText = finalClean(pickFinalText());
|
|
590
|
+
if (!finalText.trim() && pendingErrorText) {
|
|
591
|
+
finalText = pendingErrorText;
|
|
592
|
+
log.info(`[DingTalk][closeStreaming] 使用 pendingErrorText 定稿(群聊静默失败恢复),len=${finalText.length}`);
|
|
593
|
+
}
|
|
562
594
|
const hadRealAnswer = finalText.trim().length > 0;
|
|
563
|
-
|
|
595
|
+
const isErrorFinal = Boolean(pendingErrorText);
|
|
596
|
+
log.info(`[DingTalk][closeStreaming] 最终答案来源=${finalMarkedText !== null ? "marker[-final-]" : isErrorFinal ? "pendingErrorText" : lastAnswerText ? "非reasoning/累积取长" : "accumulatedText兜底"},长度=${finalText.length},lastAnswerLen=${lastAnswerText.length},accLen=${accumulatedText.length},isErrorFinal=${isErrorFinal}`);
|
|
564
597
|
if (!finalText.trim()) {
|
|
565
598
|
finalText = "✅ 任务执行完成(无文本输出)";
|
|
566
599
|
log.info(`[DingTalk][closeStreaming] 累积文本为空,使用默认提示文案`);
|
|
@@ -602,7 +635,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
602
635
|
}
|
|
603
636
|
} else log.info(`[DingTalk][closeStreaming] 养成系统:onCommandOutput 监听到 ${productsToProcess.size} 个 dws 产品: ${[...productsToProcess].join(", ")}`);
|
|
604
637
|
if (productsToProcess.size > 0) {
|
|
605
|
-
const { GamificationEngine } = await import("./game-xiyou-
|
|
638
|
+
const { GamificationEngine } = await import("./game-xiyou-uwsv23AC.mjs");
|
|
606
639
|
const engine = GamificationEngine.getInstanceForUser(senderId);
|
|
607
640
|
if (engine.isEnabled()) {
|
|
608
641
|
const primaryProduct = [...productsToProcess][0];
|
|
@@ -632,7 +665,15 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
632
665
|
const answerTplId = (account.config?.answerCardTemplateId)?.trim() || "d246b7f5-1783-4e9b-bb46-bef52d63050e.schema";
|
|
633
666
|
const answerActToken = Number(account.config?.answerActToken) || 500;
|
|
634
667
|
const answerTokens = estimateTokens(finalText);
|
|
635
|
-
if (
|
|
668
|
+
if (isErrorFinal) {
|
|
669
|
+
log.info(`[DingTalk][closeStreaming] answerCard 模式:错误定稿原卡(len=${finalText.length}),不建答案卡、不定格思考完成`);
|
|
670
|
+
try {
|
|
671
|
+
await flushCardStream(finalText, cardSnapshot);
|
|
672
|
+
} catch (e) {
|
|
673
|
+
log.warn(`[DingTalk][closeStreaming] 错误终稿 flush 失败(继续 FINISH):${e?.message || e}`);
|
|
674
|
+
}
|
|
675
|
+
await finishAICard(cardSnapshot, finalText, account.config, log, void 0, void 0, conversationId);
|
|
676
|
+
} else if (!hadRealAnswer) {
|
|
636
677
|
log.info(`[DingTalk][closeStreaming] answerCard 模式:无真实答案,仅定格原卡思考完成(不建答案卡)`);
|
|
637
678
|
await finalizeOriginalToDone();
|
|
638
679
|
} else if (answerTokens <= answerActToken) {
|
|
@@ -686,14 +727,66 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
686
727
|
accumulatedText = "";
|
|
687
728
|
}
|
|
688
729
|
};
|
|
730
|
+
/**
|
|
731
|
+
* 统一错误定稿:模型/dispatch 异常时把中文提示写到 AI Card 并 FINISHED。
|
|
732
|
+
* 供 dispatcher onError 与 message-handler catch(同步 throw)共用。
|
|
733
|
+
* 不依赖 OpenClaw 返回的 replyOptions.onError(SDK 根本不导出该字段)。
|
|
734
|
+
*/
|
|
735
|
+
const finalizeDispatchError = async (error, kind = "invoke") => {
|
|
736
|
+
const errorMsg = String(error);
|
|
737
|
+
log.error(`[DingTalk][finalizeDispatchError] kind=${kind} failed: ${errorMsg}`);
|
|
738
|
+
await startStreaming();
|
|
739
|
+
const errorText = matchModelErrorText(errorMsg, { includeCatchAll: true }) ?? "⚠️ 模型请求异常,请稍后重试";
|
|
740
|
+
log.warn(`[DingTalk][finalizeDispatchError] 错误提示: ${errorText.slice(0, 80)}`);
|
|
741
|
+
pendingErrorText = errorText;
|
|
742
|
+
accumulatedText = errorText;
|
|
743
|
+
if (!currentCardTarget && !sessionClosed) if (preCreatedCard) try {
|
|
744
|
+
currentCardTarget = preCreatedCard;
|
|
745
|
+
if (!isDirect) registerActiveCard(conversationId, preCreatedCard);
|
|
746
|
+
log.info(`[DingTalk][finalizeDispatchError] 复用即时建卡显示错误(cardInstanceId=${preCreatedCard.cardInstanceId})`);
|
|
747
|
+
} catch (cardErr) {
|
|
748
|
+
log.warn(`[DingTalk][finalizeDispatchError] 复用即时建卡失败:${cardErr.message},降级发送普通消息`);
|
|
749
|
+
try {
|
|
750
|
+
await sendMessage(account.config, sessionWebhook, accumulatedText, {
|
|
751
|
+
useMarkdown: false,
|
|
752
|
+
log: params.runtime.log
|
|
753
|
+
});
|
|
754
|
+
} catch (sendErr) {
|
|
755
|
+
log.error(`[DingTalk][finalizeDispatchError] 降级发送普通消息失败:${sendErr.message}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
log.warn(`[DingTalk][finalizeDispatchError] 无可用 AI Card,降级发送普通消息`);
|
|
760
|
+
try {
|
|
761
|
+
await sendMessage(account.config, sessionWebhook, accumulatedText, {
|
|
762
|
+
useMarkdown: false,
|
|
763
|
+
log: params.runtime.log
|
|
764
|
+
});
|
|
765
|
+
} catch (sendErr) {
|
|
766
|
+
log.error(`[DingTalk][finalizeDispatchError] 降级发送普通消息失败:${sendErr.message}`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
await closeStreaming();
|
|
770
|
+
};
|
|
689
771
|
const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
|
|
690
772
|
...prefixOptions,
|
|
691
773
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, agentId),
|
|
774
|
+
onSkip: (payload, info) => {
|
|
775
|
+
const raw = readPayloadRawError(payload);
|
|
776
|
+
const isErr = Boolean(payload?.isError);
|
|
777
|
+
if (!raw && !isErr) {
|
|
778
|
+
log.info(`[DingTalk][onSkip] kind=${info?.kind} reason=${info?.reason} 非错误静默(NO_REPLY/empty),跳过错误捕获`);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
log.warn(`[DingTalk][onSkip] kind=${info?.kind} reason=${info?.reason} isError=${isErr} rawLen=${raw.length}(群聊静默失败常见)`);
|
|
782
|
+
captureErrorFromPayload(payload, `onSkip:${info?.kind ?? "?"}:${info?.reason ?? "?"}`);
|
|
783
|
+
},
|
|
692
784
|
onReplyStart: () => {
|
|
693
785
|
log.info(`[DingTalk][onReplyStart] 开始回复,流式 enabled=${streamingEnabled}`);
|
|
694
786
|
deliveredFinalTexts.clear();
|
|
695
787
|
finalMarkedText = null;
|
|
696
788
|
lastAnswerText = "";
|
|
789
|
+
pendingErrorText = null;
|
|
697
790
|
processMarkerLogged = false;
|
|
698
791
|
finalMarkerLogged = false;
|
|
699
792
|
markerSystemActive = false;
|
|
@@ -710,6 +803,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
710
803
|
let text = payload.text ?? "";
|
|
711
804
|
const rawError = readPayloadRawError(payload);
|
|
712
805
|
if (rawError) {
|
|
806
|
+
captureErrorFromPayload(payload, `deliver:${info?.kind ?? "?"}`);
|
|
713
807
|
const matchedErrorText = matchModelErrorText(rawError, { includeCatchAll: true });
|
|
714
808
|
if (matchedErrorText) {
|
|
715
809
|
log.warn(`[DingTalk][deliver] 检测到上游 rawError,替换为中文提示(raw=${rawError.slice(0, 120)})`);
|
|
@@ -720,14 +814,22 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
720
814
|
observeReply(payload.text, payload);
|
|
721
815
|
await startStreaming();
|
|
722
816
|
if (isNonAnswerPayload(payload)) {
|
|
817
|
+
const isModelErrorFinal = Boolean(payload.isError) && info?.kind === "final" && Boolean(text.trim());
|
|
818
|
+
if (isModelErrorFinal) {
|
|
819
|
+
accumulatedText = text;
|
|
820
|
+
log.info(`[DingTalk][deliver] 将上游异常 final 文本存入 accumulatedText(len=${text.length}),防止 onIdle 覆盖`);
|
|
821
|
+
} else if (payload.isError && !accumulatedText.trim() && text.trim()) {
|
|
822
|
+
accumulatedText = text;
|
|
823
|
+
log.info(`[DingTalk][deliver] 将上游异常 payload 文本存入 accumulatedText(len=${text.length}),防止 onIdle 覆盖`);
|
|
824
|
+
}
|
|
723
825
|
if (streamingEnabled && currentCardTarget && !asyncMode && finalMarkedText === null) try {
|
|
724
|
-
await enqueueCardStream(displayClean(text));
|
|
826
|
+
await enqueueCardStream(displayClean(text), { force: isModelErrorFinal });
|
|
725
827
|
} catch (e) {
|
|
726
828
|
if (!isQpsLimitError(e)) log.warn(`[DingTalk][deliver] 状态/错误 payload 写卡失败:${e?.message || e}`);
|
|
727
829
|
}
|
|
728
|
-
if (
|
|
729
|
-
|
|
730
|
-
|
|
830
|
+
if (isModelErrorFinal) {
|
|
831
|
+
log.info(`[DingTalk][deliver] 上游 isError final,主动 closeStreaming 定稿错误卡`);
|
|
832
|
+
await closeStreaming();
|
|
731
833
|
}
|
|
732
834
|
log.info(`[DingTalk][deliver] 非答案 payload(isError=${payload.isError},isStatusNotice=${payload.isStatusNotice}),仅展示不计入最终答案`);
|
|
733
835
|
return;
|
|
@@ -838,40 +940,9 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
838
940
|
}
|
|
839
941
|
},
|
|
840
942
|
onError: async (error, info) => {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
await startStreaming();
|
|
845
|
-
const errorText = matchModelErrorText(errorMsg, { includeCatchAll: true }) ?? "⚠️ 模型请求异常,请稍后重试";
|
|
846
|
-
log.warn(`[DingTalk][onError] 错误提示: ${errorText.slice(0, 80)}`);
|
|
847
|
-
if (!accumulatedText.trim()) accumulatedText = errorText;
|
|
848
|
-
if (!currentCardTarget && !sessionClosed) if (preCreatedCard) try {
|
|
849
|
-
currentCardTarget = preCreatedCard;
|
|
850
|
-
if (!isDirect) registerActiveCard(conversationId, preCreatedCard);
|
|
851
|
-
log.info(`[DingTalk][onError] 复用即时建卡显示错误(cardInstanceId=${preCreatedCard.cardInstanceId})`);
|
|
852
|
-
} catch (cardErr) {
|
|
853
|
-
log.warn(`[DingTalk][onError] 复用即时建卡失败:${cardErr.message},降级发送普通消息`);
|
|
854
|
-
try {
|
|
855
|
-
await sendMessage(account.config, sessionWebhook, accumulatedText, {
|
|
856
|
-
useMarkdown: false,
|
|
857
|
-
log: params.runtime.log
|
|
858
|
-
});
|
|
859
|
-
} catch (sendErr) {
|
|
860
|
-
log.error(`[DingTalk][onError] 降级发送普通消息失败:${sendErr.message}`);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
else {
|
|
864
|
-
log.warn(`[DingTalk][onError] 卡片已关闭且无即时建卡,降级发送普通消息`);
|
|
865
|
-
try {
|
|
866
|
-
await sendMessage(account.config, sessionWebhook, accumulatedText, {
|
|
867
|
-
useMarkdown: false,
|
|
868
|
-
log: params.runtime.log
|
|
869
|
-
});
|
|
870
|
-
} catch (sendErr) {
|
|
871
|
-
log.error(`[DingTalk][onError] 降级发送普通消息失败:${sendErr.message}`);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
await closeStreaming();
|
|
943
|
+
log.error(`[DingTalk][onError] ${info.kind} reply failed: ${String(error)}`);
|
|
944
|
+
params.runtime.error?.(`dingtalk[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
|
|
945
|
+
await finalizeDispatchError(error, info.kind);
|
|
875
946
|
typingCallbacks.onIdle?.();
|
|
876
947
|
},
|
|
877
948
|
onIdle: async () => {
|
|
@@ -905,7 +976,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
905
976
|
currentToolLine = "";
|
|
906
977
|
await startStreaming();
|
|
907
978
|
if (currentCardTarget) try {
|
|
908
|
-
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-
|
|
979
|
+
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-DgL28gCZ.mjs");
|
|
909
980
|
await enqueueCardStream(buildCardContent().replace(FILE_MARKER_PATTERN, "").replace(VIDEO_MARKER_PATTERN, "").replace(AUDIO_MARKER_PATTERN, "").trim(), { force: true });
|
|
910
981
|
} catch (err) {
|
|
911
982
|
if (!isQpsLimitError(err)) log.error(`[DingTalk][onPartialReply] final-marker 刷卡失败:${err.message}`);
|
|
@@ -918,7 +989,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
918
989
|
if (payload.text?.trim()) hasModelStreamText = true;
|
|
919
990
|
currentToolLine = "";
|
|
920
991
|
try {
|
|
921
|
-
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-
|
|
992
|
+
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-DgL28gCZ.mjs");
|
|
922
993
|
await enqueueCardStream(buildCardContent().replace(FILE_MARKER_PATTERN, "").replace(VIDEO_MARKER_PATTERN, "").replace(AUDIO_MARKER_PATTERN, "").trim());
|
|
923
994
|
} catch (err) {
|
|
924
995
|
if (isQpsLimitError(err)) log.warn(`[DingTalk][onPartialReply] QPS 限流,已排队尾随重试 latestLen=${latestCardContent.length}`);
|
|
@@ -957,12 +1028,20 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
957
1028
|
}
|
|
958
1029
|
},
|
|
959
1030
|
markDispatchIdle,
|
|
960
|
-
getAsyncModeResponse: () => asyncModeFullResponse
|
|
1031
|
+
getAsyncModeResponse: () => asyncModeFullResponse,
|
|
1032
|
+
/** 同步 dispatch 异常时由 message-handler catch 调用,统一 finish 错误卡 */
|
|
1033
|
+
finalizeDispatchError
|
|
961
1034
|
};
|
|
962
1035
|
}
|
|
963
1036
|
//#endregion
|
|
964
1037
|
//#region src/core/message-handler.ts
|
|
965
1038
|
/**
|
|
1039
|
+
* 单条会话队列任务最长执行时间。
|
|
1040
|
+
* 503 重试 + 工具循环偶发挂死时,若不超时,后续同会话消息会永远堵在 previousTask 上,
|
|
1041
|
+
* 表现:连发多条「网关没反应 / 不贴表情」(表情在排队 ACK 或 dequeued 后才贴)。
|
|
1042
|
+
*/
|
|
1043
|
+
const SESSION_QUEUE_TASK_TIMEOUT_MS = 180 * 1e3;
|
|
1044
|
+
/**
|
|
966
1045
|
* 会话消息队列管理
|
|
967
1046
|
* 用于确保同一会话+agent的消息按顺序处理,避免并发冲突导致AI返回空响应
|
|
968
1047
|
* 队列键格式:{sessionId}:{agentId}
|
|
@@ -1926,7 +2005,7 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
1926
2005
|
const rawText = content.text || "";
|
|
1927
2006
|
let userContent = normalizeSlashCommand(rawText) || (content.imageUrls.length > 0 ? "请描述这张图片" : "");
|
|
1928
2007
|
try {
|
|
1929
|
-
const { GamificationEngine, isGamificationCommand } = await import("./game-xiyou-
|
|
2008
|
+
const { GamificationEngine, isGamificationCommand } = await import("./game-xiyou-uwsv23AC.mjs");
|
|
1930
2009
|
if (isGamificationCommand(rawText)) {
|
|
1931
2010
|
const engine = GamificationEngine.getInstanceForUser(senderId);
|
|
1932
2011
|
if (rawText.trim().startsWith("/西游") || engine.isEnabled()) {
|
|
@@ -2100,6 +2179,8 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2100
2179
|
log?.warn?.(`Failed to send acknowledgment: ${ackErr?.message || ackErr}`);
|
|
2101
2180
|
}
|
|
2102
2181
|
}
|
|
2182
|
+
let earlyCard = params.preCreatedCard;
|
|
2183
|
+
let finalizeDispatchError;
|
|
2103
2184
|
try {
|
|
2104
2185
|
const core = getDingtalkRuntime();
|
|
2105
2186
|
let finalContent = userContent;
|
|
@@ -2165,7 +2246,6 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2165
2246
|
});
|
|
2166
2247
|
const groupReplyModeForCard = config.groupReplyMode || "aicard";
|
|
2167
2248
|
const streamingEnabledForCard = !(!isDirect && (groupReplyModeForCard === "text" || groupReplyModeForCard === "markdown")) && config.streaming !== false;
|
|
2168
|
-
let earlyCard = params.preCreatedCard;
|
|
2169
2249
|
if (!earlyCard && streamingEnabledForCard && !asyncMode) try {
|
|
2170
2250
|
earlyCard = await createAICardForTarget(config, isDirect ? {
|
|
2171
2251
|
type: "user",
|
|
@@ -2183,7 +2263,7 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2183
2263
|
log.warn(`[DingTalk][earlyCard] 即时建卡失败(降级延迟建卡):${e?.message}`);
|
|
2184
2264
|
earlyCard = void 0;
|
|
2185
2265
|
}
|
|
2186
|
-
const { dispatcher, replyOptions, markDispatchIdle, getAsyncModeResponse } = createDingtalkReplyDispatcher({
|
|
2266
|
+
const { dispatcher, replyOptions, markDispatchIdle, getAsyncModeResponse, finalizeDispatchError: finalizeErr } = createDingtalkReplyDispatcher({
|
|
2187
2267
|
cfg,
|
|
2188
2268
|
agentId: matchedAgentId,
|
|
2189
2269
|
runtime,
|
|
@@ -2196,6 +2276,7 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2196
2276
|
asyncMode,
|
|
2197
2277
|
preCreatedCard: earlyCard
|
|
2198
2278
|
});
|
|
2279
|
+
finalizeDispatchError = finalizeErr;
|
|
2199
2280
|
if (config.clientId) {
|
|
2200
2281
|
const botIdentityHint = `[DingTalk Bot Context] Current bot clientId: ${String(config.clientId)}. When executing \`dws chat message send-by-bot\`, always pass \`--client-id ${String(config.clientId)}\` to ensure messages are sent from the correct bot.`;
|
|
2201
2282
|
finalContent = finalContent ? `${finalContent}\n\n${botIdentityHint}` : botIdentityHint;
|
|
@@ -2265,28 +2346,42 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2265
2346
|
}
|
|
2266
2347
|
} catch (err) {
|
|
2267
2348
|
log?.error?.(`SDK dispatch 失败: ${err.message}`);
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
await
|
|
2271
|
-
} catch (
|
|
2272
|
-
log?.error?.(
|
|
2349
|
+
if (finalizeDispatchError) try {
|
|
2350
|
+
log?.info?.(`[DingTalk] 统一错误定稿:调用 finalizeDispatchError 处理同步异常`);
|
|
2351
|
+
await finalizeDispatchError(err, "invoke");
|
|
2352
|
+
} catch (finalizeErr) {
|
|
2353
|
+
log?.error?.(`[DingTalk] finalizeDispatchError 失败: ${finalizeErr?.message || finalizeErr}`);
|
|
2273
2354
|
}
|
|
2274
|
-
else
|
|
2275
|
-
const
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2355
|
+
else {
|
|
2356
|
+
const errorText = matchModelErrorText(err.message, { includeCatchAll: true }) ?? `⚠️ 抱歉,处理请求时出错: ${err.message}`;
|
|
2357
|
+
let cardSuccess = false;
|
|
2358
|
+
if (earlyCard) try {
|
|
2359
|
+
await finishAICard(earlyCard, errorText, config, log, void 0, void 0, data.conversationId);
|
|
2360
|
+
cardSuccess = true;
|
|
2361
|
+
log?.info?.(`[DingTalk] 极端兜底:finishAICard 错误卡成功`);
|
|
2362
|
+
} catch (cardErr) {
|
|
2363
|
+
log?.error?.(`更新错误卡片失败: ${cardErr.message}`);
|
|
2364
|
+
}
|
|
2365
|
+
if (!cardSuccess) {
|
|
2366
|
+
log?.warn?.(`[DingTalk] 无可用错误定稿路径,走纯文本发送`);
|
|
2367
|
+
try {
|
|
2368
|
+
const token = await getAccessToken(config);
|
|
2369
|
+
const body = {
|
|
2370
|
+
msgtype: "text",
|
|
2371
|
+
text: { content: errorText }
|
|
2372
|
+
};
|
|
2373
|
+
if (!isDirect) body.at = {
|
|
2374
|
+
atUserIds: [senderId],
|
|
2375
|
+
isAtAll: false
|
|
2376
|
+
};
|
|
2377
|
+
await dingtalkHttp.post(sessionWebhook, body, { headers: {
|
|
2378
|
+
"x-acs-dingtalk-access-token": token,
|
|
2379
|
+
"Content-Type": "application/json"
|
|
2380
|
+
} });
|
|
2381
|
+
} catch (fallbackErr) {
|
|
2382
|
+
log?.error?.(`错误消息发送也失败: ${fallbackErr.message}`);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2290
2385
|
}
|
|
2291
2386
|
}
|
|
2292
2387
|
try {
|
|
@@ -2334,54 +2429,33 @@ async function handleDingTalkMessage(params) {
|
|
|
2334
2429
|
const queueKey = `${baseSessionId}:${matchedAgentId}`;
|
|
2335
2430
|
try {
|
|
2336
2431
|
sessionLastActivity.set(queueKey, Date.now());
|
|
2337
|
-
const isQueueBusy = sessionQueues.has(queueKey);
|
|
2338
2432
|
const previousTask = sessionQueues.get(queueKey) || Promise.resolve();
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
});
|
|
2350
|
-
log?.info?.(`[队列] 队列繁忙,已发送普通文本 ACK(groupReplyMode=${groupReplyMode})`);
|
|
2351
|
-
} catch (ackErr) {
|
|
2352
|
-
log?.warn?.(`[队列] 发送普通 ACK 失败: ${ackErr?.message || ackErr}`);
|
|
2433
|
+
const isQueueBusy = sessionQueues.has(queueKey);
|
|
2434
|
+
const cardHolder = {
|
|
2435
|
+
card: void 0,
|
|
2436
|
+
ackDone: !isQueueBusy
|
|
2437
|
+
};
|
|
2438
|
+
const currentTask = previousTask.then(async () => {
|
|
2439
|
+
log?.info?.(`[队列] 开始处理消息,queueKey=${queueKey} wasBusy=${isQueueBusy} timeoutMs=${SESSION_QUEUE_TASK_TIMEOUT_MS}`);
|
|
2440
|
+
if (isQueueBusy && !cardHolder.ackDone) {
|
|
2441
|
+
const waitStart = Date.now();
|
|
2442
|
+
while (!cardHolder.ackDone && Date.now() - waitStart < 2e3) await new Promise((r) => setTimeout(r, 50));
|
|
2353
2443
|
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
} else log?.warn?.(`[队列] 创建排队 ACK Card 失败(返回 null),跳过 ACK`);
|
|
2369
|
-
addEmotionReply(config, data, log).catch((err) => {
|
|
2370
|
-
log?.warn?.(`[队列] 贴排队表情失败: ${err.message}`);
|
|
2371
|
-
});
|
|
2372
|
-
} catch (ackErr) {
|
|
2373
|
-
log?.warn?.(`[队列] 创建排队 ACK Card 异常: ${ackErr?.message || ackErr}`);
|
|
2374
|
-
}
|
|
2444
|
+
let timeoutId;
|
|
2445
|
+
try {
|
|
2446
|
+
await Promise.race([handleDingTalkMessageInternal({
|
|
2447
|
+
...params,
|
|
2448
|
+
preCreatedCard: cardHolder.card,
|
|
2449
|
+
emotionAlreadyAdded: true
|
|
2450
|
+
}), new Promise((_, reject) => {
|
|
2451
|
+
timeoutId = setTimeout(() => {
|
|
2452
|
+
reject(/* @__PURE__ */ new Error(`会话队列任务超时(${SESSION_QUEUE_TASK_TIMEOUT_MS}ms),释放队列以免堵死后续消息`));
|
|
2453
|
+
}, SESSION_QUEUE_TASK_TIMEOUT_MS);
|
|
2454
|
+
})]);
|
|
2455
|
+
log?.info?.(`[队列] 消息处理完成,queueKey=${queueKey}`);
|
|
2456
|
+
} finally {
|
|
2457
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
2375
2458
|
}
|
|
2376
|
-
}
|
|
2377
|
-
const currentTask = previousTask.then(async () => {
|
|
2378
|
-
log?.info?.(`[队列] 开始处理消息,queueKey=${queueKey}`);
|
|
2379
|
-
await handleDingTalkMessageInternal({
|
|
2380
|
-
...params,
|
|
2381
|
-
preCreatedCard,
|
|
2382
|
-
emotionAlreadyAdded: isQueueBusy
|
|
2383
|
-
});
|
|
2384
|
-
log?.info?.(`[队列] 消息处理完成,queueKey=${queueKey}`);
|
|
2385
2459
|
}).catch((err) => {
|
|
2386
2460
|
log?.error?.(`[队列] 消息处理异常,queueKey=${queueKey}, error=${err.message}`);
|
|
2387
2461
|
}).finally(() => {
|
|
@@ -2391,9 +2465,51 @@ async function handleDingTalkMessage(params) {
|
|
|
2391
2465
|
}
|
|
2392
2466
|
});
|
|
2393
2467
|
sessionQueues.set(queueKey, currentTask);
|
|
2468
|
+
trackBackgroundWork(currentTask, queueKey);
|
|
2469
|
+
log?.info?.(`[队列] 已登记 queueKey=${queueKey} busy=${isQueueBusy} depth≈pending`);
|
|
2470
|
+
addEmotionReply(config, data, log).catch((err) => {
|
|
2471
|
+
log?.warn?.(`[队列] 即时贴表情失败: ${err?.message || err}`);
|
|
2472
|
+
});
|
|
2473
|
+
if (isQueueBusy) {
|
|
2474
|
+
const ackPhrases = QUEUE_BUSY_ACK_PHRASES;
|
|
2475
|
+
const ackText = ackPhrases[Math.floor(Math.random() * ackPhrases.length)];
|
|
2476
|
+
const groupReplyMode = config.groupReplyMode || "aicard";
|
|
2477
|
+
const skipAICard = !isDirect && (groupReplyMode === "text" || groupReplyMode === "markdown");
|
|
2478
|
+
(async () => {
|
|
2479
|
+
try {
|
|
2480
|
+
if (skipAICard) {
|
|
2481
|
+
await sendProactive(config, { openConversationId: data.conversationId }, ackText, {
|
|
2482
|
+
msgType: "text",
|
|
2483
|
+
useAICard: false,
|
|
2484
|
+
fallbackToNormal: true
|
|
2485
|
+
});
|
|
2486
|
+
log?.info?.(`[队列] 队列繁忙,已发送普通文本 ACK(groupReplyMode=${groupReplyMode})`);
|
|
2487
|
+
} else {
|
|
2488
|
+
const card = await createAICardForTarget(config, isDirect ? {
|
|
2489
|
+
type: "user",
|
|
2490
|
+
userId: senderId
|
|
2491
|
+
} : {
|
|
2492
|
+
type: "group",
|
|
2493
|
+
openConversationId: data.conversationId
|
|
2494
|
+
}, log);
|
|
2495
|
+
if (card) {
|
|
2496
|
+
await streamAICard(card, ackText, false, config, log);
|
|
2497
|
+
cardHolder.card = card;
|
|
2498
|
+
log?.info?.(`[队列] 队列繁忙,已创建排队 ACK Card,cardInstanceId=${card.cardInstanceId}`);
|
|
2499
|
+
} else log?.warn?.(`[队列] 创建排队 ACK Card 失败(返回 null)`);
|
|
2500
|
+
}
|
|
2501
|
+
} catch (ackErr) {
|
|
2502
|
+
log?.warn?.(`[队列] 繁忙 ACK 异常: ${ackErr?.message || ackErr}`);
|
|
2503
|
+
} finally {
|
|
2504
|
+
cardHolder.ackDone = true;
|
|
2505
|
+
}
|
|
2506
|
+
})();
|
|
2507
|
+
}
|
|
2394
2508
|
} catch (err) {
|
|
2395
2509
|
log?.error?.(`[队列] 队列管理异常,直接处理: ${err.message}`);
|
|
2396
|
-
handleDingTalkMessageInternal(params)
|
|
2510
|
+
trackBackgroundWork(handleDingTalkMessageInternal(params).catch((e) => {
|
|
2511
|
+
log?.error?.(`[队列] 直接处理也失败: ${e.message}`);
|
|
2512
|
+
}), "queue-fallback");
|
|
2397
2513
|
}
|
|
2398
2514
|
}
|
|
2399
2515
|
//#endregion
|
|
@@ -371,7 +371,7 @@ async function finishAICard(card, content, config, log, contentVar, skipInputing
|
|
|
371
371
|
const fixedContent = ensureTableBlankLines(cleanContent);
|
|
372
372
|
log?.info?.(`[DingTalk][AICard] 开始 finish(一次性定稿,无流式回放),最终内容长度=${fixedContent.length}`);
|
|
373
373
|
try {
|
|
374
|
-
const { rememberCardContent } = await import("./card-content-cache-
|
|
374
|
+
const { rememberCardContent } = await import("./card-content-cache-c4Ls96eJ.mjs");
|
|
375
375
|
rememberCardContent({
|
|
376
376
|
text: fixedContent,
|
|
377
377
|
outTrackId: card.cardInstanceId,
|
|
@@ -429,8 +429,12 @@ async function finishAICard(card, content, config, log, contentVar, skipInputing
|
|
|
429
429
|
return;
|
|
430
430
|
} catch (retryErr) {
|
|
431
431
|
log?.error?.(`[DingTalk][AICard] FINISHED 重试失败:${retryErr.message}`);
|
|
432
|
+
throw retryErr;
|
|
432
433
|
}
|
|
433
|
-
} else
|
|
434
|
+
} else {
|
|
435
|
+
log?.error?.(`[DingTalk][AICard] FINISHED 更新失败:${err.message}`);
|
|
436
|
+
throw err;
|
|
437
|
+
}
|
|
434
438
|
}
|
|
435
439
|
}
|
|
436
440
|
//#endregion
|
|
@@ -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-
|
|
6
|
+
import { S as finalClean, f as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-DVS3UKOp.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
|
|
@@ -955,8 +955,8 @@ async function monitorDingtalkProvider(opts = {}) {
|
|
|
955
955
|
const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
|
|
956
956
|
const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
|
|
957
957
|
import("./accounts-BQptOmgB.mjs"),
|
|
958
|
-
import("./message-handler-
|
|
959
|
-
import("./connection-
|
|
958
|
+
import("./message-handler-B1usz1wb.mjs"),
|
|
959
|
+
import("./connection-CZ7ZoN5_.mjs")
|
|
960
960
|
]);
|
|
961
961
|
const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
|
|
962
962
|
const { handleDingTalkMessage } = monitorAccountModule;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Release Notes · v0.8.28
|
|
2
|
+
|
|
3
|
+
**发布日期:** 2026-07-25
|
|
4
|
+
**npm:** `@jeik/dingtalk-connector@0.8.28`
|
|
5
|
+
**定位:** 基于官方 **0.8.24** 的社区增强正式版
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一句话
|
|
10
|
+
|
|
11
|
+
连接更稳、消息更少丢、模型出错时提示更清楚;官方 0.8.24 的长连接改进已吸收,社区图片/答案卡等增强继续保留。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 基于官方 0.8.24 做了什么
|
|
16
|
+
|
|
17
|
+
- 吸收官方对长连接的稳定性改进(少假死、少误重连)
|
|
18
|
+
- 长任务处理期间连接更不容易被中途打断
|
|
19
|
+
- 日志更干净,方便运维判断「是不是真坏了」
|
|
20
|
+
|
|
21
|
+
未合入:官方给其他插件调用的「卡片开放接口」(对话主流程不需要)。
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 社区额外优化(效果)
|
|
26
|
+
|
|
27
|
+
1. **真正能收消息才显示已连接** — 减少「显示在线、发了没反应」
|
|
28
|
+
2. **连发 / 错误后 / 刚回完再发** — 消息进网关的成功率明显提高
|
|
29
|
+
3. **上一条还在跑时再发** — 更容易马上看到排队提示和思考中表情
|
|
30
|
+
4. **模型 503、欠费、无可用线路等** — 更容易定格成中文错误说明,少卡转圈
|
|
31
|
+
5. **原有社区能力** — 大段答案独立卡片、图片全路径、中文错误映射等均保留
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 安装
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx -y @jeik/dingtalk-connector@0.8.28 install --force
|
|
39
|
+
openclaw gateway restart
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
或:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
openclaw plugins install @jeik/dingtalk-connector@0.8.28 --force
|
|
46
|
+
openclaw gateway restart
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 建议验收
|
|
52
|
+
|
|
53
|
+
1. 启动后日志出现「订阅已生效 / registered」类成功信息后再发消息
|
|
54
|
+
2. 正常对话:先「召唤大模型」再流式/答案卡
|
|
55
|
+
3. 故意用故障模型:应定格中文错误,而不是一直转圈
|
|
56
|
+
4. 任务进行中再发一条:应较快出现排队/处理中反馈
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeik/dingtalk-connector",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "OpenClaw DingTalk channel plugin (
|
|
3
|
+
"version": "0.8.28",
|
|
4
|
+
"description": "OpenClaw DingTalk channel plugin (community) | 钉钉 OpenClaw 社区增强版(基于官方 0.8.24,长连接与消息体验增强)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|