@jeik/dingtalk-connector 0.8.28 → 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 +30 -31
- package/README.en.md +36 -71
- package/README.md +46 -74
- package/dist/{background-work-Cxb4b-91.mjs → background-work-DkaSDqWk.mjs} +12 -1
- package/dist/card-content-cache-C7-XHhHw.mjs +2 -0
- package/dist/{card-content-cache-bDQFz2u8.mjs → card-content-cache-DT_OYThh.mjs} +15 -1
- package/dist/chunk-upload-BKM1y4Ba.mjs +2 -0
- package/dist/{chunk-upload-BE71drPg.mjs → chunk-upload-CIU5G_ay.mjs} +1 -1
- package/dist/{common-BZiVgd3A.mjs → common-BfIYs1yC.mjs} +3 -3
- package/dist/{common-DgL28gCZ.mjs → common-C0Oaf0z-.mjs} +1 -1
- package/dist/{connection-CZ7ZoN5_.mjs → connection-obtrPkj4.mjs} +56 -65
- package/dist/entry-bundled.mjs +1 -1
- package/dist/{gateway-methods-kNX3qvqw.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-B1usz1wb.mjs → message-handler-BrO2i7tg.mjs} +26 -30
- package/dist/{messaging-DVS3UKOp.mjs → messaging-CK0Wz3Ze.mjs} +18 -20
- package/dist/{runtime-A6G7WAWn.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 +4 -4
- package/docs/RELEASE_NOTES_V0.8.29.md +57 -0
- package/openclaw.plugin.json +9 -5
- package/package.json +1 -1
- package/src/config/schema.ts +2 -1
- package/src/core/connection.ts +90 -113
- package/src/core/message-handler.ts +24 -16
- package/src/reply-dispatcher.ts +14 -20
- package/src/services/media.ts +60 -27
- package/src/services/messaging/card-content-cache.ts +11 -6
- package/src/services/messaging.ts +52 -38
- package/src/utils/background-work.ts +3 -1
- package/src/utils/logger.ts +23 -2
- package/dist/card-content-cache-c4Ls96eJ.mjs +0 -2
- package/dist/chunk-upload-PwhogwfE.mjs +0 -2
- package/dist/gateway-methods-BkI1gKaC.mjs +0 -2
- package/dist/logger-BmJkQkm1.mjs +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as checkAndMarkDingtalkMessage } from "./utils-legacy-DQ3HeGHh.mjs";
|
|
2
|
-
import { n as onBackgroundWorkCountChange, t as getActiveBackgroundWorkCount } from "./background-work-
|
|
2
|
+
import { n as onBackgroundWorkCountChange, t as getActiveBackgroundWorkCount } from "./background-work-DkaSDqWk.mjs";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
//#region src/core/connection.ts
|
|
5
5
|
/**
|
|
@@ -64,7 +64,7 @@ let _connectionNoticePrinted = false;
|
|
|
64
64
|
function printConnectionNoticeOnce() {
|
|
65
65
|
if (_connectionNoticePrinted) return;
|
|
66
66
|
_connectionNoticePrinted = true;
|
|
67
|
-
console.log("[dingtalk-connector] ℹ️ 上游 dingtalk-stream
|
|
67
|
+
console.log("[dingtalk-connector] ℹ️ 上游 dingtalk-stream 噪音已过滤;OPEN 成功即可上线,REGISTERED 尽量等待;若环境不推 REGISTERED,首条消息会确认订阅。正常运行不应出现 ≤30s 周期性硬重连。");
|
|
68
68
|
}
|
|
69
69
|
async function monitorSingleAccount(opts) {
|
|
70
70
|
const { cfg, account, runtime, abortSignal, messageHandler, onStatusChange } = opts;
|
|
@@ -72,7 +72,8 @@ async function monitorSingleAccount(opts) {
|
|
|
72
72
|
silenceDingtalkStreamConsoleNoise();
|
|
73
73
|
const clawdbotConfig = cfg;
|
|
74
74
|
const log = runtime?.log;
|
|
75
|
-
const { createLoggerFromConfig } = await import("./logger-
|
|
75
|
+
const { createLoggerFromConfig, setDingtalkDebug } = await import("./logger-D8H2IcYz.mjs");
|
|
76
|
+
if (account.config?.debug) setDingtalkDebug(true);
|
|
76
77
|
const logger = createLoggerFromConfig(account.config, `DingTalk:${accountId}`);
|
|
77
78
|
if (!account.clientId || !account.clientSecret) throw new Error(`[DingTalk][${accountId}] Missing credentials: clientId=${account.clientId ? "present" : "MISSING"}, clientSecret=${account.clientSecret ? "present" : "MISSING"}. Please check your configuration in channels.dingtalk-connector.`);
|
|
78
79
|
const clientIdStr = String(account.clientId);
|
|
@@ -169,13 +170,14 @@ async function monitorSingleAccount(opts) {
|
|
|
169
170
|
return Math.min(exponentialDelay + jitter, MAX_BACKOFF_DELAY);
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
172
|
-
* 等待钉钉 Stream
|
|
173
|
+
* 等待钉钉 Stream 可服务。
|
|
173
174
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
175
|
+
* - OPEN:必须成功,否则 throw
|
|
176
|
+
* - REGISTERED:尽量等待;超时则 **降级 OPEN 上线**(不 throw)
|
|
176
177
|
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
178
|
+
* 原因(生产日志):部分环境 socket 已 OPEN 且 CALLBACK 能收消息,
|
|
179
|
+
* 但长期收不到 SYSTEM/REGISTERED 或 client.registered 不被置 true。
|
|
180
|
+
* 若硬性失败会触发网关 auto-restart 风暴,而旧连接/半连接其实仍在处理消息。
|
|
179
181
|
*/
|
|
180
182
|
async function waitForStreamReady(timeoutMs = STREAM_READY_TIMEOUT_MS) {
|
|
181
183
|
const started = Date.now();
|
|
@@ -209,7 +211,7 @@ async function monitorSingleAccount(opts) {
|
|
|
209
211
|
}
|
|
210
212
|
if (client.registered === true) {
|
|
211
213
|
logger.info(`✅ Stream 已就绪(OPEN + REGISTERED),耗时 ${Date.now() - started}ms`);
|
|
212
|
-
return;
|
|
214
|
+
return { registered: true };
|
|
213
215
|
}
|
|
214
216
|
if (await new Promise((resolve) => {
|
|
215
217
|
const socket = client.socket;
|
|
@@ -245,9 +247,10 @@ async function monitorSingleAccount(opts) {
|
|
|
245
247
|
socket.on("message", onMessage);
|
|
246
248
|
}) || client.registered === true) {
|
|
247
249
|
logger.info(`✅ Stream 已就绪(OPEN + REGISTERED),耗时 ${Date.now() - started}ms`);
|
|
248
|
-
return;
|
|
250
|
+
return { registered: true };
|
|
249
251
|
}
|
|
250
|
-
|
|
252
|
+
logger.warn(`⚠️ 未观察到 SYSTEM/REGISTERED(${timeoutMs}ms),socket 已 OPEN → 降级上线。 收到首条机器人消息后会记为「订阅 empirically 生效」。`);
|
|
253
|
+
return { registered: false };
|
|
251
254
|
}
|
|
252
255
|
/**
|
|
253
256
|
* connect 之后、wait ready 之前挂上 pong/message/close。
|
|
@@ -261,26 +264,27 @@ async function monitorSingleAccount(opts) {
|
|
|
261
264
|
setupCloseListener();
|
|
262
265
|
}
|
|
263
266
|
/**
|
|
264
|
-
* 单次:connect + 挂 listener + 等 OPEN
|
|
265
|
-
* 失败 throw
|
|
267
|
+
* 单次:connect + 挂 listener + 等 OPEN(REGISTERED 可选)。
|
|
268
|
+
* 仅 OPEN 失败 throw;REGISTERED 超时则降级成功。
|
|
266
269
|
*/
|
|
267
|
-
async function
|
|
270
|
+
async function connectAndWaitReady() {
|
|
268
271
|
await client.connect();
|
|
269
272
|
attachSocketLifecycleListeners();
|
|
270
|
-
await waitForStreamReady(STREAM_READY_TIMEOUT_MS);
|
|
271
|
-
if (client.registered !== true) throw new Error("connect 后 client.registered 仍为 false");
|
|
273
|
+
return await waitForStreamReady(STREAM_READY_TIMEOUT_MS);
|
|
272
274
|
}
|
|
273
275
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
+
* 建立可服务连接。
|
|
277
|
+
* - 优先拿到 REGISTERED
|
|
278
|
+
* - OPEN 成功即可上线(避免环境不推 REGISTERED 时 restart 风暴)
|
|
279
|
+
* - 仅在 OPEN 都失败时多轮重试
|
|
276
280
|
*/
|
|
277
281
|
async function ensureRegisteredConnection(maxAttempts = REGISTERED_CONNECT_MAX_ATTEMPTS) {
|
|
278
282
|
let lastError;
|
|
279
283
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
280
|
-
if (isStopped) throw new Error("连接已停止,中止
|
|
284
|
+
if (isStopped) throw new Error("连接已停止,中止 Stream 建立");
|
|
281
285
|
try {
|
|
282
286
|
if (attempt > 1) {
|
|
283
|
-
logger.warn(`🔄
|
|
287
|
+
logger.warn(`🔄 Stream 建立重试 ${attempt}/${maxAttempts}…`);
|
|
284
288
|
try {
|
|
285
289
|
if (client.socket) await client.disconnect();
|
|
286
290
|
} catch (discErr) {
|
|
@@ -288,18 +292,18 @@ async function monitorSingleAccount(opts) {
|
|
|
288
292
|
}
|
|
289
293
|
const delay = Math.min(1e3 * attempt, 5e3);
|
|
290
294
|
await new Promise((r) => setTimeout(r, delay));
|
|
291
|
-
} else logger.info(`⏳ 建立 Stream
|
|
292
|
-
await
|
|
293
|
-
noteSocketAlive("registered-ok");
|
|
295
|
+
} else logger.info(`⏳ 建立 Stream(OPEN 必须;REGISTERED 尽量等待 ${STREAM_READY_TIMEOUT_MS}ms)…`);
|
|
296
|
+
const ready = await connectAndWaitReady();
|
|
297
|
+
noteSocketAlive(ready.registered ? "registered-ok" : "open-degraded");
|
|
294
298
|
connectionEstablishedTime = Date.now();
|
|
295
|
-
logger.info(`✅
|
|
299
|
+
logger.info(`✅ Stream 可服务(attempt ${attempt}/${maxAttempts}, registered=${ready.registered || Boolean(client.registered)}, socket=${client.socket?.readyState})`);
|
|
296
300
|
return;
|
|
297
301
|
} catch (err) {
|
|
298
302
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
299
|
-
logger.warn(`⚠️ Stream
|
|
303
|
+
logger.warn(`⚠️ Stream 建立失败 attempt=${attempt}/${maxAttempts}: ${lastError.message}`);
|
|
300
304
|
}
|
|
301
305
|
}
|
|
302
|
-
throw new Error(`钉钉 Stream 在 ${maxAttempts}
|
|
306
|
+
throw new Error(`钉钉 Stream 在 ${maxAttempts} 次尝试后仍无法 OPEN。 最后错误: ${lastError?.message || "unknown"}`);
|
|
303
307
|
}
|
|
304
308
|
/** 统一重连函数,带指数退避(无限重连) */
|
|
305
309
|
async function doReconnect(immediate = false) {
|
|
@@ -506,21 +510,19 @@ async function monitorSingleAccount(opts) {
|
|
|
506
510
|
receivedCount++;
|
|
507
511
|
lastMessageTime = Date.now();
|
|
508
512
|
noteSocketAlive("inbound-callback");
|
|
513
|
+
if (client.registered !== true) {
|
|
514
|
+
client.registered = true;
|
|
515
|
+
logger.info(`✅ 收到机器人 CALLBACK,视为订阅已生效(此前可能未观察到 SYSTEM/REGISTERED)`);
|
|
516
|
+
}
|
|
509
517
|
onStatusChange?.({ lastInboundAt: Date.now() });
|
|
510
518
|
const messageId = res.headers?.messageId;
|
|
511
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
512
|
-
logger.info(`\n========== 收到新消息 ==========`);
|
|
513
|
-
logger.info(`时间:${timestamp}`);
|
|
514
|
-
logger.info(`MessageId: ${messageId || "N/A"}`);
|
|
515
|
-
logger.info(`Headers: ${JSON.stringify(res.headers || {})}`);
|
|
516
|
-
logger.info(`Data 长度:${res.data?.length || 0} 字符`);
|
|
517
519
|
let acked = false;
|
|
518
520
|
const ackMessage = (why) => {
|
|
519
521
|
if (acked || !messageId) return;
|
|
520
522
|
try {
|
|
521
523
|
client.socketCallBackResponse(messageId, { success: true });
|
|
522
524
|
acked = true;
|
|
523
|
-
logger.
|
|
525
|
+
logger.debug(`ACK messageId=${messageId} (${why})`);
|
|
524
526
|
} catch (ackErr) {
|
|
525
527
|
logger.warn(`确认回调失败: ${ackErr?.message || ackErr}`);
|
|
526
528
|
}
|
|
@@ -531,37 +533,30 @@ async function monitorSingleAccount(opts) {
|
|
|
531
533
|
try {
|
|
532
534
|
data = JSON.parse(res.data);
|
|
533
535
|
} catch (parseError) {
|
|
534
|
-
logger.error(
|
|
535
|
-
|
|
536
|
-
rawData: typeof res.data === "string" ? res.data.substring(0, 500) : res.data,
|
|
537
|
-
dataType: typeof res.data
|
|
538
|
-
});
|
|
539
|
-
throw new Error(`Invalid JSON response from DingTalk API. Error: ${parseError instanceof Error ? parseError.message : String(parseError)}. Raw data (first 100 chars): ${String(res.data).substring(0, 100)}`);
|
|
536
|
+
logger.error(`JSON 解析失败: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
537
|
+
throw new Error(`Invalid JSON from DingTalk: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
540
538
|
}
|
|
541
|
-
logger.info(`\n----- 消息详情 -----`);
|
|
542
|
-
logger.info(`消息类型:${data.msgtype || "unknown"}`);
|
|
543
|
-
logger.info(`会话类型:${data.conversationType === "1" ? "DM (单聊)" : data.conversationType === "2" ? "Group (群聊)" : data.conversationType}`);
|
|
544
|
-
logger.info(`发送者:${data.senderNick || "unknown"} (${data.senderStaffId || data.senderId || "unknown"})`);
|
|
545
|
-
logger.info(`会话 ID: ${data.conversationId || "N/A"}`);
|
|
546
|
-
logger.info(`消息 ID: ${data.msgId || "N/A"}`);
|
|
547
|
-
logger.info(`SessionWebhook: ${data.sessionWebhook ? "已提供" : "未提供"}`);
|
|
548
|
-
logger.info(`RobotCode: ${data.robotCode || account.config?.clientId || "N/A"}`);
|
|
549
|
-
if (data.chatbotUserId || data.chatbotCorpId) console.log(`[DingTalk:${accountId}] [BotIdentity] accountId=${accountId} chatbotUserId=${data.chatbotUserId || "N/A"} chatbotCorpId=${data.chatbotCorpId || "N/A"}`);
|
|
550
539
|
const businessMsgId = data.msgId;
|
|
540
|
+
const convType = data.conversationType === "1" ? "DM" : data.conversationType === "2" ? "Group" : String(data.conversationType ?? "?");
|
|
541
|
+
const preview = (data.text?.content || "").trim().slice(0, 80) || data.msgtype || "(no text)";
|
|
542
|
+
logger.info(`📩 入站 ${convType} from=${data.senderNick || data.senderStaffId || data.senderId || "?"} msg=${businessMsgId || messageId || "-"} preview=${JSON.stringify(preview)}`);
|
|
543
|
+
logger.debug(`Headers: ${JSON.stringify(res.headers || {})}`);
|
|
544
|
+
logger.debug(`字段: ${Object.keys(data).join(",")}`);
|
|
545
|
+
if (data.chatbotUserId || data.chatbotCorpId) {
|
|
546
|
+
const key = `${accountId}:${data.chatbotUserId || ""}:${data.chatbotCorpId || ""}`;
|
|
547
|
+
if (!globalThis.__dingtalkBotIdentityLogged) globalThis.__dingtalkBotIdentityLogged = /* @__PURE__ */ new Set();
|
|
548
|
+
const seen = globalThis.__dingtalkBotIdentityLogged;
|
|
549
|
+
if (!seen.has(key)) {
|
|
550
|
+
seen.add(key);
|
|
551
|
+
console.log(`[DingTalk:${accountId}] [BotIdentity] chatbotUserId=${data.chatbotUserId || "N/A"} chatbotCorpId=${data.chatbotCorpId || "N/A"}`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
551
554
|
if (checkAndMarkDingtalkMessage(accountId, messageId, businessMsgId)) {
|
|
552
555
|
processedCount++;
|
|
553
556
|
ackMessage("duplicate");
|
|
554
|
-
logger.warn(`⚠️
|
|
555
|
-
logger.info(`========== 消息处理结束(重复) ==========\n`);
|
|
557
|
+
logger.warn(`⚠️ 重复消息已跳过 protocol=${messageId || "-"} business=${businessMsgId || "-"}`);
|
|
556
558
|
return;
|
|
557
559
|
}
|
|
558
|
-
let contentPreview = "N/A";
|
|
559
|
-
if (data.text?.content) contentPreview = data.text.content.length > 100 ? data.text.content.substring(0, 100) + "..." : data.text.content;
|
|
560
|
-
else if (data.content) contentPreview = JSON.stringify(data.content).substring(0, 100) + "...";
|
|
561
|
-
logger.info(`消息内容预览:${contentPreview}`);
|
|
562
|
-
logger.info(`完整数据字段:${Object.keys(data).join(", ")}`);
|
|
563
|
-
logger.info(`----- 消息详情结束 -----\n`);
|
|
564
|
-
logger.info(`🚀 开始处理消息...`);
|
|
565
560
|
await messageHandler({
|
|
566
561
|
accountId,
|
|
567
562
|
config: account.config,
|
|
@@ -574,16 +569,12 @@ async function monitorSingleAccount(opts) {
|
|
|
574
569
|
ackMessage("enqueued");
|
|
575
570
|
noteSocketAlive("after-enqueue");
|
|
576
571
|
processedCount++;
|
|
577
|
-
logger.info(`✅
|
|
578
|
-
logger.info(`========== 消息处理结束(成功) ==========\n`);
|
|
572
|
+
logger.info(`✅ 入队完成 (${processedCount}/${receivedCount})`);
|
|
579
573
|
} catch (error) {
|
|
580
574
|
processedCount++;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
logger.
|
|
584
|
-
logger.error(`错误堆栈:\n${errorStack}`);
|
|
585
|
-
if (!acked) logger.warn(`⚠️ 未 ACK messageId=${messageId || "N/A"},等待钉钉重投(避免网关永久丢消息)`);
|
|
586
|
-
logger.info(`========== 消息处理结束(失败) ==========\n`);
|
|
575
|
+
logger.error(`❌ 入站处理失败 (${processedCount}/${receivedCount}): ${error?.message || "未知错误"}`);
|
|
576
|
+
if (error?.stack) logger.debug(error.stack);
|
|
577
|
+
if (!acked) logger.warn(`⚠️ 未 ACK messageId=${messageId || "N/A"},等待钉钉重投`);
|
|
587
578
|
} finally {
|
|
588
579
|
markMessageProcessingEnd();
|
|
589
580
|
}
|
package/dist/entry-bundled.mjs
CHANGED
|
@@ -23,7 +23,7 @@ var entry_bundled_default = defineBundledChannelEntry({
|
|
|
23
23
|
exportName: "setDingtalkRuntime"
|
|
24
24
|
},
|
|
25
25
|
async registerFull(api) {
|
|
26
|
-
const { registerGatewayMethods } = await import("./gateway-methods-
|
|
26
|
+
const { registerGatewayMethods } = await import("./gateway-methods-DQ6TwOAr.mjs");
|
|
27
27
|
registerGatewayMethods(api);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-BAzdqkAV.mjs";
|
|
2
2
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
3
3
|
import { r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
|
|
4
|
-
import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-
|
|
4
|
+
import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-CK0Wz3Ze.mjs";
|
|
5
5
|
import { d as getUnionId, h as recallEmotionReply } from "./utils-legacy-DQ3HeGHh.mjs";
|
|
6
6
|
//#region src/docs.ts
|
|
7
7
|
var DingtalkDocsClient = class {
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-
|
|
2
|
-
import { t as registerGatewayMethods } from "./gateway-methods-
|
|
1
|
+
import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-DbqGRyhI.mjs";
|
|
2
|
+
import { t as registerGatewayMethods } from "./gateway-methods-BdcpfxEZ.mjs";
|
|
3
3
|
//#region index.ts
|
|
4
4
|
/**
|
|
5
5
|
* 检测同一 plugin id 在多个路径被加载的情况。
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
//#region src/utils/logger.ts
|
|
2
2
|
/**
|
|
3
3
|
* 日志工具模块
|
|
4
|
-
*
|
|
4
|
+
* 根据配置里的 debug 控制日志输出(无需环境变量)
|
|
5
5
|
*/
|
|
6
|
+
/** 任一账号/连接开启过 debug 后为 true;默认静默 */
|
|
7
|
+
let pluginDebugEnabled = false;
|
|
8
|
+
/**
|
|
9
|
+
* 设置插件全局 debug(由配置 `debug: true` 驱动)
|
|
10
|
+
* 仅在 true 时打开;避免多账号中未开 debug 的账号把开关关掉。
|
|
11
|
+
*/
|
|
12
|
+
function setDingtalkDebug(enabled) {
|
|
13
|
+
if (enabled) pluginDebugEnabled = true;
|
|
14
|
+
}
|
|
15
|
+
/** 是否输出图片/引用/队列等详细诊断日志 */
|
|
16
|
+
function isDingtalkDebug() {
|
|
17
|
+
return pluginDebugEnabled;
|
|
18
|
+
}
|
|
6
19
|
/**
|
|
7
20
|
* 创建日志记录器
|
|
8
21
|
* @param debug - 是否启用 debug 模式
|
|
@@ -10,6 +23,7 @@
|
|
|
10
23
|
* @returns 日志记录器对象
|
|
11
24
|
*/
|
|
12
25
|
function createLogger(debug = false, prefix = "") {
|
|
26
|
+
if (debug) setDingtalkDebug(true);
|
|
13
27
|
return {
|
|
14
28
|
/**
|
|
15
29
|
* 打印 info 级别日志
|
|
@@ -55,4 +69,4 @@ function createLoggerFromConfig(config, prefix = "") {
|
|
|
55
69
|
return createLogger(!!config?.debug, prefix);
|
|
56
70
|
}
|
|
57
71
|
//#endregion
|
|
58
|
-
export { createLoggerFromConfig as n, createLogger as t };
|
|
72
|
+
export { setDingtalkDebug as i, createLoggerFromConfig as n, isDingtalkDebug as r, createLogger as t };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as toLocalPath, d as processLocalImages, f as processRawMediaPaths, h as sendFileProactive, o as hasResidualLocalMdImagesOutsideCode, p as processVideoMarkers } from "./media-
|
|
1
|
+
import { _ as toLocalPath, d as processLocalImages, f as processRawMediaPaths, h as sendFileProactive, o as hasResidualLocalMdImagesOutsideCode, p as processVideoMarkers } from "./media-g8tJtpfU.mjs";
|
|
2
2
|
export { hasResidualLocalMdImagesOutsideCode, processLocalImages, processRawMediaPaths, processVideoMarkers, sendFileProactive, toLocalPath };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { r as isDingtalkDebug } from "./logger-CnBTcwyq.mjs";
|
|
1
2
|
import { n as dingtalkOapiHttp, t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
2
3
|
import { n as DINGTALK_OAPI } from "./utils-CIfI_3Jh.mjs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
@@ -76,9 +77,8 @@ function toLocalPath(raw) {
|
|
|
76
77
|
return filePath;
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* 决策路径(匹配/exists/上传成败/残留)务必 always-on,方便查灰图。
|
|
80
|
+
* 本地图诊断日志。
|
|
81
|
+
* 默认静默:无图/空扫描不再刷屏。仅在上传/失败/灰图风险,或 debug: true 时输出。
|
|
82
82
|
*/
|
|
83
83
|
function logLocalImage(log, step, detail, verbose = false) {
|
|
84
84
|
const line = detail ? `[DingTalk][LocalImage] ${step} | ${detail}` : `[DingTalk][LocalImage] ${step}`;
|
|
@@ -88,6 +88,12 @@ function logLocalImage(log, step, detail, verbose = false) {
|
|
|
88
88
|
} catch {}
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
|
+
if (!(isDingtalkDebug() || /失败|灰图|残留|上传成功|上传失败|下载成功|下载失败|重试|remote|Remote/i.test(`${step} ${detail || ""}`))) {
|
|
92
|
+
try {
|
|
93
|
+
log?.debug?.(line);
|
|
94
|
+
} catch {}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
91
97
|
console.log(line);
|
|
92
98
|
}
|
|
93
99
|
/** 钉钉 mediaId 形态(oapi 返回常带 @) */
|
|
@@ -117,7 +123,7 @@ function logMediaIdTrace(stage, content, extra, force = false) {
|
|
|
117
123
|
const residualLocal = mdTargets.filter((t) => isLocalImageRef(t));
|
|
118
124
|
const httpCount = (text.match(/https?:\/\//gi) || []).length;
|
|
119
125
|
const suspiciousBare = /type=image|sampleImage|photoURL/i.test(`${stage} ${extra || ""}`) ? [] : bareIds;
|
|
120
|
-
if (!(force || suspiciousBare.length > 0 || residualLocal.length > 0
|
|
126
|
+
if (!(force || isDingtalkDebug() || suspiciousBare.length > 0 || residualLocal.length > 0)) return;
|
|
121
127
|
const preview = text.replace(/\n/g, "\\n").slice(0, 200);
|
|
122
128
|
console.log(`[DingTalk][MediaIdTrace] ${stage} | len=${text.length} mediaIdCount=${ids.length}` + (ids.length ? ` ids=[${ids.slice(0, 4).join(",")}]` : "") + ` BARE_ids=${suspiciousBare.length ? suspiciousBare.join(",") : "无"} mdImgs=[${mdImgs.length ? mdImgs.join(" || ") : "-"}] httpCount=${httpCount}` + (residualLocal.length ? ` residualLocal=${residualLocal.length}:${residualLocal.slice(0, 3).map((p) => p.slice(0, 80)).join("|")}` : " residualLocal=0") + (extra ? ` ${extra}` : "") + ` preview="${preview}"`);
|
|
123
129
|
}
|
|
@@ -277,7 +283,7 @@ async function downloadRemoteUrlToTemp(url) {
|
|
|
277
283
|
}
|
|
278
284
|
const tmp = path.join(os.tmpdir(), `dingtalk-md-remote-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`);
|
|
279
285
|
fs.writeFileSync(tmp, buf);
|
|
280
|
-
console.log(`[DingTalk][MediaIdTrace] 远程图下载成功 | bytes=${buf.length} tmp=${tmp}`);
|
|
286
|
+
if (isDingtalkDebug()) console.log(`[DingTalk][MediaIdTrace] 远程图下载成功 | bytes=${buf.length} tmp=${tmp}`);
|
|
281
287
|
return tmp;
|
|
282
288
|
} catch (e) {
|
|
283
289
|
console.warn(`[DingTalk][MediaIdTrace] 远程图下载异常 | ${e?.message || e}`);
|
|
@@ -312,27 +318,29 @@ function replaceOutsideImageTargets(content, original, replacement) {
|
|
|
312
318
|
* - 本地:正文重复的本地路径换成 down.dingtalk.com 可点链接(仍同一条消息)
|
|
313
319
|
* - **不再拆第二条 text 消息**
|
|
314
320
|
*
|
|
315
|
-
*
|
|
316
|
-
* -
|
|
321
|
+
* 日志约定(默认静默;配置 debug: true 全量):
|
|
322
|
+
* - 有图上传/失败/灰图残留才 console;无图路径不刷屏
|
|
317
323
|
*/
|
|
318
324
|
async function processImagesForOutbound(content, oapiToken, log) {
|
|
319
325
|
logLocalImage(log, "processImagesForOutbound 开始", `contentLen=${content?.length ?? 0} hasToken=${!!oapiToken}`);
|
|
320
326
|
if (!oapiToken) {
|
|
321
|
-
|
|
322
|
-
|
|
327
|
+
if (/!\[[^\]]*\]\([^)]+\)/.test(content || "")) {
|
|
328
|
+
logLocalImage(log, "processImagesForOutbound 跳过", "无 oapiToken(本地路径无法上传→必灰图)");
|
|
329
|
+
logMediaIdTrace("processImagesForOutbound:skip-no-token", content);
|
|
330
|
+
}
|
|
323
331
|
return {
|
|
324
332
|
text: content || "",
|
|
325
333
|
followUpUrls: []
|
|
326
334
|
};
|
|
327
335
|
}
|
|
328
336
|
if (!content || typeof content !== "string") {
|
|
329
|
-
logLocalImage(log, "processImagesForOutbound 跳过", "content 为空");
|
|
337
|
+
logLocalImage(log, "processImagesForOutbound 跳过", "content 为空", true);
|
|
330
338
|
return {
|
|
331
339
|
text: content || "",
|
|
332
340
|
followUpUrls: []
|
|
333
341
|
};
|
|
334
342
|
}
|
|
335
|
-
logMediaIdTrace("processImagesForOutbound:in", content
|
|
343
|
+
logMediaIdTrace("processImagesForOutbound:in", content);
|
|
336
344
|
const codeRanges = getMarkdownCodeRanges(content);
|
|
337
345
|
if (codeRanges.length > 0) logLocalImage(log, "processImagesForOutbound 代码区域", `count=${codeRanges.length}(围栏/行内 code 内的图路径将跳过)`);
|
|
338
346
|
const mdMatches = [...content.matchAll(LOCAL_IMAGE_RE)];
|
|
@@ -435,9 +443,10 @@ async function processImagesForOutbound(content, oapiToken, log) {
|
|
|
435
443
|
if (m.index !== void 0 && isIndexInCodeRange(m.index, getMarkdownCodeRanges(result))) return false;
|
|
436
444
|
return isLocalImageRef((m[2] || "").trim());
|
|
437
445
|
}).map((m) => (m[2] || "").trim());
|
|
438
|
-
logLocalImage(log, "processImagesForOutbound 完成", `uploads=${uploadCount} localTried=${localTried} remoteTried=${remoteTried} skippedCode=${skippedCode} skippedOther=${skippedOther} outLen=${result.length}` + (residual.length ? ` ⚠️residualLocal=${residual.length}:${residual.slice(0, 3).join("|")}` : " residualLocal=0"));
|
|
446
|
+
if (uploadCount > 0 || localTried > 0 || remoteTried > 0 || residual.length > 0) logLocalImage(log, residual.length ? "processImagesForOutbound 完成(有残留)" : "processImagesForOutbound 上传成功", `uploads=${uploadCount} localTried=${localTried} remoteTried=${remoteTried} skippedCode=${skippedCode} skippedOther=${skippedOther} outLen=${result.length}` + (residual.length ? ` ⚠️residualLocal=${residual.length}:${residual.slice(0, 3).join("|")}` : " residualLocal=0"));
|
|
447
|
+
else logLocalImage(log, "processImagesForOutbound 完成", `uploads=0 outLen=${result.length}`, true);
|
|
439
448
|
if (residual.length) logLocalImage(log, "processImagesForOutbound ⚠️仍有代码块外本地路径未替换(钉钉必灰图)", residual.join(" | "));
|
|
440
|
-
logMediaIdTrace("processImagesForOutbound:out", result
|
|
449
|
+
logMediaIdTrace("processImagesForOutbound:out", result);
|
|
441
450
|
return {
|
|
442
451
|
text: result,
|
|
443
452
|
followUpUrls: []
|
|
@@ -658,7 +667,7 @@ async function extractAudioDuration(filePath, log) {
|
|
|
658
667
|
*/
|
|
659
668
|
async function sendVideoMessage(config, sessionWebhook, fileName, mediaId, log, metadata) {
|
|
660
669
|
try {
|
|
661
|
-
const token = await (await import("./utils-
|
|
670
|
+
const token = await (await import("./utils-Bwz2Fls_.mjs")).getAccessToken(config);
|
|
662
671
|
const videoMessage = {
|
|
663
672
|
msgtype: "video",
|
|
664
673
|
video: {
|
|
@@ -686,8 +695,8 @@ async function sendVideoMessage(config, sessionWebhook, fileName, mediaId, log,
|
|
|
686
695
|
*/
|
|
687
696
|
async function sendVideoProactive(config, target, videoMediaId, picMediaId, metadata, log) {
|
|
688
697
|
try {
|
|
689
|
-
const token = await (await import("./utils-
|
|
690
|
-
const { DINGTALK_API } = await import("./utils-
|
|
698
|
+
const token = await (await import("./utils-Bwz2Fls_.mjs")).getAccessToken(config);
|
|
699
|
+
const { DINGTALK_API } = await import("./utils-Bwz2Fls_.mjs");
|
|
691
700
|
const msgParam = {
|
|
692
701
|
duration: metadata?.duration.toString() || "60000",
|
|
693
702
|
videoMediaId,
|
|
@@ -732,8 +741,8 @@ async function sendVideoProactive(config, target, videoMediaId, picMediaId, meta
|
|
|
732
741
|
*/
|
|
733
742
|
async function sendAudioProactive(config, target, fileName, mediaId, log, durationMs) {
|
|
734
743
|
try {
|
|
735
|
-
const token = await (await import("./utils-
|
|
736
|
-
const { DINGTALK_API } = await import("./utils-
|
|
744
|
+
const token = await (await import("./utils-Bwz2Fls_.mjs")).getAccessToken(config);
|
|
745
|
+
const { DINGTALK_API } = await import("./utils-Bwz2Fls_.mjs");
|
|
737
746
|
const msgParam = {
|
|
738
747
|
mediaId,
|
|
739
748
|
duration: durationMs && durationMs > 0 ? durationMs.toString() : "60000"
|
|
@@ -770,8 +779,8 @@ async function sendAudioProactive(config, target, fileName, mediaId, log, durati
|
|
|
770
779
|
*/
|
|
771
780
|
async function sendFileProactive(config, target, fileInfo, mediaId, log) {
|
|
772
781
|
try {
|
|
773
|
-
const token = await (await import("./utils-
|
|
774
|
-
const { DINGTALK_API } = await import("./utils-
|
|
782
|
+
const token = await (await import("./utils-Bwz2Fls_.mjs")).getAccessToken(config);
|
|
783
|
+
const { DINGTALK_API } = await import("./utils-Bwz2Fls_.mjs");
|
|
775
784
|
const resolvedFileName = fileInfo.fileName || path.basename(fileInfo.path);
|
|
776
785
|
const msgParam = {
|
|
777
786
|
mediaId,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount } from "./accounts-BAzdqkAV.mjs";
|
|
2
|
-
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-
|
|
3
|
-
import { n as createLoggerFromConfig } from "./logger-
|
|
2
|
+
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-DbqGRyhI.mjs";
|
|
3
|
+
import { n as createLoggerFromConfig, r as isDingtalkDebug } from "./logger-CnBTcwyq.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-CK0Wz3Ze.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 { r as trackBackgroundWork } from "./background-work-
|
|
10
|
-
import "./chunk-upload-
|
|
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-
|
|
12
|
-
import { n as lookupCardContent, t as collectCardLookupIds } from "./card-content-cache-
|
|
9
|
+
import { r as trackBackgroundWork } from "./background-work-DkaSDqWk.mjs";
|
|
10
|
+
import "./chunk-upload-CIU5G_ay.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-BfIYs1yC.mjs";
|
|
12
|
+
import { n as lookupCardContent, t as collectCardLookupIds } from "./card-content-cache-DT_OYThh.mjs";
|
|
13
13
|
import * as fs from "fs";
|
|
14
14
|
import * as path from "path";
|
|
15
15
|
import * as os from "node:os";
|
|
@@ -63,7 +63,7 @@ function resolveAgentWorkspaceDir(cfg, agentId) {
|
|
|
63
63
|
* 直接复用 media.ts 权威实现,避免 reply 路径与 message 路径两套逻辑分叉。
|
|
64
64
|
*/
|
|
65
65
|
async function processLocalImages(content, oapiToken, log) {
|
|
66
|
-
const { processLocalImages: impl } = await import("./media-
|
|
66
|
+
const { processLocalImages: impl } = await import("./media-DDtX37T2.mjs");
|
|
67
67
|
return impl(content, oapiToken, log);
|
|
68
68
|
}
|
|
69
69
|
//#endregion
|
|
@@ -606,22 +606,20 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
606
606
|
type: "group",
|
|
607
607
|
openConversationId: conversationId
|
|
608
608
|
};
|
|
609
|
-
console.log(`[DingTalk][LocalImage] closeStreaming 开始媒体处理 | target=${JSON.stringify(target)} hasToken=${!!oapiToken} textLen=${finalText.length}`);
|
|
610
609
|
const mdImgCount = (finalText.match(/!\[[^\]]*\]\([^)]+\)/g) || []).length;
|
|
611
|
-
|
|
610
|
+
if (mdImgCount > 0 || isDingtalkDebug()) log.info(`[DingTalk][LocalImage] closeStreaming 媒体处理 | target=${JSON.stringify(target)} hasToken=${!!oapiToken} textLen=${finalText.length} mdImgs=${mdImgCount}`);
|
|
612
611
|
if (oapiToken) {
|
|
613
612
|
const beforeImg = finalText;
|
|
614
613
|
finalText = await processLocalImages(finalText, oapiToken, log);
|
|
615
|
-
if (beforeImg !== finalText)
|
|
614
|
+
if (beforeImg !== finalText) log.info(`[DingTalk][LocalImage] closeStreaming 图片已处理 | ${beforeImg.length}→${finalText.length}`);
|
|
616
615
|
try {
|
|
617
|
-
const { hasResidualLocalMdImagesOutsideCode } = await import("./media-
|
|
618
|
-
if (hasResidualLocalMdImagesOutsideCode(finalText))
|
|
616
|
+
const { hasResidualLocalMdImagesOutsideCode } = await import("./media-DDtX37T2.mjs");
|
|
617
|
+
if (hasResidualLocalMdImagesOutsideCode(finalText)) log.warn(`[DingTalk][LocalImage] closeStreaming 定稿后仍有代码块外的本地 MD 图未上传,钉钉可能灰图`);
|
|
619
618
|
} catch {}
|
|
620
619
|
finalText = await processVideoMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
621
620
|
finalText = await processAudioMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
622
621
|
finalText = await uploadAndReplaceFileMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
623
|
-
|
|
624
|
-
const { processRawMediaPaths } = await import("./media-BqpUGCni.mjs");
|
|
622
|
+
const { processRawMediaPaths } = await import("./media-DDtX37T2.mjs");
|
|
625
623
|
finalText = await processRawMediaPaths(finalText, account.config, oapiToken, log, target);
|
|
626
624
|
log.info(`[DingTalk][closeStreaming] processRawMediaPaths 处理完成`);
|
|
627
625
|
} else log.warn(`[DingTalk][closeStreaming] oapiToken 为空,跳过媒体处理`);
|
|
@@ -774,11 +772,8 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
774
772
|
onSkip: (payload, info) => {
|
|
775
773
|
const raw = readPayloadRawError(payload);
|
|
776
774
|
const isErr = Boolean(payload?.isError);
|
|
777
|
-
if (!raw && !isErr)
|
|
778
|
-
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
log.warn(`[DingTalk][onSkip] kind=${info?.kind} reason=${info?.reason} isError=${isErr} rawLen=${raw.length}(群聊静默失败常见)`);
|
|
775
|
+
if (!raw && !isErr) return;
|
|
776
|
+
log.warn(`[DingTalk][onSkip] 群聊静默失败 kind=${info?.kind} reason=${info?.reason} rawLen=${raw.length}`);
|
|
782
777
|
captureErrorFromPayload(payload, `onSkip:${info?.kind ?? "?"}:${info?.reason ?? "?"}`);
|
|
783
778
|
},
|
|
784
779
|
onReplyStart: () => {
|
|
@@ -846,7 +841,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
846
841
|
const oapiToken = await getOapiAccessToken(account.config);
|
|
847
842
|
if (oapiToken) {
|
|
848
843
|
log.info(`[DingTalk][deliver] 检测到 final 响应,准备处理裸露文件路径`);
|
|
849
|
-
const { processRawMediaPaths } = await import("./media-
|
|
844
|
+
const { processRawMediaPaths } = await import("./media-DDtX37T2.mjs");
|
|
850
845
|
text = await processRawMediaPaths(text, account.config, oapiToken, log, target);
|
|
851
846
|
log.info(`[DingTalk][deliver] 裸露文件路径处理完成`);
|
|
852
847
|
}
|
|
@@ -976,7 +971,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
976
971
|
currentToolLine = "";
|
|
977
972
|
await startStreaming();
|
|
978
973
|
if (currentCardTarget) try {
|
|
979
|
-
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-
|
|
974
|
+
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-C0Oaf0z-.mjs");
|
|
980
975
|
await enqueueCardStream(buildCardContent().replace(FILE_MARKER_PATTERN, "").replace(VIDEO_MARKER_PATTERN, "").replace(AUDIO_MARKER_PATTERN, "").trim(), { force: true });
|
|
981
976
|
} catch (err) {
|
|
982
977
|
if (!isQpsLimitError(err)) log.error(`[DingTalk][onPartialReply] final-marker 刷卡失败:${err.message}`);
|
|
@@ -989,7 +984,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
989
984
|
if (payload.text?.trim()) hasModelStreamText = true;
|
|
990
985
|
currentToolLine = "";
|
|
991
986
|
try {
|
|
992
|
-
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-
|
|
987
|
+
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-C0Oaf0z-.mjs");
|
|
993
988
|
await enqueueCardStream(buildCardContent().replace(FILE_MARKER_PATTERN, "").replace(VIDEO_MARKER_PATTERN, "").replace(AUDIO_MARKER_PATTERN, "").trim());
|
|
994
989
|
} catch (err) {
|
|
995
990
|
if (isQpsLimitError(err)) log.warn(`[DingTalk][onPartialReply] QPS 限流,已排队尾随重试 latestLen=${latestCardContent.length}`);
|
|
@@ -1184,8 +1179,9 @@ function extractQuotedMsgText(container, maxDepth, conversationId) {
|
|
|
1184
1179
|
conversationId,
|
|
1185
1180
|
allowConversationRecent: false
|
|
1186
1181
|
}) || "";
|
|
1187
|
-
if (cardText)
|
|
1188
|
-
|
|
1182
|
+
if (cardText) {
|
|
1183
|
+
if (isDingtalkDebug()) console.log(`[DingTalk][Quote] interactiveCard 已从 CardCache 精确回填 | ids=${ids.join(",") || "-"} len=${cardText.length}`);
|
|
1184
|
+
} else if (isDingtalkDebug()) {
|
|
1189
1185
|
const dump = JSON.stringify(repliedMsg ?? {}).slice(0, 800);
|
|
1190
1186
|
console.log(`[DingTalk][Quote] interactiveCard 无正文且缓存未命中(未使用会话最近兜底) | conv=${conversationId || "-"} keys=${Object.keys(contentObj || {}).join(",")} repliedMsg=${dump}`);
|
|
1191
1187
|
}
|
|
@@ -1208,13 +1204,13 @@ function extractQuotedMsgText(container, maxDepth, conversationId) {
|
|
|
1208
1204
|
});
|
|
1209
1205
|
if (cached) {
|
|
1210
1206
|
bodyText = cached;
|
|
1211
|
-
console.log(`[DingTalk][Quote] msgType=${msgType} 已从 CardCache 精确回填 | ids=${ids.join(",") || "-"} len=${cached.length}`);
|
|
1207
|
+
if (isDingtalkDebug()) console.log(`[DingTalk][Quote] msgType=${msgType} 已从 CardCache 精确回填 | ids=${ids.join(",") || "-"} len=${cached.length}`);
|
|
1212
1208
|
} else if (/card|interactive/i.test(String(msgType || "")) || ids.length > 0) {
|
|
1213
1209
|
bodyText = "钉钉卡片消息。";
|
|
1214
|
-
console.log(`[DingTalk][Quote] 未知卡片类引用 msgType=${msgType} 已简化占位 | keys=${Object.keys(contentObj || {}).join(",")}`);
|
|
1215
|
-
} else console.log(`[DingTalk][Quote] 未知引用类型 msgType=${msgType} keys=${Object.keys(contentObj || {}).join(",")} dump=${JSON.stringify(repliedMsg ?? {}).slice(0, 600)}`);
|
|
1210
|
+
if (isDingtalkDebug()) console.log(`[DingTalk][Quote] 未知卡片类引用 msgType=${msgType} 已简化占位 | keys=${Object.keys(contentObj || {}).join(",")}`);
|
|
1211
|
+
} else if (isDingtalkDebug()) console.log(`[DingTalk][Quote] 未知引用类型 msgType=${msgType} keys=${Object.keys(contentObj || {}).join(",")} dump=${JSON.stringify(repliedMsg ?? {}).slice(0, 600)}`);
|
|
1216
1212
|
} catch {
|
|
1217
|
-
console.log(`[DingTalk][Quote] 未知引用类型 msgType=${msgType} keys=${Object.keys(contentObj || {}).join(",")}`);
|
|
1213
|
+
if (isDingtalkDebug()) console.log(`[DingTalk][Quote] 未知引用类型 msgType=${msgType} keys=${Object.keys(contentObj || {}).join(",")}`);
|
|
1218
1214
|
}
|
|
1219
1215
|
break;
|
|
1220
1216
|
}
|
|
@@ -2320,7 +2316,7 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2320
2316
|
finalText = await processVideoMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2321
2317
|
finalText = await processAudioMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2322
2318
|
finalText = await uploadAndReplaceFileMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2323
|
-
const { processRawMediaPaths } = await import("./media-
|
|
2319
|
+
const { processRawMediaPaths } = await import("./media-DDtX37T2.mjs");
|
|
2324
2320
|
finalText = await processRawMediaPaths(finalText, config, oapiToken, log, mediaTarget);
|
|
2325
2321
|
}
|
|
2326
2322
|
const textToSend = finalText.trim() || "✅ 任务执行完成(无文本输出)";
|