@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.en.md +40 -82
  3. package/README.md +50 -93
  4. package/dist/background-work-DkaSDqWk.mjs +53 -0
  5. package/dist/card-content-cache-C7-XHhHw.mjs +2 -0
  6. package/dist/{card-content-cache-CYUzCaQJ.mjs → card-content-cache-DT_OYThh.mjs} +15 -1
  7. package/dist/chunk-upload-BKM1y4Ba.mjs +2 -0
  8. package/dist/{chunk-upload-BBQgGtcZ.mjs → chunk-upload-CIU5G_ay.mjs} +1 -1
  9. package/dist/{common-DE0gd0Jg.mjs → common-BfIYs1yC.mjs} +3 -3
  10. package/dist/{common-B_A78n8u.mjs → common-C0Oaf0z-.mjs} +1 -1
  11. package/dist/connection-obtrPkj4.mjs +641 -0
  12. package/dist/entry-bundled.mjs +1 -1
  13. package/dist/{gateway-methods--aKsyMs-.mjs → gateway-methods-BdcpfxEZ.mjs} +1 -1
  14. package/dist/gateway-methods-DQ6TwOAr.mjs +2 -0
  15. package/dist/index.mjs +2 -2
  16. package/dist/{logger-mZ9OSbmD.mjs → logger-CnBTcwyq.mjs} +16 -2
  17. package/dist/logger-D8H2IcYz.mjs +2 -0
  18. package/dist/{media-BqpUGCni.mjs → media-DDtX37T2.mjs} +1 -1
  19. package/dist/{media-DnQB2BrL.mjs → media-g8tJtpfU.mjs} +29 -20
  20. package/dist/{message-handler-DQZiKx3i.mjs → message-handler-BrO2i7tg.mjs} +251 -139
  21. package/dist/{messaging-WleKf54h.mjs → messaging-CK0Wz3Ze.mjs} +23 -21
  22. package/dist/{runtime-DzZo6Gvk.mjs → runtime-DbqGRyhI.mjs} +6 -5
  23. package/dist/{utils-Bjh4r_qS.mjs → utils-Bwz2Fls_.mjs} +1 -1
  24. package/docs/RELEASE_NOTES_V0.8.28.md +56 -0
  25. package/docs/RELEASE_NOTES_V0.8.29.md +57 -0
  26. package/openclaw.plugin.json +9 -5
  27. package/package.json +2 -2
  28. package/src/config/schema.ts +2 -1
  29. package/src/core/connection.ts +504 -220
  30. package/src/core/message-handler.ts +197 -102
  31. package/src/reply-dispatcher.ts +244 -93
  32. package/src/services/media.ts +60 -27
  33. package/src/services/messaging/card-content-cache.ts +11 -6
  34. package/src/services/messaging/card.ts +2 -0
  35. package/src/services/messaging.ts +52 -38
  36. package/src/utils/background-work.ts +65 -0
  37. package/src/utils/logger.ts +23 -2
  38. package/dist/card-content-cache-Dwd8HICP.mjs +0 -2
  39. package/dist/chunk-upload-DaLXXZH3.mjs +0 -2
  40. package/dist/connection-fc6B4z4G.mjs +0 -423
  41. package/dist/gateway-methods-BQd4E0Sk.mjs +0 -2
  42. package/dist/logger-BmJkQkm1.mjs +0 -2
  43. /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
- console.log(`${tag} 开始下载 | ${url}`);
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
- console.log(`${tag} 下载成功 | bytes=${buf.length} ct=${ct || "-"} tmp=${tmp}`);
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
- console.log(
902
- `[DingTalk][LocalImage] sendTextToDingTalk 处理后 | ${beforeLen}→${text.length} 字`,
903
- );
904
- logMediaIdTrace("sendTextToDingTalk:after-process", text, undefined, true);
905
- } else if (!oapiToken) {
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
- 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
- );
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
- console.log(
1131
- `[DingTalk][LocalImage] sendMedia 图片策略 | messageImageMd=${config.messageImageMd === true} imgsInCaption=${imgsInCaption} mergeMd=${mergeMd} remote=${isRemoteHttpUrl(mediaUrl)}`,
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![](${mediaId})`;
@@ -1250,7 +1255,9 @@ export async function sendMediaToDingTalk(params: {
1250
1255
  if (cleanupTemp) {
1251
1256
  try {
1252
1257
  _fs.unlinkSync(cleanupTemp);
1253
- console.log(`[DingTalk][RemoteMedia] 已清理临时文件 | ${cleanupTemp}`);
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
- console.log(
1396
- `[DingTalk][MessageCard] AI Card 发送成功 | outTrack=${card.cardInstanceId} tpl=${cardTemplateId || "default"} skipInputing=${skipInputingWalk}`,
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
- logMediaIdTrace(
1459
- "API出站",
1460
- content,
1461
- `msgKey=${payload.msgKey} type=${msgType}`,
1462
- true,
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
+ }
@@ -1,8 +1,24 @@
1
1
  /**
2
2
  * 日志工具模块
3
- * 根据 debug 配置控制日志输出
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(config: { debug?: boolean } | undefined | null, prefix: string = '') {
94
+ export function createLoggerFromConfig(
95
+ config: { debug?: boolean } | undefined | null,
96
+ prefix: string = '',
97
+ ) {
77
98
  return createLogger(!!config?.debug, prefix);
78
99
  }
@@ -1,2 +0,0 @@
1
- import { r as rememberCardContent } from "./card-content-cache-CYUzCaQJ.mjs";
2
- export { rememberCardContent };
@@ -1,2 +0,0 @@
1
- import { a as uploadLargeFileByChunks } from "./chunk-upload-BBQgGtcZ.mjs";
2
- export { uploadLargeFileByChunks };