@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
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
uploadAndReplaceFileMarkers
|
|
57
57
|
} from "../services/media/index.ts";
|
|
58
58
|
import { sendProactive, type AICardTarget } from "../services/messaging/index.ts";
|
|
59
|
-
import { createAICardForTarget, streamAICard, registerActiveCard, type AICardInstance } from "../services/messaging/card.ts";
|
|
59
|
+
import { createAICardForTarget, streamAICard, finishAICard, registerActiveCard, type AICardInstance } from "../services/messaging/card.ts";
|
|
60
60
|
import {
|
|
61
61
|
collectCardLookupIds,
|
|
62
62
|
lookupCardContent,
|
|
@@ -66,11 +66,19 @@ import { createDingtalkReplyDispatcher, matchModelErrorText } from "../reply-dis
|
|
|
66
66
|
import { normalizeSlashCommand } from "../utils/session.ts";
|
|
67
67
|
import { getDingtalkRuntime } from "../runtime.ts";
|
|
68
68
|
import { dingtalkHttp } from '../utils/http-client.ts';
|
|
69
|
-
import { createLoggerFromConfig } from '../utils/index.ts';
|
|
69
|
+
import { createLoggerFromConfig, isDingtalkDebug } from '../utils/index.ts';
|
|
70
|
+
import { trackBackgroundWork } from "../utils/background-work.ts";
|
|
70
71
|
import * as fs from 'fs';
|
|
71
72
|
import * as path from 'path';
|
|
72
73
|
import * as os from 'os';
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* 单条会话队列任务最长执行时间。
|
|
77
|
+
* 503 重试 + 工具循环偶发挂死时,若不超时,后续同会话消息会永远堵在 previousTask 上,
|
|
78
|
+
* 表现:连发多条「网关没反应 / 不贴表情」(表情在排队 ACK 或 dequeued 后才贴)。
|
|
79
|
+
*/
|
|
80
|
+
const SESSION_QUEUE_TASK_TIMEOUT_MS = 3 * 60 * 1000; // 3 分钟
|
|
81
|
+
|
|
74
82
|
// ============ 常量 ============
|
|
75
83
|
|
|
76
84
|
const AI_CARD_TEMPLATE_ID = '02fcf2f4-5e02-4a85-b672-46d1f715543e.schema';
|
|
@@ -350,11 +358,13 @@ function extractQuotedMsgText(
|
|
|
350
358
|
allowConversationRecent: false,
|
|
351
359
|
}) || '';
|
|
352
360
|
if (cardText) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
361
|
+
if (isDingtalkDebug()) {
|
|
362
|
+
console.log(
|
|
363
|
+
`[DingTalk][Quote] interactiveCard 已从 CardCache 精确回填 | ids=${ids.join(',') || '-'} len=${cardText.length}`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
} else if (isDingtalkDebug()) {
|
|
367
|
+
// 调试:dump 载荷确认钉钉给了哪些字段
|
|
358
368
|
const dump = JSON.stringify(repliedMsg ?? {}).slice(0, 800);
|
|
359
369
|
console.log(
|
|
360
370
|
`[DingTalk][Quote] interactiveCard 无正文且缓存未命中(未使用会话最近兜底) | conv=${conversationId || '-'} keys=${Object.keys(contentObj || {}).join(',')} repliedMsg=${dump}`,
|
|
@@ -390,26 +400,32 @@ function extractQuotedMsgText(
|
|
|
390
400
|
});
|
|
391
401
|
if (cached) {
|
|
392
402
|
bodyText = cached;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
403
|
+
if (isDingtalkDebug()) {
|
|
404
|
+
console.log(
|
|
405
|
+
`[DingTalk][Quote] msgType=${msgType} 已从 CardCache 精确回填 | ids=${ids.join(',') || '-'} len=${cached.length}`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
396
408
|
} else if (
|
|
397
409
|
/card|interactive/i.test(String(msgType || "")) ||
|
|
398
410
|
ids.length > 0
|
|
399
411
|
) {
|
|
400
412
|
bodyText = "钉钉卡片消息。";
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
413
|
+
if (isDingtalkDebug()) {
|
|
414
|
+
console.log(
|
|
415
|
+
`[DingTalk][Quote] 未知卡片类引用 msgType=${msgType} 已简化占位 | keys=${Object.keys(contentObj || {}).join(',')}`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
} else if (isDingtalkDebug()) {
|
|
405
419
|
console.log(
|
|
406
420
|
`[DingTalk][Quote] 未知引用类型 msgType=${msgType} keys=${Object.keys(contentObj || {}).join(',')} dump=${JSON.stringify(repliedMsg ?? {}).slice(0, 600)}`,
|
|
407
421
|
);
|
|
408
422
|
}
|
|
409
423
|
} catch {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
424
|
+
if (isDingtalkDebug()) {
|
|
425
|
+
console.log(
|
|
426
|
+
`[DingTalk][Quote] 未知引用类型 msgType=${msgType} keys=${Object.keys(contentObj || {}).join(',')}`,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
413
429
|
}
|
|
414
430
|
}
|
|
415
431
|
break;
|
|
@@ -1645,6 +1661,10 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
|
|
|
1645
1661
|
}
|
|
1646
1662
|
|
|
1647
1663
|
// ===== 使用 SDK 的 dispatchReplyFromConfig =====
|
|
1664
|
+
// 提升到 try 外:catch 需要 finish 即时建卡;replyOptions.onError 不存在(OpenClaw 不导出)
|
|
1665
|
+
let earlyCard: AICardInstance | undefined = params.preCreatedCard;
|
|
1666
|
+
let finalizeDispatchError: ((error: unknown, kind?: string) => Promise<void>) | undefined;
|
|
1667
|
+
|
|
1648
1668
|
try {
|
|
1649
1669
|
const core = getDingtalkRuntime();
|
|
1650
1670
|
|
|
@@ -1736,7 +1756,6 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
|
|
|
1736
1756
|
const skipAICardForCard = !isDirect && (groupReplyModeForCard === 'text' || groupReplyModeForCard === 'markdown');
|
|
1737
1757
|
const streamingEnabledForCard = !skipAICardForCard && (config as any).streaming !== false;
|
|
1738
1758
|
|
|
1739
|
-
let earlyCard: AICardInstance | undefined = params.preCreatedCard;
|
|
1740
1759
|
if (!earlyCard && streamingEnabledForCard && !asyncMode) {
|
|
1741
1760
|
try {
|
|
1742
1761
|
const earlyTarget: AICardTarget = isDirect
|
|
@@ -1757,7 +1776,13 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
|
|
|
1757
1776
|
}
|
|
1758
1777
|
|
|
1759
1778
|
// 创建 reply dispatcher,使用解析后的 agentId
|
|
1760
|
-
const {
|
|
1779
|
+
const {
|
|
1780
|
+
dispatcher,
|
|
1781
|
+
replyOptions,
|
|
1782
|
+
markDispatchIdle,
|
|
1783
|
+
getAsyncModeResponse,
|
|
1784
|
+
finalizeDispatchError: finalizeErr,
|
|
1785
|
+
} = createDingtalkReplyDispatcher({
|
|
1761
1786
|
cfg,
|
|
1762
1787
|
agentId: matchedAgentId, // ✅ 使用手动匹配的 agentId
|
|
1763
1788
|
runtime: runtime as RuntimeEnv,
|
|
@@ -1770,6 +1795,7 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
|
|
|
1770
1795
|
asyncMode,
|
|
1771
1796
|
preCreatedCard: earlyCard,
|
|
1772
1797
|
});
|
|
1798
|
+
finalizeDispatchError = finalizeErr;
|
|
1773
1799
|
|
|
1774
1800
|
// ===== 注入当前 bot 的 clientId(用于 dws CLI --client-id 参数) =====
|
|
1775
1801
|
// 多 bot 场景下,AI 需要知道当前对话属于哪个 bot,以便在调用
|
|
@@ -1905,32 +1931,59 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
|
|
|
1905
1931
|
|
|
1906
1932
|
} catch (err: any) {
|
|
1907
1933
|
log?.error?.(`SDK dispatch 失败: ${err.message}`);
|
|
1908
|
-
|
|
1909
|
-
// 解析上游错误文本(例如 503 No available channel),匹配中文提示文案
|
|
1910
|
-
const errorText = matchModelErrorText(err.message, { includeCatchAll: true }) ?? `⚠️ 抱歉,处理请求时出错: ${err.message}`;
|
|
1911
1934
|
|
|
1912
|
-
//
|
|
1913
|
-
|
|
1935
|
+
// 统一错误定稿:优先走 dispatcher 的 finalizeDispatchError(finish AI Card + 中文提示)。
|
|
1936
|
+
// 注意:OpenClaw createReplyDispatcherWithTyping 返回的 replyOptions 不含 onError,
|
|
1937
|
+
// 绝不能依赖 replyOptions.onError(会永远 undefined / 甚至 catch 作用域 ReferenceError)。
|
|
1938
|
+
if (finalizeDispatchError) {
|
|
1914
1939
|
try {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1940
|
+
log?.info?.(`[DingTalk] 统一错误定稿:调用 finalizeDispatchError 处理同步异常`);
|
|
1941
|
+
await finalizeDispatchError(err, "invoke");
|
|
1942
|
+
} catch (finalizeErr: any) {
|
|
1943
|
+
log?.error?.(`[DingTalk] finalizeDispatchError 失败: ${finalizeErr?.message || finalizeErr}`);
|
|
1918
1944
|
}
|
|
1919
1945
|
} else {
|
|
1920
|
-
//
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1946
|
+
// dispatcher 尚未创建就失败:直接 finish 即时建卡,再不行发纯文本
|
|
1947
|
+
const errorText =
|
|
1948
|
+
matchModelErrorText(err.message, { includeCatchAll: true }) ??
|
|
1949
|
+
`⚠️ 抱歉,处理请求时出错: ${err.message}`;
|
|
1950
|
+
let cardSuccess = false;
|
|
1951
|
+
if (earlyCard) {
|
|
1952
|
+
try {
|
|
1953
|
+
await finishAICard(
|
|
1954
|
+
earlyCard,
|
|
1955
|
+
errorText,
|
|
1956
|
+
config,
|
|
1957
|
+
log,
|
|
1958
|
+
undefined,
|
|
1959
|
+
undefined,
|
|
1960
|
+
data.conversationId,
|
|
1961
|
+
);
|
|
1962
|
+
cardSuccess = true;
|
|
1963
|
+
log?.info?.(`[DingTalk] 极端兜底:finishAICard 错误卡成功`);
|
|
1964
|
+
} catch (cardErr: any) {
|
|
1965
|
+
log?.error?.(`更新错误卡片失败: ${cardErr.message}`);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
if (!cardSuccess) {
|
|
1969
|
+
log?.warn?.(`[DingTalk] 无可用错误定稿路径,走纯文本发送`);
|
|
1970
|
+
try {
|
|
1971
|
+
const token = await getAccessToken(config);
|
|
1972
|
+
const body: any = {
|
|
1973
|
+
msgtype: "text",
|
|
1974
|
+
text: { content: errorText },
|
|
1975
|
+
};
|
|
1976
|
+
if (!isDirect) body.at = { atUserIds: [senderId], isAtAll: false };
|
|
1977
|
+
|
|
1978
|
+
await dingtalkHttp.post(sessionWebhook, body, {
|
|
1979
|
+
headers: {
|
|
1980
|
+
"x-acs-dingtalk-access-token": token,
|
|
1981
|
+
"Content-Type": "application/json",
|
|
1982
|
+
},
|
|
1983
|
+
});
|
|
1984
|
+
} catch (fallbackErr: any) {
|
|
1985
|
+
log?.error?.(`错误消息发送也失败: ${fallbackErr.message}`);
|
|
1986
|
+
}
|
|
1934
1987
|
}
|
|
1935
1988
|
}
|
|
1936
1989
|
}
|
|
@@ -2007,89 +2060,131 @@ export async function handleDingTalkMessage(params: HandleMessageParams): Promis
|
|
|
2007
2060
|
// 更新会话活跃时间
|
|
2008
2061
|
sessionLastActivity.set(queueKey, Date.now());
|
|
2009
2062
|
|
|
2010
|
-
//
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
//
|
|
2063
|
+
// ═══════════════════════════════════════════════════════════
|
|
2064
|
+
// ⚠️ 关键:下面「读 previous → 建 currentTask → set」之间禁止任何 await。
|
|
2065
|
+
// JS 虽是单线程,但 await 会让出执行权;若在 set 之前 await 繁忙 ACK(最多 3s),
|
|
2066
|
+
// 并发第 2/3 条会看到「队列仍只有第 1 条」甚至「空闲」,表现为:
|
|
2067
|
+
// - 任务进行中连发却不弹排队卡(要发两三次才命中 isQueueBusy)
|
|
2068
|
+
// - 多条消息错误地 chain 到同一 previous,乱序/并行
|
|
2069
|
+
// ═══════════════════════════════════════════════════════════
|
|
2014
2070
|
const previousTask = sessionQueues.get(queueKey) || Promise.resolve();
|
|
2071
|
+
const isQueueBusy = sessionQueues.has(queueKey);
|
|
2015
2072
|
|
|
2016
|
-
//
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2073
|
+
// 异步 ACK 写回的卡片:主任务 dequeue 时再读,避免 await ACK 再入队
|
|
2074
|
+
const cardHolder: { card?: AICardInstance; ackDone: boolean } = {
|
|
2075
|
+
card: undefined,
|
|
2076
|
+
ackDone: !isQueueBusy,
|
|
2077
|
+
};
|
|
2021
2078
|
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2079
|
+
const currentTask = previousTask
|
|
2080
|
+
.then(async () => {
|
|
2081
|
+
log?.info?.(
|
|
2082
|
+
`[队列] 开始处理消息,queueKey=${queueKey} wasBusy=${isQueueBusy} timeoutMs=${SESSION_QUEUE_TASK_TIMEOUT_MS}`,
|
|
2083
|
+
);
|
|
2025
2084
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
fallbackToNormal: true,
|
|
2033
|
-
});
|
|
2034
|
-
log?.info?.(`[队列] 队列繁忙,已发送普通文本 ACK(groupReplyMode=${groupReplyMode})`);
|
|
2035
|
-
} catch (ackErr: any) {
|
|
2036
|
-
log?.warn?.(`[队列] 发送普通 ACK 失败: ${ackErr?.message || ackErr}`);
|
|
2085
|
+
// 若排队 ACK 仍在建卡,最多再等 2s 拿 preCreatedCard,避免丢复用
|
|
2086
|
+
if (isQueueBusy && !cardHolder.ackDone) {
|
|
2087
|
+
const waitStart = Date.now();
|
|
2088
|
+
while (!cardHolder.ackDone && Date.now() - waitStart < 2_000) {
|
|
2089
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
2090
|
+
}
|
|
2037
2091
|
}
|
|
2038
|
-
// text/markdown 模式不贴🤔表情(表情是 AI Card 场景的配套功能)
|
|
2039
|
-
} else {
|
|
2040
|
-
// aicard 模式:创建 AI Card 显示排队 ACK
|
|
2041
|
-
const cardTarget: AICardTarget = isDirect
|
|
2042
|
-
? { type: 'user', userId: senderId }
|
|
2043
|
-
: { type: 'group', openConversationId: data.conversationId };
|
|
2044
2092
|
|
|
2093
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
2045
2094
|
try {
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2095
|
+
await Promise.race([
|
|
2096
|
+
handleDingTalkMessageInternal({
|
|
2097
|
+
...params,
|
|
2098
|
+
preCreatedCard: cardHolder.card,
|
|
2099
|
+
emotionAlreadyAdded: true,
|
|
2100
|
+
}),
|
|
2101
|
+
new Promise<never>((_, reject) => {
|
|
2102
|
+
timeoutId = setTimeout(() => {
|
|
2103
|
+
reject(
|
|
2104
|
+
new Error(
|
|
2105
|
+
`会话队列任务超时(${SESSION_QUEUE_TASK_TIMEOUT_MS}ms),释放队列以免堵死后续消息`,
|
|
2106
|
+
),
|
|
2107
|
+
);
|
|
2108
|
+
}, SESSION_QUEUE_TASK_TIMEOUT_MS);
|
|
2109
|
+
}),
|
|
2110
|
+
]);
|
|
2111
|
+
log?.info?.(`[队列] 消息处理完成,queueKey=${queueKey}`);
|
|
2112
|
+
} finally {
|
|
2113
|
+
if (timeoutId !== undefined) clearTimeout(timeoutId);
|
|
2061
2114
|
}
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
// 创建当前消息的处理任务
|
|
2066
|
-
const currentTask = previousTask
|
|
2067
|
-
.then(async () => {
|
|
2068
|
-
log?.info?.(`[队列] 开始处理消息,queueKey=${queueKey}`);
|
|
2069
|
-
await handleDingTalkMessageInternal({ ...params, preCreatedCard, emotionAlreadyAdded: isQueueBusy });
|
|
2070
|
-
log?.info?.(`[队列] 消息处理完成,queueKey=${queueKey}`);
|
|
2071
2115
|
})
|
|
2072
2116
|
.catch((err: any) => {
|
|
2073
2117
|
log?.error?.(`[队列] 消息处理异常,queueKey=${queueKey}, error=${err.message}`);
|
|
2074
|
-
// 不抛出错误,避免阻塞后续消息
|
|
2075
2118
|
})
|
|
2076
2119
|
.finally(() => {
|
|
2077
|
-
// 如果当前任务是队列中的最后一个任务,清理队列
|
|
2078
2120
|
if (sessionQueues.get(queueKey) === currentTask) {
|
|
2079
2121
|
sessionQueues.delete(queueKey);
|
|
2080
2122
|
log?.info?.(`[队列] 队列已清空,queueKey=${queueKey}`);
|
|
2081
2123
|
}
|
|
2082
2124
|
});
|
|
2083
|
-
|
|
2084
|
-
//
|
|
2125
|
+
|
|
2126
|
+
// ★ 立刻占槽:必须在任何 await 之前,保证并发入站能看到 isQueueBusy
|
|
2085
2127
|
sessionQueues.set(queueKey, currentTask);
|
|
2128
|
+
trackBackgroundWork(currentTask, queueKey);
|
|
2129
|
+
log?.info?.(
|
|
2130
|
+
`[队列] 已登记 queueKey=${queueKey} busy=${isQueueBusy} depth≈pending`,
|
|
2131
|
+
);
|
|
2132
|
+
|
|
2133
|
+
// ===== 登记之后再做用户反馈(可异步,不再阻塞占槽)=====
|
|
2134
|
+
addEmotionReply(config, data, log).catch((err: any) => {
|
|
2135
|
+
log?.warn?.(`[队列] 即时贴表情失败: ${err?.message || err}`);
|
|
2136
|
+
});
|
|
2086
2137
|
|
|
2087
|
-
|
|
2088
|
-
|
|
2138
|
+
if (isQueueBusy) {
|
|
2139
|
+
const ackPhrases = QUEUE_BUSY_ACK_PHRASES;
|
|
2140
|
+
const ackText = ackPhrases[Math.floor(Math.random() * ackPhrases.length)];
|
|
2141
|
+
const groupReplyMode = config.groupReplyMode || "aicard";
|
|
2142
|
+
const skipAICard =
|
|
2143
|
+
!isDirect && (groupReplyMode === "text" || groupReplyMode === "markdown");
|
|
2144
|
+
|
|
2145
|
+
// 火并忘记:不 await,避免拖慢本回调、也不挡住下一条消息的占槽
|
|
2146
|
+
void (async () => {
|
|
2147
|
+
try {
|
|
2148
|
+
if (skipAICard) {
|
|
2149
|
+
await sendProactive(
|
|
2150
|
+
config,
|
|
2151
|
+
{ openConversationId: data.conversationId },
|
|
2152
|
+
ackText,
|
|
2153
|
+
{ msgType: "text", useAICard: false, fallbackToNormal: true },
|
|
2154
|
+
);
|
|
2155
|
+
log?.info?.(
|
|
2156
|
+
`[队列] 队列繁忙,已发送普通文本 ACK(groupReplyMode=${groupReplyMode})`,
|
|
2157
|
+
);
|
|
2158
|
+
} else {
|
|
2159
|
+
const cardTarget: AICardTarget = isDirect
|
|
2160
|
+
? { type: "user", userId: senderId }
|
|
2161
|
+
: { type: "group", openConversationId: data.conversationId };
|
|
2162
|
+
const card = await createAICardForTarget(config, cardTarget, log);
|
|
2163
|
+
if (card) {
|
|
2164
|
+
await streamAICard(card, ackText, false, config, log);
|
|
2165
|
+
cardHolder.card = card;
|
|
2166
|
+
log?.info?.(
|
|
2167
|
+
`[队列] 队列繁忙,已创建排队 ACK Card,cardInstanceId=${card.cardInstanceId}`,
|
|
2168
|
+
);
|
|
2169
|
+
} else {
|
|
2170
|
+
log?.warn?.(`[队列] 创建排队 ACK Card 失败(返回 null)`);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
} catch (ackErr: any) {
|
|
2174
|
+
log?.warn?.(`[队列] 繁忙 ACK 异常: ${ackErr?.message || ackErr}`);
|
|
2175
|
+
} finally {
|
|
2176
|
+
cardHolder.ackDone = true;
|
|
2177
|
+
}
|
|
2178
|
+
})();
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
// 立即返回,不阻塞 WebSocket 收下一条
|
|
2089
2182
|
} catch (err: any) {
|
|
2090
2183
|
log?.error?.(`[队列] 队列管理异常,直接处理: ${err.message}`);
|
|
2091
|
-
|
|
2092
|
-
|
|
2184
|
+
const fallback = handleDingTalkMessageInternal(params).catch((e: any) => {
|
|
2185
|
+
log?.error?.(`[队列] 直接处理也失败: ${e.message}`);
|
|
2186
|
+
});
|
|
2187
|
+
trackBackgroundWork(fallback, "queue-fallback");
|
|
2093
2188
|
}
|
|
2094
2189
|
}
|
|
2095
2190
|
|