@jeik/dingtalk-connector 0.8.27 → 0.8.29
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 +48 -0
- package/README.en.md +40 -82
- package/README.md +50 -93
- package/dist/background-work-DkaSDqWk.mjs +53 -0
- package/dist/card-content-cache-C7-XHhHw.mjs +2 -0
- package/dist/{card-content-cache-CYUzCaQJ.mjs → card-content-cache-DT_OYThh.mjs} +15 -1
- package/dist/chunk-upload-BKM1y4Ba.mjs +2 -0
- package/dist/{chunk-upload-BBQgGtcZ.mjs → chunk-upload-CIU5G_ay.mjs} +1 -1
- package/dist/{common-DE0gd0Jg.mjs → common-BfIYs1yC.mjs} +3 -3
- package/dist/{common-B_A78n8u.mjs → common-C0Oaf0z-.mjs} +1 -1
- package/dist/connection-obtrPkj4.mjs +641 -0
- package/dist/entry-bundled.mjs +1 -1
- package/dist/{gateway-methods--aKsyMs-.mjs → gateway-methods-BdcpfxEZ.mjs} +1 -1
- package/dist/gateway-methods-DQ6TwOAr.mjs +2 -0
- package/dist/index.mjs +2 -2
- package/dist/{logger-mZ9OSbmD.mjs → logger-CnBTcwyq.mjs} +16 -2
- package/dist/logger-D8H2IcYz.mjs +2 -0
- package/dist/{media-BqpUGCni.mjs → media-DDtX37T2.mjs} +1 -1
- package/dist/{media-DnQB2BrL.mjs → media-g8tJtpfU.mjs} +29 -20
- package/dist/{message-handler-DQZiKx3i.mjs → message-handler-BrO2i7tg.mjs} +251 -139
- package/dist/{messaging-WleKf54h.mjs → messaging-CK0Wz3Ze.mjs} +23 -21
- package/dist/{runtime-DzZo6Gvk.mjs → runtime-DbqGRyhI.mjs} +6 -5
- package/dist/{utils-Bjh4r_qS.mjs → utils-Bwz2Fls_.mjs} +1 -1
- package/docs/RELEASE_NOTES_V0.8.28.md +56 -0
- package/docs/RELEASE_NOTES_V0.8.29.md +57 -0
- package/openclaw.plugin.json +9 -5
- package/package.json +2 -2
- package/src/config/schema.ts +2 -1
- package/src/core/connection.ts +504 -220
- package/src/core/message-handler.ts +197 -102
- package/src/reply-dispatcher.ts +244 -93
- package/src/services/media.ts +60 -27
- package/src/services/messaging/card-content-cache.ts +11 -6
- package/src/services/messaging/card.ts +2 -0
- package/src/services/messaging.ts +52 -38
- package/src/utils/background-work.ts +65 -0
- package/src/utils/logger.ts +23 -2
- package/dist/card-content-cache-Dwd8HICP.mjs +0 -2
- package/dist/chunk-upload-DaLXXZH3.mjs +0 -2
- package/dist/connection-fc6B4z4G.mjs +0 -423
- package/dist/gateway-methods-BQd4E0Sk.mjs +0 -2
- package/dist/logger-BmJkQkm1.mjs +0 -2
- /package/dist/{game-xiyou-CAT0ilSj.mjs → game-xiyou-uwsv23AC.mjs} +0 -0
|
@@ -674,9 +674,11 @@ export async function finishAICard(
|
|
|
674
674
|
log?.error?.(
|
|
675
675
|
`[DingTalk][AICard] FINISHED 重试失败:${retryErr.message}`,
|
|
676
676
|
);
|
|
677
|
+
throw retryErr;
|
|
677
678
|
}
|
|
678
679
|
} else {
|
|
679
680
|
log?.error?.(`[DingTalk][AICard] FINISHED 更新失败:${err.message}`);
|
|
681
|
+
throw err;
|
|
680
682
|
}
|
|
681
683
|
}
|
|
682
684
|
}
|
|
@@ -7,7 +7,7 @@ import type { DingtalkConfig } from "../types/index.ts";
|
|
|
7
7
|
import { DINGTALK_API, getAccessToken, getOapiAccessToken } from "../utils/index.ts";
|
|
8
8
|
import { dingtalkHttp, dingtalkOapiHttp } from "../utils/http-client.ts";
|
|
9
9
|
import { MEDIA_MSG_TYPES } from "../utils/constants.ts";
|
|
10
|
-
import { createLoggerFromConfig } from "../utils/logger.ts";
|
|
10
|
+
import { createLoggerFromConfig, isDingtalkDebug } from "../utils/logger.ts";
|
|
11
11
|
import {
|
|
12
12
|
processLocalImages,
|
|
13
13
|
processImagesForOutbound,
|
|
@@ -779,7 +779,9 @@ async function downloadRemoteMediaToTemp(
|
|
|
779
779
|
): Promise<string | null> {
|
|
780
780
|
const tag = "[DingTalk][RemoteMedia]";
|
|
781
781
|
try {
|
|
782
|
-
|
|
782
|
+
if (isDingtalkDebug()) {
|
|
783
|
+
console.log(`${tag} 开始下载 | ${url}`);
|
|
784
|
+
}
|
|
783
785
|
const res = await fetch(url, {
|
|
784
786
|
redirect: "follow",
|
|
785
787
|
signal: AbortSignal.timeout(60_000),
|
|
@@ -819,7 +821,9 @@ async function downloadRemoteMediaToTemp(
|
|
|
819
821
|
`dingtalk-remote-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`,
|
|
820
822
|
);
|
|
821
823
|
writeFileSync(tmp, buf);
|
|
822
|
-
|
|
824
|
+
if (isDingtalkDebug()) {
|
|
825
|
+
console.log(`${tag} 下载成功 | bytes=${buf.length} ct=${ct || "-"} tmp=${tmp}`);
|
|
826
|
+
}
|
|
823
827
|
return tmp;
|
|
824
828
|
} catch (err: any) {
|
|
825
829
|
console.warn(`${tag} 下载异常 | ${err?.message || err}`);
|
|
@@ -883,26 +887,27 @@ export async function sendTextToDingTalk(params: {
|
|
|
883
887
|
|
|
884
888
|
const targetParam = resolveOutboundTarget(target);
|
|
885
889
|
|
|
886
|
-
console.log(
|
|
887
|
-
`[DingTalk][LocalImage] sendTextToDingTalk 入口 | target=${target} textLen=${text?.length ?? 0}`,
|
|
888
|
-
);
|
|
889
|
-
logMediaIdTrace("sendTextToDingTalk:in", text, `target=${target}`, true);
|
|
890
|
-
|
|
891
890
|
// 图片:![] 上传为 mediaId;下载链接原 URL 留在同一条 markdown(不拆消息)
|
|
891
|
+
// 无图路径全程静默;有 MD 图/失败才打日志(debug: true 可开全量)
|
|
892
892
|
const beforeLen = text?.length ?? 0;
|
|
893
|
+
const hasMdImg = /!\[[^\]]*\]\([^)]+\)/.test(text || "");
|
|
894
|
+
if (hasMdImg || isDingtalkDebug()) {
|
|
895
|
+
logMediaIdTrace("sendTextToDingTalk:in", text, `target=${target}`);
|
|
896
|
+
}
|
|
893
897
|
try {
|
|
894
898
|
const oapiToken = await getOapiAccessToken(config);
|
|
895
|
-
console.log(
|
|
896
|
-
`[DingTalk][LocalImage] sendTextToDingTalk 入口 | target=${target} textLen=${beforeLen} hasToken=${!!oapiToken}`,
|
|
897
|
-
);
|
|
898
899
|
if (oapiToken && text) {
|
|
899
900
|
const processed = await processImagesForOutbound(text, oapiToken, log);
|
|
900
901
|
text = processed.text;
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
902
|
+
if (beforeLen !== text.length && (hasMdImg || isDingtalkDebug())) {
|
|
903
|
+
console.log(
|
|
904
|
+
`[DingTalk][LocalImage] sendTextToDingTalk 处理后 | ${beforeLen}→${text.length} 字`,
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
if (hasMdImg || isDingtalkDebug()) {
|
|
908
|
+
logMediaIdTrace("sendTextToDingTalk:after-process", text);
|
|
909
|
+
}
|
|
910
|
+
} else if (!oapiToken && hasMdImg) {
|
|
906
911
|
console.warn(
|
|
907
912
|
`[DingTalk][LocalImage] sendTextToDingTalk 无 oapiToken,跳过图片上传(本地 MD 图会灰)`,
|
|
908
913
|
);
|
|
@@ -915,15 +920,13 @@ export async function sendTextToDingTalk(params: {
|
|
|
915
920
|
|
|
916
921
|
const msgType: DingTalkMsgType = shouldSendAsMarkdown(text) ? "markdown" : "text";
|
|
917
922
|
const cardOpts = messageToolProactiveCardOptions(config);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
`[DingTalk][MessageCard] sendTextToDingTalk | messageAnswerCard=${isMessageAnswerCardEnabled(config)} useAICard=${cardOpts.useAICard} tpl=${cardOpts.cardTemplateId || "-"}`,
|
|
926
|
-
);
|
|
923
|
+
if (hasMdImg || isDingtalkDebug()) {
|
|
924
|
+
logMediaIdTrace(
|
|
925
|
+
"sendTextToDingTalk:API前",
|
|
926
|
+
text,
|
|
927
|
+
`msgType=${msgType} messageAnswerCard=${isMessageAnswerCardEnabled(config)} useAICard=${cardOpts.useAICard}`,
|
|
928
|
+
);
|
|
929
|
+
}
|
|
927
930
|
|
|
928
931
|
return sendProactive(config, targetParam, text, {
|
|
929
932
|
msgType,
|
|
@@ -1127,9 +1130,11 @@ export async function sendMediaToDingTalk(params: {
|
|
|
1127
1130
|
const mergeMd =
|
|
1128
1131
|
config.messageImageMd === true && caption.length > 0 && imgsInCaption >= 1;
|
|
1129
1132
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
+
if (isDingtalkDebug()) {
|
|
1134
|
+
console.log(
|
|
1135
|
+
`[DingTalk][LocalImage] sendMedia 图片策略 | messageImageMd=${config.messageImageMd === true} imgsInCaption=${imgsInCaption} mergeMd=${mergeMd} remote=${isRemoteHttpUrl(mediaUrl)}`,
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1133
1138
|
|
|
1134
1139
|
if (mergeMd) {
|
|
1135
1140
|
const combined = `${caption}\n\n`;
|
|
@@ -1250,7 +1255,9 @@ export async function sendMediaToDingTalk(params: {
|
|
|
1250
1255
|
if (cleanupTemp) {
|
|
1251
1256
|
try {
|
|
1252
1257
|
_fs.unlinkSync(cleanupTemp);
|
|
1253
|
-
|
|
1258
|
+
if (isDingtalkDebug()) {
|
|
1259
|
+
console.log(`[DingTalk][RemoteMedia] 已清理临时文件 | ${cleanupTemp}`);
|
|
1260
|
+
}
|
|
1254
1261
|
} catch {
|
|
1255
1262
|
// ignore
|
|
1256
1263
|
}
|
|
@@ -1392,9 +1399,11 @@ async function sendProactiveInternal(
|
|
|
1392
1399
|
undefined,
|
|
1393
1400
|
skipInputingWalk,
|
|
1394
1401
|
);
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1402
|
+
if (isDingtalkDebug()) {
|
|
1403
|
+
console.log(
|
|
1404
|
+
`[DingTalk][MessageCard] AI Card 发送成功 | outTrack=${card.cardInstanceId} tpl=${cardTemplateId || "default"} skipInputing=${skipInputingWalk}`,
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1398
1407
|
return {
|
|
1399
1408
|
ok: true,
|
|
1400
1409
|
cardInstanceId: card.cardInstanceId,
|
|
@@ -1455,12 +1464,17 @@ async function sendProactiveInternal(
|
|
|
1455
1464
|
return { ok: false, error: payload.error, usedAICard: false };
|
|
1456
1465
|
}
|
|
1457
1466
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
content
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1467
|
+
// 仅有 MD 图或调试时追踪(纯文本出站不再刷屏)
|
|
1468
|
+
if (
|
|
1469
|
+
/!\[[^\]]*\]\([^)]+\)/.test(content || "") ||
|
|
1470
|
+
isDingtalkDebug()
|
|
1471
|
+
) {
|
|
1472
|
+
logMediaIdTrace(
|
|
1473
|
+
"API出站",
|
|
1474
|
+
content,
|
|
1475
|
+
`msgKey=${payload.msgKey} type=${msgType}`,
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1464
1478
|
|
|
1465
1479
|
const body: any = {
|
|
1466
1480
|
robotCode: String(config.clientId),
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 后台任务引用计数。
|
|
3
|
+
*
|
|
4
|
+
* 钉钉 WS 回调只「入队」后就返回,真正的 AI 跑在 sessionQueues 后台 Promise 里。
|
|
5
|
+
* 若只在 WS 回调里 markMessageProcessingStart/End,会在入队后立刻 End,
|
|
6
|
+
* 长任务(尤其 503 重试)期间心跳超时会触发幽灵重连 → 后续消息「网关没反应」。
|
|
7
|
+
*
|
|
8
|
+
* 用法:session 队列任务创建后 trackBackgroundWork(taskPromise)。
|
|
9
|
+
* connection 层订阅计数变化,在 count>0 时保持处理中 keepAlive。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { isDingtalkDebug } from "./logger.ts";
|
|
13
|
+
|
|
14
|
+
type CountListener = (count: number) => void;
|
|
15
|
+
|
|
16
|
+
let activeCount = 0;
|
|
17
|
+
const listeners = new Set<CountListener>();
|
|
18
|
+
|
|
19
|
+
export function getActiveBackgroundWorkCount(): number {
|
|
20
|
+
return activeCount;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 订阅计数变化;返回取消订阅函数 */
|
|
24
|
+
export function onBackgroundWorkCountChange(listener: CountListener): () => void {
|
|
25
|
+
listeners.add(listener);
|
|
26
|
+
return () => {
|
|
27
|
+
listeners.delete(listener);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function notify(): void {
|
|
32
|
+
for (const listener of listeners) {
|
|
33
|
+
try {
|
|
34
|
+
listener(activeCount);
|
|
35
|
+
} catch {
|
|
36
|
+
// ignore listener errors
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 跟踪一个后台 Promise:开始 +1,settle -1。
|
|
43
|
+
* 重复 settle 安全(只减一次)。
|
|
44
|
+
*/
|
|
45
|
+
export function trackBackgroundWork(
|
|
46
|
+
work: Promise<unknown>,
|
|
47
|
+
label?: string,
|
|
48
|
+
): void {
|
|
49
|
+
activeCount += 1;
|
|
50
|
+
notify();
|
|
51
|
+
let settled = false;
|
|
52
|
+
const done = () => {
|
|
53
|
+
if (settled) return;
|
|
54
|
+
settled = true;
|
|
55
|
+
activeCount = Math.max(0, activeCount - 1);
|
|
56
|
+
notify();
|
|
57
|
+
};
|
|
58
|
+
void work.then(done, done);
|
|
59
|
+
if (label && typeof process !== "undefined" && isDingtalkDebug()) {
|
|
60
|
+
console.log(`[DingTalk][bgWork] +1 label=${label} count=${activeCount}`);
|
|
61
|
+
void work.finally(() => {
|
|
62
|
+
console.log(`[DingTalk][bgWork] -1 label=${label} count=${activeCount}`);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 日志工具模块
|
|
3
|
-
*
|
|
3
|
+
* 根据配置里的 debug 控制日志输出(无需环境变量)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/** 任一账号/连接开启过 debug 后为 true;默认静默 */
|
|
7
|
+
let pluginDebugEnabled = false;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 设置插件全局 debug(由配置 `debug: true` 驱动)
|
|
11
|
+
* 仅在 true 时打开;避免多账号中未开 debug 的账号把开关关掉。
|
|
12
|
+
*/
|
|
13
|
+
export function setDingtalkDebug(enabled: boolean): void {
|
|
14
|
+
if (enabled) pluginDebugEnabled = true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 是否输出图片/引用/队列等详细诊断日志 */
|
|
18
|
+
export function isDingtalkDebug(): boolean {
|
|
19
|
+
return pluginDebugEnabled;
|
|
20
|
+
}
|
|
21
|
+
|
|
6
22
|
/**
|
|
7
23
|
* 创建日志记录器
|
|
8
24
|
* @param debug - 是否启用 debug 模式
|
|
@@ -10,6 +26,8 @@
|
|
|
10
26
|
* @returns 日志记录器对象
|
|
11
27
|
*/
|
|
12
28
|
export function createLogger(debug: boolean = false, prefix: string = '') {
|
|
29
|
+
if (debug) setDingtalkDebug(true);
|
|
30
|
+
|
|
13
31
|
const logger = {
|
|
14
32
|
/**
|
|
15
33
|
* 打印 info 级别日志
|
|
@@ -73,6 +91,9 @@ export function createLogger(debug: boolean = false, prefix: string = '') {
|
|
|
73
91
|
* @param prefix - 日志前缀
|
|
74
92
|
* @returns 日志记录器对象
|
|
75
93
|
*/
|
|
76
|
-
export function createLoggerFromConfig(
|
|
94
|
+
export function createLoggerFromConfig(
|
|
95
|
+
config: { debug?: boolean } | undefined | null,
|
|
96
|
+
prefix: string = '',
|
|
97
|
+
) {
|
|
77
98
|
return createLogger(!!config?.debug, prefix);
|
|
78
99
|
}
|