@soimy/dingtalk 3.6.7 → 3.6.9
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/dist/index.js +702 -183
- package/dist/index.js.map +4 -4
- package/dist/src/card/ask-user-question.d.ts.map +1 -1
- package/dist/src/card-service.d.ts +4 -1
- package/dist/src/card-service.d.ts.map +1 -1
- package/dist/src/gateway/channel-gateway.d.ts.map +1 -1
- package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts +23 -0
- package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts.map +1 -0
- package/dist/src/gateway/inbound-session-queue.d.ts +46 -0
- package/dist/src/gateway/inbound-session-queue.d.ts.map +1 -0
- package/dist/src/gateway/reply-session-conflict.d.ts +22 -0
- package/dist/src/gateway/reply-session-conflict.d.ts.map +1 -0
- package/dist/src/inbound-handler.d.ts.map +1 -1
- package/dist/src/onboarding.d.ts.map +1 -1
- package/dist/src/targeting/agent-routing.d.ts +9 -0
- package/dist/src/targeting/agent-routing.d.ts.map +1 -1
- package/dist/src/types.d.ts +22 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/card/ask-user-question.ts +18 -3
- package/src/card-service.ts +55 -3
- package/src/gateway/channel-gateway.ts +32 -30
- package/src/gateway/inbound-session-queue-dispatcher.ts +304 -0
- package/src/gateway/inbound-session-queue.ts +244 -0
- package/src/gateway/reply-session-conflict.ts +82 -0
- package/src/inbound-handler.ts +144 -20
- package/src/onboarding.ts +6 -2
- package/src/targeting/agent-routing.ts +17 -0
- package/src/types.ts +22 -0
package/dist/index.js
CHANGED
|
@@ -2681,7 +2681,7 @@ function normalizePendingState(parsed) {
|
|
|
2681
2681
|
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
|
|
2682
2682
|
pendingCards: records2.filter(
|
|
2683
2683
|
(entry) => Boolean(
|
|
2684
|
-
entry && typeof entry.accountId === "string" && typeof entry.cardInstanceId === "string" && (entry.outTrackId === void 0 || typeof entry.outTrackId === "string") && typeof entry.conversationId === "string" && (entry.lastContent === void 0 || typeof entry.lastContent === "string") && (entry.lastBlockListJson === void 0 || typeof entry.lastBlockListJson === "string") && (entry.streamLifecycleOpened === void 0 || typeof entry.streamLifecycleOpened === "boolean")
|
|
2684
|
+
entry && typeof entry.accountId === "string" && typeof entry.cardInstanceId === "string" && (entry.outTrackId === void 0 || typeof entry.outTrackId === "string") && (entry.processQueryKey === void 0 || typeof entry.processQueryKey === "string") && typeof entry.conversationId === "string" && (entry.lastContent === void 0 || typeof entry.lastContent === "string") && (entry.lastBlockListJson === void 0 || typeof entry.lastBlockListJson === "string") && (entry.streamLifecycleOpened === void 0 || typeof entry.streamLifecycleOpened === "boolean") && (entry.recoveryAction === void 0 || entry.recoveryAction === "finalize" || entry.recoveryAction === "recall")
|
|
2685
2685
|
)
|
|
2686
2686
|
)
|
|
2687
2687
|
};
|
|
@@ -2738,6 +2738,7 @@ function upsertPendingCard(card, storePath, log) {
|
|
|
2738
2738
|
accountId: card.accountId,
|
|
2739
2739
|
cardInstanceId: card.cardInstanceId,
|
|
2740
2740
|
outTrackId: card.outTrackId,
|
|
2741
|
+
processQueryKey: card.processQueryKey,
|
|
2741
2742
|
conversationId: card.conversationId,
|
|
2742
2743
|
contextConversationId: card.contextConversationId,
|
|
2743
2744
|
createdAt: card.createdAt,
|
|
@@ -2800,6 +2801,28 @@ function removePendingCardById(cardInstanceId, storePath, log) {
|
|
|
2800
2801
|
state.updatedAt = Date.now();
|
|
2801
2802
|
writePendingCardState(state, storePath, log);
|
|
2802
2803
|
}
|
|
2804
|
+
function retainPendingCardForRecovery(card, recoveryAction, log) {
|
|
2805
|
+
if (!card.accountId || !card.storePath) {
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
const state = readPendingCardState(card.storePath, log);
|
|
2809
|
+
const index = state.pendingCards.findIndex((item) => item.cardInstanceId === card.cardInstanceId);
|
|
2810
|
+
if (index < 0) {
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
const existing = state.pendingCards[index];
|
|
2814
|
+
state.pendingCards[index] = {
|
|
2815
|
+
...existing,
|
|
2816
|
+
state: AICardStatus.FAILED,
|
|
2817
|
+
lastUpdated: Date.now(),
|
|
2818
|
+
lastContent: card.lastStreamedContent ?? existing.lastContent,
|
|
2819
|
+
lastBlockListJson: card.lastBlockListJson ?? existing.lastBlockListJson,
|
|
2820
|
+
streamLifecycleOpened: card.streamLifecycleOpened,
|
|
2821
|
+
recoveryAction
|
|
2822
|
+
};
|
|
2823
|
+
state.updatedAt = Date.now();
|
|
2824
|
+
writePendingCardState(state, card.storePath, log);
|
|
2825
|
+
}
|
|
2803
2826
|
function listPendingCardsByAccount(accountId, storePath, log) {
|
|
2804
2827
|
const state = readPendingCardState(storePath, log);
|
|
2805
2828
|
return state.pendingCards.filter((item) => item.accountId === accountId);
|
|
@@ -2827,6 +2850,37 @@ async function ensureFreshToken(card, log) {
|
|
|
2827
2850
|
}
|
|
2828
2851
|
}
|
|
2829
2852
|
}
|
|
2853
|
+
async function sendTemplateMismatchNotification(card, text, log) {
|
|
2854
|
+
const config = card.config;
|
|
2855
|
+
if (!config) {
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
try {
|
|
2859
|
+
const token = await getAccessToken(config, log);
|
|
2860
|
+
const { targetId, isExplicitUser } = stripTargetPrefix(card.conversationId);
|
|
2861
|
+
const resolvedTarget = resolveOriginalPeerId(targetId);
|
|
2862
|
+
const isGroup = !isExplicitUser && resolvedTarget.startsWith("cid");
|
|
2863
|
+
const url = isGroup ? "https://api.dingtalk.com/v1.0/robot/groupMessages/send" : "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend";
|
|
2864
|
+
const payload = {
|
|
2865
|
+
robotCode: resolveRobotCode(config),
|
|
2866
|
+
msgKey: "sampleMarkdown",
|
|
2867
|
+
msgParam: JSON.stringify({ title: "OpenClaw \u63D0\u9192", text })
|
|
2868
|
+
};
|
|
2869
|
+
if (isGroup) {
|
|
2870
|
+
payload.openConversationId = resolvedTarget;
|
|
2871
|
+
} else {
|
|
2872
|
+
payload.userIds = [resolvedTarget];
|
|
2873
|
+
}
|
|
2874
|
+
await http_client_default({
|
|
2875
|
+
url,
|
|
2876
|
+
method: "POST",
|
|
2877
|
+
data: payload,
|
|
2878
|
+
headers: { "x-acs-dingtalk-access-token": token, "Content-Type": "application/json" }
|
|
2879
|
+
});
|
|
2880
|
+
} catch (sendErr) {
|
|
2881
|
+
log?.warn?.(`[DingTalk][AICard] Failed to send error notification to user: ${sendErr.message}`);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2830
2884
|
async function sendProactiveCardText(config, conversationId, content, log) {
|
|
2831
2885
|
try {
|
|
2832
2886
|
const card = await createAICard(config, conversationId, log, { persistPending: false });
|
|
@@ -2867,7 +2921,7 @@ async function finalizePendingCardsByAccount(config, accountId, reason, storePat
|
|
|
2867
2921
|
return 0;
|
|
2868
2922
|
}
|
|
2869
2923
|
const pendingCards = listPendingCardsByAccount(accountId, storePath, log).filter(
|
|
2870
|
-
(item) => !isCardInTerminalState(item.state)
|
|
2924
|
+
(item) => item.recoveryAction === "recall" && mode === "recover" || item.recoveryAction !== "recall" && (!isCardInTerminalState(item.state) || item.recoveryAction === "finalize")
|
|
2871
2925
|
);
|
|
2872
2926
|
if (pendingCards.length === 0) {
|
|
2873
2927
|
return 0;
|
|
@@ -2892,6 +2946,7 @@ async function finalizePendingCardsByAccount(config, accountId, reason, storePat
|
|
|
2892
2946
|
accountId: entry.accountId,
|
|
2893
2947
|
storePath,
|
|
2894
2948
|
outTrackId: entry.outTrackId,
|
|
2949
|
+
processQueryKey: entry.processQueryKey,
|
|
2895
2950
|
createdAt: entry.createdAt || Date.now(),
|
|
2896
2951
|
lastUpdated: entry.lastUpdated || Date.now(),
|
|
2897
2952
|
state: normalizeRecoveredState(entry.state),
|
|
@@ -2901,6 +2956,12 @@ async function finalizePendingCardsByAccount(config, accountId, reason, storePat
|
|
|
2901
2956
|
streamLifecycleOpened: entry.streamLifecycleOpened
|
|
2902
2957
|
};
|
|
2903
2958
|
try {
|
|
2959
|
+
if (entry.recoveryAction === "recall") {
|
|
2960
|
+
if (await recallAICardMessage(card, log)) {
|
|
2961
|
+
finalizedCount += 1;
|
|
2962
|
+
}
|
|
2963
|
+
continue;
|
|
2964
|
+
}
|
|
2904
2965
|
await finalizeStoppedAICard(card, {
|
|
2905
2966
|
reason,
|
|
2906
2967
|
previousContent: entry.lastContent,
|
|
@@ -3177,6 +3238,9 @@ async function commitAICardBlocks(card, options, log) {
|
|
|
3177
3238
|
} catch (err) {
|
|
3178
3239
|
const message = err instanceof Error ? err.message : String(err);
|
|
3179
3240
|
log?.error?.(`[DingTalk][AICard] Finalize via instances API failed: ${message}`);
|
|
3241
|
+
card.state = AICardStatus.FAILED;
|
|
3242
|
+
card.lastUpdated = Date.now();
|
|
3243
|
+
retainPendingCardForRecovery(card, "finalize", log);
|
|
3180
3244
|
throw err;
|
|
3181
3245
|
}
|
|
3182
3246
|
if (card.conversationId && options.content.trim() && card.accountId && card.processQueryKey) {
|
|
@@ -3196,6 +3260,35 @@ async function commitAICardBlocks(card, options, log) {
|
|
|
3196
3260
|
removePendingCard(card, log);
|
|
3197
3261
|
log?.info?.(`[DingTalk][AICard] Card finalized: outTrackId=${card.outTrackId || card.cardInstanceId} state=FINISHED`);
|
|
3198
3262
|
}
|
|
3263
|
+
async function streamAICard(card, content, finished = false, log, options = {}) {
|
|
3264
|
+
if (isCardInTerminalState(card.state)) {
|
|
3265
|
+
log?.debug?.(
|
|
3266
|
+
`[DingTalk][AICard] Skip stream update because card already terminal: outTrackId=${card.cardInstanceId} state=${card.state}`
|
|
3267
|
+
);
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
const template = DINGTALK_CARD_TEMPLATE;
|
|
3271
|
+
try {
|
|
3272
|
+
await putAICardStreamingField(card, template.contentKey, content, finished, log);
|
|
3273
|
+
card.lastStreamedContent = content;
|
|
3274
|
+
if (finished) {
|
|
3275
|
+
card.state = AICardStatus.FINISHED;
|
|
3276
|
+
removePendingCard(card, log);
|
|
3277
|
+
} else if (card.state === AICardStatus.PROCESSING) {
|
|
3278
|
+
card.state = AICardStatus.INPUTING;
|
|
3279
|
+
upsertPendingCard(card, card.storePath, log);
|
|
3280
|
+
}
|
|
3281
|
+
} catch (err) {
|
|
3282
|
+
card.state = AICardStatus.FAILED;
|
|
3283
|
+
card.lastUpdated = Date.now();
|
|
3284
|
+
retainPendingCardForRecovery(card, options.recoveryAction ?? "finalize", log);
|
|
3285
|
+
if (err.response?.status === 500 && err.response?.data?.code === "unknownError") {
|
|
3286
|
+
const errorMsg = "\u26A0\uFE0F **[DingTalk] AI Card \u4E32\u6D41\u66F4\u65B0\u5931\u8D25 (500 unknownError)**\n\n\u8FD9\u901A\u5E38\u8868\u793A\u5F53\u524D\u5185\u7F6E\u6A21\u677F\u5951\u7EA6\u4E0E\u9489\u9489\u4FA7\u6A21\u677F\u5B57\u6BB5\u4E0D\u4E00\u81F4\uFF0C\u5F53\u524D\u53CA\u540E\u7EED\u6D88\u606F\u5C06\u81EA\u52A8\u56DE\u9000\u4E3A Markdown \u53D1\u9001\u3002";
|
|
3287
|
+
await sendTemplateMismatchNotification(card, errorMsg, log);
|
|
3288
|
+
}
|
|
3289
|
+
throw err;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3199
3292
|
function getCardRecallTarget(card) {
|
|
3200
3293
|
const { targetId, isExplicitUser } = stripTargetPrefix(card.conversationId);
|
|
3201
3294
|
const resolvedTarget = resolveOriginalPeerId(targetId);
|
|
@@ -4944,6 +5037,130 @@ async function handleInboundCommandDispatch(params) {
|
|
|
4944
5037
|
return false;
|
|
4945
5038
|
}
|
|
4946
5039
|
|
|
5040
|
+
// src/gateway/inbound-session-queue.ts
|
|
5041
|
+
var SESSION_QUEUE_TTL_MS = 5 * 60 * 1e3;
|
|
5042
|
+
var SESSION_QUEUE_CLEANUP_INTERVAL_MS = 60 * 1e3;
|
|
5043
|
+
var MAX_INBOUND_SESSION_QUEUE_DEPTH = 8;
|
|
5044
|
+
var MAX_INBOUND_SESSION_QUEUE_WAIT_MS = 15 * 60 * 1e3;
|
|
5045
|
+
var sessionQueues = /* @__PURE__ */ new Map();
|
|
5046
|
+
var sessionLastActivity = /* @__PURE__ */ new Map();
|
|
5047
|
+
var sessionQueueDepths = /* @__PURE__ */ new Map();
|
|
5048
|
+
var cleanupTimer = null;
|
|
5049
|
+
var InboundSessionQueueWaitTimeoutError = class extends Error {
|
|
5050
|
+
constructor(queueKey) {
|
|
5051
|
+
super(`Inbound session queue wait timed out for ${queueKey}`);
|
|
5052
|
+
this.name = "InboundSessionQueueWaitTimeoutError";
|
|
5053
|
+
}
|
|
5054
|
+
};
|
|
5055
|
+
function ensureCleanupTimer() {
|
|
5056
|
+
if (cleanupTimer) {
|
|
5057
|
+
return;
|
|
5058
|
+
}
|
|
5059
|
+
cleanupTimer = setInterval(() => {
|
|
5060
|
+
const now2 = Date.now();
|
|
5061
|
+
for (const [key, lastSeen] of sessionLastActivity) {
|
|
5062
|
+
if (now2 - lastSeen > SESSION_QUEUE_TTL_MS && !sessionQueues.has(key)) {
|
|
5063
|
+
sessionLastActivity.delete(key);
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
}, SESSION_QUEUE_CLEANUP_INTERVAL_MS);
|
|
5067
|
+
if (typeof cleanupTimer?.unref === "function") {
|
|
5068
|
+
cleanupTimer.unref();
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
function isInboundSessionQueueBusy(queueKey) {
|
|
5072
|
+
return sessionQueues.has(queueKey);
|
|
5073
|
+
}
|
|
5074
|
+
function getInboundSessionQueueDepth(queueKey) {
|
|
5075
|
+
return sessionQueueDepths.get(queueKey) ?? 0;
|
|
5076
|
+
}
|
|
5077
|
+
function chainInboundSessionTask(queueKey, task, options = {}) {
|
|
5078
|
+
const hadPriorTask = sessionQueues.has(queueKey);
|
|
5079
|
+
const previousTail = sessionQueues.get(queueKey) ?? Promise.resolve();
|
|
5080
|
+
sessionLastActivity.set(queueKey, Date.now());
|
|
5081
|
+
sessionQueueDepths.set(queueKey, getInboundSessionQueueDepth(queueKey) + 1);
|
|
5082
|
+
ensureCleanupTimer();
|
|
5083
|
+
let timedOut = false;
|
|
5084
|
+
let timeout;
|
|
5085
|
+
let resolveWaitingCaller;
|
|
5086
|
+
let rejectWaitingCaller;
|
|
5087
|
+
const maxQueueWaitMs = Math.max(0, options.maxQueueWaitMs ?? 0);
|
|
5088
|
+
const caller = maxQueueWaitMs > 0 && hadPriorTask ? new Promise((resolve3, reject) => {
|
|
5089
|
+
resolveWaitingCaller = resolve3;
|
|
5090
|
+
rejectWaitingCaller = reject;
|
|
5091
|
+
timeout = setTimeout(() => {
|
|
5092
|
+
timedOut = true;
|
|
5093
|
+
reject(new InboundSessionQueueWaitTimeoutError(queueKey));
|
|
5094
|
+
}, maxQueueWaitMs);
|
|
5095
|
+
if (typeof timeout.unref === "function") {
|
|
5096
|
+
timeout.unref();
|
|
5097
|
+
}
|
|
5098
|
+
}) : void 0;
|
|
5099
|
+
if (caller) {
|
|
5100
|
+
void caller.catch(() => void 0);
|
|
5101
|
+
}
|
|
5102
|
+
const current = previousTail.then(() => {
|
|
5103
|
+
if (timeout) {
|
|
5104
|
+
clearTimeout(timeout);
|
|
5105
|
+
timeout = void 0;
|
|
5106
|
+
}
|
|
5107
|
+
if (timedOut) {
|
|
5108
|
+
throw new InboundSessionQueueWaitTimeoutError(queueKey);
|
|
5109
|
+
}
|
|
5110
|
+
return task();
|
|
5111
|
+
});
|
|
5112
|
+
const tail = current.then(
|
|
5113
|
+
() => void 0,
|
|
5114
|
+
() => {
|
|
5115
|
+
}
|
|
5116
|
+
);
|
|
5117
|
+
sessionQueues.set(queueKey, tail);
|
|
5118
|
+
const cleanup = () => {
|
|
5119
|
+
const nextDepth = Math.max(0, getInboundSessionQueueDepth(queueKey) - 1);
|
|
5120
|
+
if (nextDepth) {
|
|
5121
|
+
sessionQueueDepths.set(queueKey, nextDepth);
|
|
5122
|
+
} else {
|
|
5123
|
+
sessionQueueDepths.delete(queueKey);
|
|
5124
|
+
}
|
|
5125
|
+
if (sessionQueues.get(queueKey) === tail) {
|
|
5126
|
+
sessionQueues.delete(queueKey);
|
|
5127
|
+
sessionLastActivity.delete(queueKey);
|
|
5128
|
+
}
|
|
5129
|
+
};
|
|
5130
|
+
void current.then(cleanup, cleanup);
|
|
5131
|
+
if (!caller) {
|
|
5132
|
+
return current;
|
|
5133
|
+
}
|
|
5134
|
+
void current.then(
|
|
5135
|
+
(value) => {
|
|
5136
|
+
if (timeout) {
|
|
5137
|
+
clearTimeout(timeout);
|
|
5138
|
+
}
|
|
5139
|
+
resolveWaitingCaller?.(value);
|
|
5140
|
+
},
|
|
5141
|
+
(error) => {
|
|
5142
|
+
if (timeout) {
|
|
5143
|
+
clearTimeout(timeout);
|
|
5144
|
+
}
|
|
5145
|
+
rejectWaitingCaller?.(error);
|
|
5146
|
+
}
|
|
5147
|
+
);
|
|
5148
|
+
return caller;
|
|
5149
|
+
}
|
|
5150
|
+
var QUEUE_BUSY_ACK_PHRASES = [
|
|
5151
|
+
"\u4E0A\u4E00\u6761\u8FD8\u6CA1\u7ED3\u675F\uFF0C\u8FD9\u6761\u6211\u5DF2\u7ECF\u8BB0\u4E0B\uFF0C\u7A0D\u540E\u6309\u987A\u5E8F\u7EE7\u7EED\u5904\u7406\u3002",
|
|
5152
|
+
"\u5F53\u524D\u8FD8\u5728\u5FD9\uFF0C\u4F60\u7684\u65B0\u6D88\u606F\u5DF2\u7ECF\u6392\u961F\uFF0C\u4E0A\u4E00\u6761\u5B8C\u6210\u540E\u6211\u9A6C\u4E0A\u7EE7\u7EED\u3002",
|
|
5153
|
+
"\u6211\u8FD9\u8FB9\u8FD8\u5728\u5904\u7406\u4E0A\u4E00\u6761\uFF0C\u8FD9\u6761\u5DF2\u52A0\u5165\u961F\u5217\uFF0C\u5B8C\u6210\u540E\u7EE7\u7EED\u5904\u7406\u3002"
|
|
5154
|
+
];
|
|
5155
|
+
function pickQueueBusyAckPhrase(seed) {
|
|
5156
|
+
const list = QUEUE_BUSY_ACK_PHRASES;
|
|
5157
|
+
const index = seed === void 0 ? Math.floor(Math.random() * list.length) : seed % list.length;
|
|
5158
|
+
return list[index];
|
|
5159
|
+
}
|
|
5160
|
+
|
|
5161
|
+
// src/send-service.ts
|
|
5162
|
+
import * as path6 from "node:path";
|
|
5163
|
+
|
|
4947
5164
|
// src/media-utils.ts
|
|
4948
5165
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4949
5166
|
import * as os4 from "node:os";
|
|
@@ -6217,96 +6434,6 @@ function extractMessageContent(data) {
|
|
|
6217
6434
|
};
|
|
6218
6435
|
}
|
|
6219
6436
|
|
|
6220
|
-
// src/messaging/attachment-text-extractor.ts
|
|
6221
|
-
import fs4 from "node:fs/promises";
|
|
6222
|
-
import path6 from "node:path";
|
|
6223
|
-
var MAX_EXTRACTED_TEXT_CHARS = 6e3;
|
|
6224
|
-
var MAX_ATTACHMENT_EXTRACT_BYTES = 2 * 1024 * 1024;
|
|
6225
|
-
async function isFileTooLarge(filePath) {
|
|
6226
|
-
const stat = await fs4.stat(filePath);
|
|
6227
|
-
return stat.size > MAX_ATTACHMENT_EXTRACT_BYTES;
|
|
6228
|
-
}
|
|
6229
|
-
function isTextLikeMimeType(mimeType) {
|
|
6230
|
-
if (!mimeType) {
|
|
6231
|
-
return false;
|
|
6232
|
-
}
|
|
6233
|
-
return mimeType.startsWith("text/") || mimeType === "application/json" || mimeType === "application/xml" || mimeType === "application/javascript";
|
|
6234
|
-
}
|
|
6235
|
-
function normalizeWhitespace(text) {
|
|
6236
|
-
return text.replace(/\r\n/g, "\n").split("\0").join("").replace(/[ \t]{2,}/g, " ").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
6237
|
-
}
|
|
6238
|
-
function limitExtractedText(text) {
|
|
6239
|
-
const normalized = normalizeWhitespace(text);
|
|
6240
|
-
if (!normalized) {
|
|
6241
|
-
return null;
|
|
6242
|
-
}
|
|
6243
|
-
const truncated = normalized.length > MAX_EXTRACTED_TEXT_CHARS;
|
|
6244
|
-
const limited = truncated ? `${normalized.slice(0, MAX_EXTRACTED_TEXT_CHARS)}
|
|
6245
|
-
|
|
6246
|
-
[\u5185\u5BB9\u5DF2\u622A\u65AD]` : normalized;
|
|
6247
|
-
return {
|
|
6248
|
-
text: limited,
|
|
6249
|
-
truncated,
|
|
6250
|
-
sourceType: "text"
|
|
6251
|
-
};
|
|
6252
|
-
}
|
|
6253
|
-
function stripHtml(html) {
|
|
6254
|
-
return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/ /gi, " ").replace(/</gi, "<").replace(/>/gi, ">").replace(/&/gi, "&").replace(/'/gi, "'").replace(/"/gi, '"');
|
|
6255
|
-
}
|
|
6256
|
-
async function extractTextLikeFile(filePath) {
|
|
6257
|
-
const raw = await fs4.readFile(filePath, "utf8");
|
|
6258
|
-
return limitExtractedText(raw);
|
|
6259
|
-
}
|
|
6260
|
-
async function extractPdf(filePath) {
|
|
6261
|
-
const pdfParseModule = await import("pdf-parse");
|
|
6262
|
-
const fileBuffer = await fs4.readFile(filePath);
|
|
6263
|
-
const parser = new pdfParseModule.PDFParse({ data: new Uint8Array(fileBuffer) });
|
|
6264
|
-
try {
|
|
6265
|
-
const result = await parser.getText();
|
|
6266
|
-
const limited = limitExtractedText(result.text || "");
|
|
6267
|
-
return limited ? { ...limited, sourceType: "pdf" } : null;
|
|
6268
|
-
} finally {
|
|
6269
|
-
await parser.destroy();
|
|
6270
|
-
}
|
|
6271
|
-
}
|
|
6272
|
-
async function extractDocx(filePath) {
|
|
6273
|
-
const mammothModule = await import("mammoth");
|
|
6274
|
-
const result = await mammothModule.extractRawText({ path: filePath });
|
|
6275
|
-
const limited = limitExtractedText(result.value || "");
|
|
6276
|
-
return limited ? { ...limited, sourceType: "docx" } : null;
|
|
6277
|
-
}
|
|
6278
|
-
async function extractHtml(filePath) {
|
|
6279
|
-
const raw = await fs4.readFile(filePath, "utf8");
|
|
6280
|
-
const limited = limitExtractedText(stripHtml(raw));
|
|
6281
|
-
return limited ? { ...limited, sourceType: "html" } : null;
|
|
6282
|
-
}
|
|
6283
|
-
async function extractAttachmentText(input) {
|
|
6284
|
-
const mimeType = input.mimeType?.toLowerCase();
|
|
6285
|
-
const fileName = (input.fileName || path6.basename(input.path)).toLowerCase();
|
|
6286
|
-
if (mimeType?.startsWith("image/") || mimeType?.startsWith("audio/") || mimeType?.startsWith("video/")) {
|
|
6287
|
-
return null;
|
|
6288
|
-
}
|
|
6289
|
-
if (await isFileTooLarge(input.path)) {
|
|
6290
|
-
return null;
|
|
6291
|
-
}
|
|
6292
|
-
if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || fileName.endsWith(".docx")) {
|
|
6293
|
-
return extractDocx(input.path);
|
|
6294
|
-
}
|
|
6295
|
-
if (mimeType === "application/pdf" || fileName.endsWith(".pdf")) {
|
|
6296
|
-
return extractPdf(input.path);
|
|
6297
|
-
}
|
|
6298
|
-
if (mimeType === "text/html" || mimeType === "application/xhtml+xml" || fileName.endsWith(".html") || fileName.endsWith(".htm")) {
|
|
6299
|
-
return extractHtml(input.path);
|
|
6300
|
-
}
|
|
6301
|
-
if (isTextLikeMimeType(mimeType) || fileName.endsWith(".txt") || fileName.endsWith(".md") || fileName.endsWith(".markdown") || fileName.endsWith(".csv") || fileName.endsWith(".json") || fileName.endsWith(".xml") || fileName.endsWith(".log")) {
|
|
6302
|
-
return extractTextLikeFile(input.path);
|
|
6303
|
-
}
|
|
6304
|
-
return null;
|
|
6305
|
-
}
|
|
6306
|
-
|
|
6307
|
-
// src/send-service.ts
|
|
6308
|
-
import * as path7 from "node:path";
|
|
6309
|
-
|
|
6310
6437
|
// src/proactive-risk-registry.ts
|
|
6311
6438
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6312
6439
|
var store = /* @__PURE__ */ new Map();
|
|
@@ -6694,9 +6821,9 @@ async function sendProactiveMedia(config, target, mediaPath, mediaType, options
|
|
|
6694
6821
|
const durationMs = uploadedDurationMs ?? await getVoiceDurationMs(mediaPath, mediaType, log, { preReadBuffer: buffer });
|
|
6695
6822
|
msgParam = JSON.stringify({ mediaId, duration: String(durationMs) });
|
|
6696
6823
|
} else {
|
|
6697
|
-
const filename =
|
|
6824
|
+
const filename = path6.basename(mediaPath);
|
|
6698
6825
|
const defaultExt = mediaType === "video" ? "mp4" : "file";
|
|
6699
|
-
const ext =
|
|
6826
|
+
const ext = path6.extname(mediaPath).slice(1) || defaultExt;
|
|
6700
6827
|
msgKey = "sampleFile";
|
|
6701
6828
|
msgParam = JSON.stringify({ mediaId, fileName: filename, fileType: ext });
|
|
6702
6829
|
}
|
|
@@ -6799,7 +6926,7 @@ async function sendMedia(config, target, mediaInput, options = {}) {
|
|
|
6799
6926
|
let preparedMedia;
|
|
6800
6927
|
try {
|
|
6801
6928
|
preparedMedia = await prepareMediaInput(mediaInput, log, config.mediaUrlAllowlist);
|
|
6802
|
-
const mediaPath = preparedMedia.cleanup ? preparedMedia.path :
|
|
6929
|
+
const mediaPath = preparedMedia.cleanup ? preparedMedia.path : path6.resolve(process.cwd(), preparedMedia.path);
|
|
6803
6930
|
const mediaType = resolveOutboundMediaType({
|
|
6804
6931
|
mediaType: options.mediaType,
|
|
6805
6932
|
mediaPath,
|
|
@@ -6855,7 +6982,7 @@ async function sendBySession(config, sessionWebhook, text, options = {}) {
|
|
|
6855
6982
|
mediaLocalRoots: options.mediaLocalRoots
|
|
6856
6983
|
});
|
|
6857
6984
|
if (uploadResult) {
|
|
6858
|
-
const imageMarkdown = ``;
|
|
6859
6986
|
text = text ? `${text}
|
|
6860
6987
|
|
|
6861
6988
|
${imageMarkdown}` : imageMarkdown;
|
|
@@ -7038,6 +7165,284 @@ async function sendMessage(config, conversationId, text, options = {}) {
|
|
|
7038
7165
|
}
|
|
7039
7166
|
}
|
|
7040
7167
|
|
|
7168
|
+
// src/gateway/inbound-session-queue-dispatcher.ts
|
|
7169
|
+
var QUEUE_FULL_ACK = "\u5F53\u524D\u6D88\u606F\u8F83\u591A\uFF0C\u5DF2\u8FBE\u5230\u672C\u4F1A\u8BDD\u6392\u961F\u4E0A\u9650\uFF1B\u8BF7\u7B49\u5F85\u4E0A\u4E00\u8F6E\u5B8C\u6210\u540E\u518D\u53D1\u9001\u3002";
|
|
7170
|
+
var QUEUE_WAIT_TIMEOUT_ACK = "\u4E0A\u4E00\u8F6E\u5904\u7406\u65F6\u95F4\u8F83\u957F\uFF0C\u8FD9\u6761\u6D88\u606F\u672A\u6267\u884C\uFF1B\u8BF7\u7A0D\u540E\u91CD\u65B0\u53D1\u9001\u3002";
|
|
7171
|
+
var QUEUE_HANDLER_FAILURE_ACK = "\u672C\u6B21\u5904\u7406\u5F02\u5E38\uFF0C\u672A\u80FD\u5B8C\u6210\uFF1B\u8BF7\u7A0D\u540E\u91CD\u65B0\u53D1\u9001\u3002";
|
|
7172
|
+
var MIN_QUEUE_ACK_CARD_VISIBLE_MS = 750;
|
|
7173
|
+
var queuedAckVisibleAt = /* @__PURE__ */ new WeakMap();
|
|
7174
|
+
function shouldPrepareQueueAckCard(input) {
|
|
7175
|
+
return input.dingtalkConfig.messageType === "card";
|
|
7176
|
+
}
|
|
7177
|
+
async function keepQueueAckCardVisible(card) {
|
|
7178
|
+
const visibleAt = queuedAckVisibleAt.get(card);
|
|
7179
|
+
if (!visibleAt) {
|
|
7180
|
+
return;
|
|
7181
|
+
}
|
|
7182
|
+
const remainingMs = MIN_QUEUE_ACK_CARD_VISIBLE_MS - (Date.now() - visibleAt);
|
|
7183
|
+
if (remainingMs > 0) {
|
|
7184
|
+
await new Promise((resolve3) => setTimeout(resolve3, remainingMs));
|
|
7185
|
+
}
|
|
7186
|
+
}
|
|
7187
|
+
async function settleUnusedQueueAckCard(input, card) {
|
|
7188
|
+
if (isCardInTerminalState(card.state)) {
|
|
7189
|
+
return;
|
|
7190
|
+
}
|
|
7191
|
+
try {
|
|
7192
|
+
if (await recallAICardMessage(card, input.log)) {
|
|
7193
|
+
return;
|
|
7194
|
+
}
|
|
7195
|
+
} catch (err) {
|
|
7196
|
+
input.log?.warn?.(
|
|
7197
|
+
`[DingTalk] Failed to recall unused queue acknowledgement card: ${err instanceof Error ? err.message : String(err)}`
|
|
7198
|
+
);
|
|
7199
|
+
}
|
|
7200
|
+
await sendQueueTerminalAck(input, "\u5DF2\u7ED3\u675F\u6392\u961F\u786E\u8BA4\uFF0C\u8BF7\u4EE5\u672C\u6B21\u5B9E\u9645\u56DE\u590D\u4E3A\u51C6\u3002", card);
|
|
7201
|
+
}
|
|
7202
|
+
async function settleFailedQueueAckCard(input, card) {
|
|
7203
|
+
if (isCardInTerminalState(card.state)) {
|
|
7204
|
+
return;
|
|
7205
|
+
}
|
|
7206
|
+
await sendQueueTerminalAck(input, QUEUE_HANDLER_FAILURE_ACK, card);
|
|
7207
|
+
}
|
|
7208
|
+
async function dispatchInboundViaSessionQueue(input, handler) {
|
|
7209
|
+
const queueKey = input.sessionKey;
|
|
7210
|
+
if (!queueKey) {
|
|
7211
|
+
return handler(void 0);
|
|
7212
|
+
}
|
|
7213
|
+
const wasBusy = isInboundSessionQueueBusy(queueKey);
|
|
7214
|
+
if (getInboundSessionQueueDepth(queueKey) >= MAX_INBOUND_SESSION_QUEUE_DEPTH) {
|
|
7215
|
+
await sendQueueTerminalAck(input, QUEUE_FULL_ACK);
|
|
7216
|
+
return void 0;
|
|
7217
|
+
}
|
|
7218
|
+
let queuedAckState = "queued";
|
|
7219
|
+
const preCreatedCardPromise = wasBusy && shouldPrepareQueueAckCard(input) ? tryPrepareQueueAckCard(
|
|
7220
|
+
input,
|
|
7221
|
+
() => queuedAckState === "timed-out" ? { content: QUEUE_WAIT_TIMEOUT_ACK, finished: true } : { content: pickQueueBusyAckPhrase(), finished: false }
|
|
7222
|
+
) : void 0;
|
|
7223
|
+
try {
|
|
7224
|
+
return await chainInboundSessionTask(
|
|
7225
|
+
queueKey,
|
|
7226
|
+
async () => {
|
|
7227
|
+
const preCreatedCard = preCreatedCardPromise ? await preCreatedCardPromise : void 0;
|
|
7228
|
+
if (!preCreatedCard) {
|
|
7229
|
+
return handler(void 0);
|
|
7230
|
+
}
|
|
7231
|
+
await keepQueueAckCardVisible(preCreatedCard);
|
|
7232
|
+
let handlerFailed = false;
|
|
7233
|
+
try {
|
|
7234
|
+
return await handler(preCreatedCard);
|
|
7235
|
+
} catch (err) {
|
|
7236
|
+
handlerFailed = true;
|
|
7237
|
+
await settleFailedQueueAckCard(input, preCreatedCard);
|
|
7238
|
+
throw err;
|
|
7239
|
+
} finally {
|
|
7240
|
+
if (!handlerFailed && !isCardInTerminalState(preCreatedCard.state)) {
|
|
7241
|
+
await settleUnusedQueueAckCard(input, preCreatedCard);
|
|
7242
|
+
}
|
|
7243
|
+
}
|
|
7244
|
+
},
|
|
7245
|
+
{
|
|
7246
|
+
maxQueueWaitMs: wasBusy ? MAX_INBOUND_SESSION_QUEUE_WAIT_MS : void 0
|
|
7247
|
+
}
|
|
7248
|
+
);
|
|
7249
|
+
} catch (err) {
|
|
7250
|
+
if (err instanceof InboundSessionQueueWaitTimeoutError) {
|
|
7251
|
+
queuedAckState = "timed-out";
|
|
7252
|
+
await sendQueueTerminalAck(
|
|
7253
|
+
input,
|
|
7254
|
+
QUEUE_WAIT_TIMEOUT_ACK,
|
|
7255
|
+
preCreatedCardPromise ? await preCreatedCardPromise : void 0
|
|
7256
|
+
);
|
|
7257
|
+
return void 0;
|
|
7258
|
+
}
|
|
7259
|
+
throw err;
|
|
7260
|
+
}
|
|
7261
|
+
}
|
|
7262
|
+
async function tryPrepareQueueAckCard(input, ack) {
|
|
7263
|
+
const { dingtalkConfig, data, log, to, storePath, quoteContent } = input;
|
|
7264
|
+
if (!data) {
|
|
7265
|
+
return void 0;
|
|
7266
|
+
}
|
|
7267
|
+
if (!to) {
|
|
7268
|
+
return void 0;
|
|
7269
|
+
}
|
|
7270
|
+
let card = null;
|
|
7271
|
+
let ackFinished = false;
|
|
7272
|
+
try {
|
|
7273
|
+
card = await createAICard(dingtalkConfig, to, log, {
|
|
7274
|
+
accountId: input.accountId,
|
|
7275
|
+
storePath,
|
|
7276
|
+
quoteContent
|
|
7277
|
+
});
|
|
7278
|
+
if (!card) {
|
|
7279
|
+
return void 0;
|
|
7280
|
+
}
|
|
7281
|
+
const { content, finished } = ack();
|
|
7282
|
+
ackFinished = finished;
|
|
7283
|
+
await streamAICard(card, content, finished, log, {
|
|
7284
|
+
recoveryAction: finished ? "finalize" : "recall"
|
|
7285
|
+
});
|
|
7286
|
+
if (!finished) {
|
|
7287
|
+
queuedAckVisibleAt.set(card, Date.now());
|
|
7288
|
+
}
|
|
7289
|
+
if (finished) {
|
|
7290
|
+
return card;
|
|
7291
|
+
}
|
|
7292
|
+
void attachNativeAckReaction(
|
|
7293
|
+
dingtalkConfig,
|
|
7294
|
+
{ msgId: data.msgId, conversationId: data.conversationId },
|
|
7295
|
+
log
|
|
7296
|
+
).catch((err) => {
|
|
7297
|
+
log?.debug?.(
|
|
7298
|
+
`[DingTalk] Queue-busy ack reaction attach failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7299
|
+
);
|
|
7300
|
+
});
|
|
7301
|
+
log?.info?.(
|
|
7302
|
+
`[DingTalk] Inbound message queued behind active run for session=${input.sessionKey}; pre-created ACK card outTrackId=${card.cardInstanceId}.`
|
|
7303
|
+
);
|
|
7304
|
+
return card;
|
|
7305
|
+
} catch (err) {
|
|
7306
|
+
if (card && !ackFinished) {
|
|
7307
|
+
try {
|
|
7308
|
+
await recallAICardMessage(card, log);
|
|
7309
|
+
} catch (recallErr) {
|
|
7310
|
+
log?.warn?.(
|
|
7311
|
+
`[DingTalk] Failed to recall queue ACK card after prepare failure: ${recallErr instanceof Error ? recallErr.message : String(recallErr)}`
|
|
7312
|
+
);
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
log?.warn?.(
|
|
7316
|
+
`[DingTalk] Queue-busy ACK card prepare failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7317
|
+
);
|
|
7318
|
+
return void 0;
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
async function sendQueueTerminalAck(input, content, preCreatedCard) {
|
|
7322
|
+
const { dingtalkConfig, data, log, to, storePath } = input;
|
|
7323
|
+
try {
|
|
7324
|
+
if (preCreatedCard) {
|
|
7325
|
+
try {
|
|
7326
|
+
await streamAICard(preCreatedCard, content, true, log);
|
|
7327
|
+
return;
|
|
7328
|
+
} catch (err) {
|
|
7329
|
+
log?.warn?.(
|
|
7330
|
+
`[DingTalk] Queue acknowledgement card finalization failed; falling back to text: ${err instanceof Error ? err.message : String(err)}`
|
|
7331
|
+
);
|
|
7332
|
+
}
|
|
7333
|
+
} else {
|
|
7334
|
+
const card = await tryPrepareQueueAckCard(input, () => ({ content, finished: true }));
|
|
7335
|
+
if (card) {
|
|
7336
|
+
return;
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
7339
|
+
if (!to) {
|
|
7340
|
+
return;
|
|
7341
|
+
}
|
|
7342
|
+
const result = await sendMessage(dingtalkConfig, to, content, {
|
|
7343
|
+
sessionWebhook: data.sessionWebhook,
|
|
7344
|
+
log,
|
|
7345
|
+
accountId: input.accountId,
|
|
7346
|
+
storePath,
|
|
7347
|
+
conversationId: data.conversationId
|
|
7348
|
+
});
|
|
7349
|
+
if (!result.ok) {
|
|
7350
|
+
log?.warn?.(`[DingTalk] Queue terminal acknowledgement failed: ${result.error || "unknown"}`);
|
|
7351
|
+
}
|
|
7352
|
+
} catch (err) {
|
|
7353
|
+
log?.warn?.(
|
|
7354
|
+
`[DingTalk] Queue terminal acknowledgement delivery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7355
|
+
);
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
|
|
7359
|
+
// src/messaging/attachment-text-extractor.ts
|
|
7360
|
+
import fs4 from "node:fs/promises";
|
|
7361
|
+
import path7 from "node:path";
|
|
7362
|
+
var MAX_EXTRACTED_TEXT_CHARS = 6e3;
|
|
7363
|
+
var MAX_ATTACHMENT_EXTRACT_BYTES = 2 * 1024 * 1024;
|
|
7364
|
+
async function isFileTooLarge(filePath) {
|
|
7365
|
+
const stat = await fs4.stat(filePath);
|
|
7366
|
+
return stat.size > MAX_ATTACHMENT_EXTRACT_BYTES;
|
|
7367
|
+
}
|
|
7368
|
+
function isTextLikeMimeType(mimeType) {
|
|
7369
|
+
if (!mimeType) {
|
|
7370
|
+
return false;
|
|
7371
|
+
}
|
|
7372
|
+
return mimeType.startsWith("text/") || mimeType === "application/json" || mimeType === "application/xml" || mimeType === "application/javascript";
|
|
7373
|
+
}
|
|
7374
|
+
function normalizeWhitespace(text) {
|
|
7375
|
+
return text.replace(/\r\n/g, "\n").split("\0").join("").replace(/[ \t]{2,}/g, " ").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
7376
|
+
}
|
|
7377
|
+
function limitExtractedText(text) {
|
|
7378
|
+
const normalized = normalizeWhitespace(text);
|
|
7379
|
+
if (!normalized) {
|
|
7380
|
+
return null;
|
|
7381
|
+
}
|
|
7382
|
+
const truncated = normalized.length > MAX_EXTRACTED_TEXT_CHARS;
|
|
7383
|
+
const limited = truncated ? `${normalized.slice(0, MAX_EXTRACTED_TEXT_CHARS)}
|
|
7384
|
+
|
|
7385
|
+
[\u5185\u5BB9\u5DF2\u622A\u65AD]` : normalized;
|
|
7386
|
+
return {
|
|
7387
|
+
text: limited,
|
|
7388
|
+
truncated,
|
|
7389
|
+
sourceType: "text"
|
|
7390
|
+
};
|
|
7391
|
+
}
|
|
7392
|
+
function stripHtml(html) {
|
|
7393
|
+
return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/ /gi, " ").replace(/</gi, "<").replace(/>/gi, ">").replace(/&/gi, "&").replace(/'/gi, "'").replace(/"/gi, '"');
|
|
7394
|
+
}
|
|
7395
|
+
async function extractTextLikeFile(filePath) {
|
|
7396
|
+
const raw = await fs4.readFile(filePath, "utf8");
|
|
7397
|
+
return limitExtractedText(raw);
|
|
7398
|
+
}
|
|
7399
|
+
async function extractPdf(filePath) {
|
|
7400
|
+
const pdfParseModule = await import("pdf-parse");
|
|
7401
|
+
const fileBuffer = await fs4.readFile(filePath);
|
|
7402
|
+
const parser = new pdfParseModule.PDFParse({ data: new Uint8Array(fileBuffer) });
|
|
7403
|
+
try {
|
|
7404
|
+
const result = await parser.getText();
|
|
7405
|
+
const limited = limitExtractedText(result.text || "");
|
|
7406
|
+
return limited ? { ...limited, sourceType: "pdf" } : null;
|
|
7407
|
+
} finally {
|
|
7408
|
+
await parser.destroy();
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
7411
|
+
async function extractDocx(filePath) {
|
|
7412
|
+
const mammothModule = await import("mammoth");
|
|
7413
|
+
const result = await mammothModule.extractRawText({ path: filePath });
|
|
7414
|
+
const limited = limitExtractedText(result.value || "");
|
|
7415
|
+
return limited ? { ...limited, sourceType: "docx" } : null;
|
|
7416
|
+
}
|
|
7417
|
+
async function extractHtml(filePath) {
|
|
7418
|
+
const raw = await fs4.readFile(filePath, "utf8");
|
|
7419
|
+
const limited = limitExtractedText(stripHtml(raw));
|
|
7420
|
+
return limited ? { ...limited, sourceType: "html" } : null;
|
|
7421
|
+
}
|
|
7422
|
+
async function extractAttachmentText(input) {
|
|
7423
|
+
const mimeType = input.mimeType?.toLowerCase();
|
|
7424
|
+
const fileName = (input.fileName || path7.basename(input.path)).toLowerCase();
|
|
7425
|
+
if (mimeType?.startsWith("image/") || mimeType?.startsWith("audio/") || mimeType?.startsWith("video/")) {
|
|
7426
|
+
return null;
|
|
7427
|
+
}
|
|
7428
|
+
if (await isFileTooLarge(input.path)) {
|
|
7429
|
+
return null;
|
|
7430
|
+
}
|
|
7431
|
+
if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || fileName.endsWith(".docx")) {
|
|
7432
|
+
return extractDocx(input.path);
|
|
7433
|
+
}
|
|
7434
|
+
if (mimeType === "application/pdf" || fileName.endsWith(".pdf")) {
|
|
7435
|
+
return extractPdf(input.path);
|
|
7436
|
+
}
|
|
7437
|
+
if (mimeType === "text/html" || mimeType === "application/xhtml+xml" || fileName.endsWith(".html") || fileName.endsWith(".htm")) {
|
|
7438
|
+
return extractHtml(input.path);
|
|
7439
|
+
}
|
|
7440
|
+
if (isTextLikeMimeType(mimeType) || fileName.endsWith(".txt") || fileName.endsWith(".md") || fileName.endsWith(".markdown") || fileName.endsWith(".csv") || fileName.endsWith(".json") || fileName.endsWith(".xml") || fileName.endsWith(".log")) {
|
|
7441
|
+
return extractTextLikeFile(input.path);
|
|
7442
|
+
}
|
|
7443
|
+
return null;
|
|
7444
|
+
}
|
|
7445
|
+
|
|
7041
7446
|
// src/messaging/btw-deliver.ts
|
|
7042
7447
|
var MAX_QUESTION_LENGTH = 80;
|
|
7043
7448
|
var LEADING_MENTIONS_RE = /^(?:@\S+\s+)*/u;
|
|
@@ -7622,6 +8027,47 @@ async function resolveQuotedFile(config, params, log) {
|
|
|
7622
8027
|
}
|
|
7623
8028
|
}
|
|
7624
8029
|
|
|
8030
|
+
// src/gateway/reply-session-conflict.ts
|
|
8031
|
+
var REPLY_SESSION_CONFLICT_PATTERN = /reply session initialization conflicted/i;
|
|
8032
|
+
function readErrorMessage(error) {
|
|
8033
|
+
if (error instanceof Error) {
|
|
8034
|
+
return error.message;
|
|
8035
|
+
}
|
|
8036
|
+
if (typeof error === "string") {
|
|
8037
|
+
return error;
|
|
8038
|
+
}
|
|
8039
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
8040
|
+
const msg = error.message;
|
|
8041
|
+
return typeof msg === "string" ? msg : "";
|
|
8042
|
+
}
|
|
8043
|
+
return "";
|
|
8044
|
+
}
|
|
8045
|
+
function isReplySessionConflictError(error) {
|
|
8046
|
+
return REPLY_SESSION_CONFLICT_PATTERN.test(readErrorMessage(error));
|
|
8047
|
+
}
|
|
8048
|
+
var sleep = (ms) => new Promise((resolve3) => {
|
|
8049
|
+
setTimeout(resolve3, ms);
|
|
8050
|
+
});
|
|
8051
|
+
async function withReplySessionConflictRetry(fn, options = {}) {
|
|
8052
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
8053
|
+
const baseDelayMs = options.baseDelayMs ?? 1500;
|
|
8054
|
+
const sessionLabel = options.sessionKey ?? "?";
|
|
8055
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
8056
|
+
try {
|
|
8057
|
+
return await fn();
|
|
8058
|
+
} catch (error) {
|
|
8059
|
+
if (!isReplySessionConflictError(error) || attempt >= maxRetries) {
|
|
8060
|
+
throw error;
|
|
8061
|
+
}
|
|
8062
|
+
const delay = baseDelayMs * (attempt + 1);
|
|
8063
|
+
options.log?.warn?.(
|
|
8064
|
+
`[DingTalk] Reply session initialization conflicted for session=${sessionLabel}; active run still draining. Retry ${attempt + 1}/${maxRetries} after ${delay}ms.`
|
|
8065
|
+
);
|
|
8066
|
+
await sleep(delay);
|
|
8067
|
+
}
|
|
8068
|
+
}
|
|
8069
|
+
}
|
|
8070
|
+
|
|
7625
8071
|
// src/card/reasoning-answer-split.ts
|
|
7626
8072
|
var THINKING_TAG_RE = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\b[^<>]*>/gi;
|
|
7627
8073
|
function isWrappedReasoningLine(line) {
|
|
@@ -9793,7 +10239,8 @@ async function dispatchSubAgents(params) {
|
|
|
9793
10239
|
onRoutesResolved,
|
|
9794
10240
|
handleMessage,
|
|
9795
10241
|
downloadMedia: download,
|
|
9796
|
-
log
|
|
10242
|
+
log,
|
|
10243
|
+
inboundQueueEligible
|
|
9797
10244
|
} = params;
|
|
9798
10245
|
let helperMissingWarningSent = false;
|
|
9799
10246
|
const sendHelperMissingWarning = async () => {
|
|
@@ -9885,7 +10332,14 @@ async function dispatchSubAgents(params) {
|
|
|
9885
10332
|
matchedName: agentMatch.matchedName,
|
|
9886
10333
|
commandText
|
|
9887
10334
|
},
|
|
9888
|
-
preDownloadedMedia
|
|
10335
|
+
preDownloadedMedia,
|
|
10336
|
+
// Propagate queue eligibility so each recursive sub-agent handler
|
|
10337
|
+
// enters the handler-owned queue on its own `target.route.sessionKey`
|
|
10338
|
+
// instead of bypassing it (which previously left @子Agent messages
|
|
10339
|
+
// exposed to reply-session conflicts when the sub-agent session was
|
|
10340
|
+
// already busy). Synthetic callers without this flag keep the legacy
|
|
10341
|
+
// direct-dispatch behavior.
|
|
10342
|
+
inboundQueueEligible
|
|
9889
10343
|
});
|
|
9890
10344
|
} catch (error) {
|
|
9891
10345
|
const message = getErrorMessage(error);
|
|
@@ -10840,7 +11294,8 @@ async function handleDingTalkMessageInner(params) {
|
|
|
10840
11294
|
onRoutesResolved: (targets) => invalidateQuestionRoutes(targets.map((target) => target.route)),
|
|
10841
11295
|
handleMessage: handleDingTalkMessage,
|
|
10842
11296
|
downloadMedia,
|
|
10843
|
-
log
|
|
11297
|
+
log,
|
|
11298
|
+
inboundQueueEligible: params.inboundQueueEligible
|
|
10844
11299
|
});
|
|
10845
11300
|
return;
|
|
10846
11301
|
}
|
|
@@ -10870,7 +11325,8 @@ async function handleDingTalkMessageInner(params) {
|
|
|
10870
11325
|
onRoutesResolved: (targets) => invalidateQuestionRoutes(targets.map((target) => target.route)),
|
|
10871
11326
|
handleMessage: handleDingTalkMessage,
|
|
10872
11327
|
downloadMedia,
|
|
10873
|
-
log
|
|
11328
|
+
log,
|
|
11329
|
+
inboundQueueEligible: params.inboundQueueEligible
|
|
10874
11330
|
});
|
|
10875
11331
|
return;
|
|
10876
11332
|
}
|
|
@@ -10948,7 +11404,29 @@ async function handleDingTalkMessageInner(params) {
|
|
|
10948
11404
|
const quotedRef = buildInboundQuotedRef(data, extractedContent);
|
|
10949
11405
|
const replyQuotedRef = createReplyQuotedRef(data.msgId);
|
|
10950
11406
|
const content = extractedContent;
|
|
10951
|
-
const
|
|
11407
|
+
const controlText = stripLeadingMentions(content.text).trim();
|
|
11408
|
+
const isBtwBypass = isBtwRequestText(controlText);
|
|
11409
|
+
const isAbortBypass = isAbortRequestText(controlText);
|
|
11410
|
+
if (params.inboundQueueEligible && !params.inboundQueueHandled && inboundOrigin !== "ask-user" && !isBtwBypass && !isAbortBypass) {
|
|
11411
|
+
await dispatchInboundViaSessionQueue(
|
|
11412
|
+
{
|
|
11413
|
+
accountId,
|
|
11414
|
+
data,
|
|
11415
|
+
dingtalkConfig,
|
|
11416
|
+
sessionKey: route.sessionKey,
|
|
11417
|
+
to,
|
|
11418
|
+
storePath: accountStorePath,
|
|
11419
|
+
quoteContent: rawInboundText.slice(0, 200),
|
|
11420
|
+
log
|
|
11421
|
+
},
|
|
11422
|
+
(preCreatedCard) => handleDingTalkMessage({
|
|
11423
|
+
...params,
|
|
11424
|
+
preCreatedCard,
|
|
11425
|
+
inboundQueueHandled: true
|
|
11426
|
+
})
|
|
11427
|
+
);
|
|
11428
|
+
return;
|
|
11429
|
+
}
|
|
10952
11430
|
const taskInfoConversationId = groupId || to;
|
|
10953
11431
|
const agentDisplayName = getAgentDisplayName({
|
|
10954
11432
|
subAgentOptions,
|
|
@@ -11029,7 +11507,7 @@ async function handleDingTalkMessageInner(params) {
|
|
|
11029
11507
|
return true;
|
|
11030
11508
|
};
|
|
11031
11509
|
}
|
|
11032
|
-
if (useCardMode && !isBtwBypass) {
|
|
11510
|
+
if (useCardMode && !isBtwBypass && !params.preCreatedCard) {
|
|
11033
11511
|
const key = `${accountId}:${to}`;
|
|
11034
11512
|
if (cardCreationInFlight.has(key)) {
|
|
11035
11513
|
useCardMode = false;
|
|
@@ -11047,7 +11525,7 @@ async function handleDingTalkMessageInner(params) {
|
|
|
11047
11525
|
`[DingTalk][AICard] conversationType=${data.conversationType}, conversationId=${to}`
|
|
11048
11526
|
);
|
|
11049
11527
|
const inboundQuoteText = rawInboundText.slice(0, 200);
|
|
11050
|
-
const aiCard = await createAICard(dingtalkConfig, to, log, {
|
|
11528
|
+
const aiCard = params.preCreatedCard ?? await createAICard(dingtalkConfig, to, log, {
|
|
11051
11529
|
accountId,
|
|
11052
11530
|
storePath: accountStorePath,
|
|
11053
11531
|
contextConversationId: groupId,
|
|
@@ -11711,8 +12189,7 @@ ${attachmentExtractedText}` : inboundBody;
|
|
|
11711
12189
|
}
|
|
11712
12190
|
});
|
|
11713
12191
|
log?.info?.(`[DingTalk] Inbound: from=${senderName} text="${content.text.slice(0, 50)}..."`);
|
|
11714
|
-
|
|
11715
|
-
if (isAbortRequestText(textForAbortCheck)) {
|
|
12192
|
+
if (isAbortBypass) {
|
|
11716
12193
|
log?.info?.(
|
|
11717
12194
|
`[DingTalk] Abort request detected, bypassing session lock for session=${route.sessionKey}`
|
|
11718
12195
|
);
|
|
@@ -11973,53 +12450,57 @@ ${attachmentExtractedText}` : inboundBody;
|
|
|
11973
12450
|
inboundText: rawInboundText,
|
|
11974
12451
|
taskMeta
|
|
11975
12452
|
});
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
12453
|
+
let deliveredFinalCount = 0;
|
|
12454
|
+
const runDispatch = () => rt.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
12455
|
+
ctx,
|
|
12456
|
+
cfg,
|
|
12457
|
+
dispatcherOptions: {
|
|
12458
|
+
responsePrefix: subAgentOptions?.responsePrefix || "",
|
|
12459
|
+
deliver: async (payload, info) => {
|
|
12460
|
+
if (isCurrentCardStopRequested()) {
|
|
12461
|
+
log?.debug?.(
|
|
12462
|
+
"[DingTalk][CardStop] Ignoring reply delivery because stop was already requested"
|
|
12463
|
+
);
|
|
12464
|
+
return;
|
|
12465
|
+
}
|
|
12466
|
+
try {
|
|
12467
|
+
if (info?.kind === "final") {
|
|
12468
|
+
deliveredFinalCount += 1;
|
|
12469
|
+
}
|
|
12470
|
+
const inlineReplyPayload = parseInlineReplyPayloadText(payload.text);
|
|
12471
|
+
const mediaUrls = extractMediaUrls(payload, inlineReplyPayload);
|
|
12472
|
+
const richPayload = payload;
|
|
12473
|
+
const replyKind = info?.kind || "block";
|
|
12474
|
+
if (questionCardTookOver) {
|
|
12475
|
+
log?.info?.(
|
|
12476
|
+
`[DingTalk][AskUser] Suppressed ${replyKind} reply after question card took over`
|
|
11987
12477
|
);
|
|
11988
12478
|
return;
|
|
11989
12479
|
}
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
}
|
|
12004
|
-
await strategy.deliver({
|
|
12005
|
-
text: inlineReplyPayload.text,
|
|
12006
|
-
mediaUrls,
|
|
12007
|
-
audioAsVoice: extractSharedAudioAsVoice(payload, inlineReplyPayload),
|
|
12008
|
-
kind: replyKind,
|
|
12009
|
-
isError: payload.isError === true,
|
|
12010
|
-
isReasoning: richPayload.isReasoning === true
|
|
12011
|
-
});
|
|
12012
|
-
} catch (err) {
|
|
12013
|
-
log?.error?.(`[DingTalk] Reply failed: ${getErrorMessage(err)}`);
|
|
12014
|
-
const responseData = getErrorResponseData(err);
|
|
12015
|
-
if (responseData !== void 0) {
|
|
12016
|
-
log?.error?.(formatDingTalkErrorPayloadLog("inbound.replyDeliver", responseData));
|
|
12017
|
-
}
|
|
12018
|
-
throw err;
|
|
12480
|
+
await strategy.deliver({
|
|
12481
|
+
text: inlineReplyPayload.text,
|
|
12482
|
+
mediaUrls,
|
|
12483
|
+
audioAsVoice: extractSharedAudioAsVoice(payload, inlineReplyPayload),
|
|
12484
|
+
kind: replyKind,
|
|
12485
|
+
isError: payload.isError === true,
|
|
12486
|
+
isReasoning: richPayload.isReasoning === true
|
|
12487
|
+
});
|
|
12488
|
+
} catch (err) {
|
|
12489
|
+
log?.error?.(`[DingTalk] Reply failed: ${getErrorMessage(err)}`);
|
|
12490
|
+
const responseData = getErrorResponseData(err);
|
|
12491
|
+
if (responseData !== void 0) {
|
|
12492
|
+
log?.error?.(formatDingTalkErrorPayloadLog("inbound.replyDeliver", responseData));
|
|
12019
12493
|
}
|
|
12494
|
+
throw err;
|
|
12020
12495
|
}
|
|
12021
|
-
}
|
|
12022
|
-
|
|
12496
|
+
}
|
|
12497
|
+
},
|
|
12498
|
+
replyOptions: strategy.getReplyOptions()
|
|
12499
|
+
});
|
|
12500
|
+
try {
|
|
12501
|
+
const dispatchResult = await withReplySessionConflictRetry(runDispatch, {
|
|
12502
|
+
log,
|
|
12503
|
+
sessionKey: route.sessionKey
|
|
12023
12504
|
});
|
|
12024
12505
|
const bufferedFinal = dispatchResult && typeof dispatchResult === "object" && "queuedFinal" in dispatchResult ? dispatchResult.queuedFinal : void 0;
|
|
12025
12506
|
const finalCount = dispatchResult && typeof dispatchResult === "object" && "counts" in dispatchResult ? dispatchResult.counts?.final : void 0;
|
|
@@ -12049,6 +12530,51 @@ ${attachmentExtractedText}` : inboundBody;
|
|
|
12049
12530
|
}
|
|
12050
12531
|
} catch (dispatchErr) {
|
|
12051
12532
|
const error = dispatchErr instanceof Error ? dispatchErr : new Error(getErrorMessage(dispatchErr));
|
|
12533
|
+
if (isReplySessionConflictError(error)) {
|
|
12534
|
+
log?.warn?.(
|
|
12535
|
+
`[DingTalk] Reply session still conflicted after retries for session=${route.sessionKey}; sending "processing" acknowledgement instead of dropping the message.`
|
|
12536
|
+
);
|
|
12537
|
+
const ackText = "\u6536\u5230\uFF0C\u4E0A\u4E00\u8F6E\u8FD8\u5728\u5904\u7406\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5\u3002";
|
|
12538
|
+
if (replyMode === "card") {
|
|
12539
|
+
try {
|
|
12540
|
+
await strategy.deliver({
|
|
12541
|
+
text: ackText,
|
|
12542
|
+
mediaUrls: [],
|
|
12543
|
+
kind: "final",
|
|
12544
|
+
isReasoning: false
|
|
12545
|
+
});
|
|
12546
|
+
await strategy.finalize();
|
|
12547
|
+
return;
|
|
12548
|
+
} catch (cardAckErr) {
|
|
12549
|
+
log?.warn?.(
|
|
12550
|
+
`[DingTalk] Processing acknowledgement card finalize failed: ${getErrorMessage(cardAckErr)}`
|
|
12551
|
+
);
|
|
12552
|
+
return;
|
|
12553
|
+
}
|
|
12554
|
+
}
|
|
12555
|
+
try {
|
|
12556
|
+
if (sessionWebhook) {
|
|
12557
|
+
await sendBySession(dingtalkConfig, sessionWebhook, ackText, {
|
|
12558
|
+
log,
|
|
12559
|
+
accountId,
|
|
12560
|
+
storePath: accountStorePath
|
|
12561
|
+
});
|
|
12562
|
+
} else {
|
|
12563
|
+
await sendMessage(dingtalkConfig, to, ackText, {
|
|
12564
|
+
log,
|
|
12565
|
+
accountId,
|
|
12566
|
+
storePath: accountStorePath,
|
|
12567
|
+
conversationId: groupId
|
|
12568
|
+
});
|
|
12569
|
+
}
|
|
12570
|
+
} catch (ackErr) {
|
|
12571
|
+
log?.warn?.(
|
|
12572
|
+
`[DingTalk] Processing acknowledgement delivery failed: ${getErrorMessage(ackErr)}`
|
|
12573
|
+
);
|
|
12574
|
+
}
|
|
12575
|
+
await strategy.abort(error);
|
|
12576
|
+
return;
|
|
12577
|
+
}
|
|
12052
12578
|
await strategy.abort(error);
|
|
12053
12579
|
throw dispatchErr;
|
|
12054
12580
|
}
|
|
@@ -13130,13 +13656,12 @@ function registerDingTalkAskUserQuestionTool(api) {
|
|
|
13130
13656
|
api.logger?.warn?.(`${TOOL_NAME}: registerTool unavailable, skipping tool registration`);
|
|
13131
13657
|
return;
|
|
13132
13658
|
}
|
|
13133
|
-
|
|
13659
|
+
const createTool = (context) => ({
|
|
13134
13660
|
name: TOOL_NAME,
|
|
13135
13661
|
label: "Ask User Question",
|
|
13136
13662
|
description: "Ask the user a blocking question or collect structured input via an interactive DingTalk form card when the current task cannot continue without the user's answer. Returns immediately after sending the card. The user's answer will arrive as a new message in the conversation. Do NOT poll or re-call this tool \u2014 just wait for the response message. Use questions only for simple confirmation, single-select, multi-select, or simple free-text prompts. For simple selection questions, provide options; for simple free-text input, set options to an empty array. When collecting multiple missing values, when the user asks for a form, or when you would otherwise list required parameters for the user to fill, call this tool with top-level fields instead of replying with a markdown checklist. Do not call this tool for normal explanations, why/how questions, capability introductions, or cases where you can answer directly.",
|
|
13137
13663
|
parameters: AskUserQuestionSchema,
|
|
13138
13664
|
async execute(_toolCallId, params) {
|
|
13139
|
-
const context = getDingTalkQuestionContext();
|
|
13140
13665
|
if (!context) {
|
|
13141
13666
|
return jsonToolResult({
|
|
13142
13667
|
status: "failed",
|
|
@@ -13285,6 +13810,18 @@ function registerDingTalkAskUserQuestionTool(api) {
|
|
|
13285
13810
|
});
|
|
13286
13811
|
}
|
|
13287
13812
|
});
|
|
13813
|
+
registerTool.call(
|
|
13814
|
+
api,
|
|
13815
|
+
(toolContext) => {
|
|
13816
|
+
const context = getDingTalkQuestionContext();
|
|
13817
|
+
const runtimeSessionKey = toolContext.sessionKey?.trim();
|
|
13818
|
+
const contextSessionKey = context?.resolvedRoute?.sessionKey.trim();
|
|
13819
|
+
return createTool(
|
|
13820
|
+
context && (!runtimeSessionKey || contextSessionKey === runtimeSessionKey) ? context : void 0
|
|
13821
|
+
);
|
|
13822
|
+
},
|
|
13823
|
+
{ name: TOOL_NAME }
|
|
13824
|
+
);
|
|
13288
13825
|
api.logger?.debug?.(`${TOOL_NAME}: registered tool`);
|
|
13289
13826
|
}
|
|
13290
13827
|
|
|
@@ -14346,7 +14883,6 @@ function instrumentConnectionStages(client) {
|
|
|
14346
14883
|
}
|
|
14347
14884
|
};
|
|
14348
14885
|
}
|
|
14349
|
-
var INFLIGHT_TTL_MS = 5 * 60 * 1e3;
|
|
14350
14886
|
var processingDedupKeys = /* @__PURE__ */ new Map();
|
|
14351
14887
|
var inboundCountersByAccount = /* @__PURE__ */ new Map();
|
|
14352
14888
|
var INBOUND_COUNTER_LOG_EVERY = 10;
|
|
@@ -14490,7 +15026,9 @@ function createDingTalkGateway() {
|
|
|
14490
15026
|
data,
|
|
14491
15027
|
sessionWebhook: data.sessionWebhook,
|
|
14492
15028
|
log: pluginLog,
|
|
14493
|
-
dingtalkConfig: config
|
|
15029
|
+
dingtalkConfig: config,
|
|
15030
|
+
inboundOrigin: "stream",
|
|
15031
|
+
inboundQueueEligible: true
|
|
14494
15032
|
});
|
|
14495
15033
|
stats.processed += 1;
|
|
14496
15034
|
if (stats.received % INBOUND_COUNTER_LOG_EVERY === 0) {
|
|
@@ -14505,22 +15043,14 @@ function createDingTalkGateway() {
|
|
|
14505
15043
|
logInboundCounters(pluginLog, account.accountId, "dedup-skipped");
|
|
14506
15044
|
return;
|
|
14507
15045
|
}
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
pluginLog?.debug?.(
|
|
14517
|
-
`[${account.accountId}] Skipping in-flight duplicate message: ${dedupKey}`
|
|
14518
|
-
);
|
|
14519
|
-
stats.inflightSkipped += 1;
|
|
14520
|
-
acknowledge();
|
|
14521
|
-
logInboundCounters(pluginLog, account.accountId, "inflight-skipped");
|
|
14522
|
-
return;
|
|
14523
|
-
}
|
|
15046
|
+
if (processingDedupKeys.has(dedupKey)) {
|
|
15047
|
+
pluginLog?.debug?.(
|
|
15048
|
+
`[${account.accountId}] Skipping in-flight duplicate message: ${dedupKey}`
|
|
15049
|
+
);
|
|
15050
|
+
stats.inflightSkipped += 1;
|
|
15051
|
+
acknowledge();
|
|
15052
|
+
logInboundCounters(pluginLog, account.accountId, "inflight-skipped");
|
|
15053
|
+
return;
|
|
14524
15054
|
}
|
|
14525
15055
|
acknowledge();
|
|
14526
15056
|
processingDedupKeys.set(dedupKey, Date.now());
|
|
@@ -14531,7 +15061,9 @@ function createDingTalkGateway() {
|
|
|
14531
15061
|
data,
|
|
14532
15062
|
sessionWebhook: data.sessionWebhook,
|
|
14533
15063
|
log: pluginLog,
|
|
14534
|
-
dingtalkConfig: config
|
|
15064
|
+
dingtalkConfig: config,
|
|
15065
|
+
inboundOrigin: "stream",
|
|
15066
|
+
inboundQueueEligible: true
|
|
14535
15067
|
});
|
|
14536
15068
|
stats.processed += 1;
|
|
14537
15069
|
markMessageProcessed(dedupKey);
|
|
@@ -14755,19 +15287,6 @@ function createDingTalkGateway() {
|
|
|
14755
15287
|
lastError: null
|
|
14756
15288
|
});
|
|
14757
15289
|
} else if (state === "FAILED" /* FAILED */ || state === "DISCONNECTED" /* DISCONNECTED */) {
|
|
14758
|
-
const robotKey = resolveRobotCode(config) || account.accountId;
|
|
14759
|
-
let cleared = 0;
|
|
14760
|
-
for (const key of processingDedupKeys.keys()) {
|
|
14761
|
-
if (key.startsWith(`${robotKey}:`)) {
|
|
14762
|
-
processingDedupKeys.delete(key);
|
|
14763
|
-
cleared++;
|
|
14764
|
-
}
|
|
14765
|
-
}
|
|
14766
|
-
if (cleared > 0) {
|
|
14767
|
-
pluginLog?.info?.(
|
|
14768
|
-
`[${account.accountId}] Cleared ${cleared} stale in-flight lock(s) on disconnect`
|
|
14769
|
-
);
|
|
14770
|
-
}
|
|
14771
15290
|
applyStatusPatch({
|
|
14772
15291
|
running: false,
|
|
14773
15292
|
connected: false,
|
|
@@ -14908,13 +15427,13 @@ async function beginDeviceRegistration() {
|
|
|
14908
15427
|
}) : null;
|
|
14909
15428
|
abortPromise?.catch(() => {
|
|
14910
15429
|
});
|
|
14911
|
-
const
|
|
15430
|
+
const sleep2 = () => new Promise((resolve3) => setTimeout(resolve3, interval * 1e3));
|
|
14912
15431
|
try {
|
|
14913
15432
|
while (Date.now() < deadline) {
|
|
14914
15433
|
if (signal?.aborted) {
|
|
14915
15434
|
throw new RegistrationError("registration cancelled");
|
|
14916
15435
|
}
|
|
14917
|
-
await (abortPromise ? Promise.race([
|
|
15436
|
+
await (abortPromise ? Promise.race([sleep2(), abortPromise]) : sleep2());
|
|
14918
15437
|
if (signal?.aborted) {
|
|
14919
15438
|
throw new RegistrationError("registration cancelled");
|
|
14920
15439
|
}
|