@qwen-code/channel-dingtalk 0.23.2 â 0.23.3-nightly.20260911.aaa6a32aae
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/DingtalkAdapter.d.ts +1 -43
- package/dist/DingtalkAdapter.d.ts.map +1 -1
- package/dist/DingtalkAdapter.js +9 -723
- package/dist/DingtalkAdapter.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/DingtalkAdapter.js
CHANGED
|
@@ -410,15 +410,6 @@ function formatChatRecord(content, budget = MAX_CHAT_RECORD_CHARS) {
|
|
|
410
410
|
}
|
|
411
411
|
/** Track seen msgIds to deduplicate retried callbacks. */
|
|
412
412
|
const DEDUP_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
413
|
-
const BACKGROUND_RESPONSE_AGGREGATION_TIMEOUT_MS = 10 * 60 * 1000;
|
|
414
|
-
/**
|
|
415
|
-
* A failed aggregation delivery is retried rather than dropped: aggregating
|
|
416
|
-
* concentrates a whole turn into one send, and the burst that makes a send
|
|
417
|
-
* fail (several agents finishing at once against one chat quota) is exactly
|
|
418
|
-
* when the whole result would be lost.
|
|
419
|
-
*/
|
|
420
|
-
const BACKGROUND_RESPONSE_AGGREGATION_RETRY_MS = 30 * 1000;
|
|
421
|
-
const BACKGROUND_RESPONSE_AGGREGATION_MAX_RETRIES = 3;
|
|
422
413
|
const ACK_REACTION_NAME = 'đ';
|
|
423
414
|
const ACK_EMOTION_ID = '2659900';
|
|
424
415
|
const ACK_EMOTION_BG_ID = 'im_bg_1';
|
|
@@ -631,24 +622,6 @@ async function withConnectLoggingSuppressed(connect) {
|
|
|
631
622
|
}
|
|
632
623
|
}
|
|
633
624
|
}
|
|
634
|
-
class ProactiveTextDeliveryError extends Error {
|
|
635
|
-
plan;
|
|
636
|
-
retryable;
|
|
637
|
-
constructor(plan, cause) {
|
|
638
|
-
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
639
|
-
this.plan = plan;
|
|
640
|
-
if (cause instanceof DingtalkCardRequestError) {
|
|
641
|
-
this.retryable = cause.retryable;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
class ReplyTextDeliveryError extends Error {
|
|
646
|
-
plan;
|
|
647
|
-
constructor(plan, cause) {
|
|
648
|
-
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
649
|
-
this.plan = plan;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
625
|
export class DingtalkChannel extends ChannelBase {
|
|
653
626
|
client;
|
|
654
627
|
atSender;
|
|
@@ -680,7 +653,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
680
653
|
*/
|
|
681
654
|
proactiveToken;
|
|
682
655
|
interactiveCardConfig;
|
|
683
|
-
aggregateBackgroundAgentResponses;
|
|
684
656
|
interactiveCardClient;
|
|
685
657
|
statusCardController;
|
|
686
658
|
questionCardController;
|
|
@@ -693,10 +665,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
693
665
|
// boundary, input requested) mints a fresh segment UUID but the projection
|
|
694
666
|
// state must survive it, or a marker split across the reset leaks.
|
|
695
667
|
fileProjectors = new Map();
|
|
696
|
-
backgroundResponseAggregations = new Map();
|
|
697
|
-
detachedBackgroundResponseAggregations = new Set();
|
|
698
|
-
pendingBackgroundResponseTerminals = new Map();
|
|
699
|
-
detachedPendingBackgroundResponseTerminals = new Set();
|
|
700
668
|
constructor(name, config, bridge, options) {
|
|
701
669
|
super(name, config, bridge, options);
|
|
702
670
|
this.atSender =
|
|
@@ -717,13 +685,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
717
685
|
this.config.instructions += FILE_INSTRUCTIONS;
|
|
718
686
|
}
|
|
719
687
|
this.interactiveCardConfig = parseDingtalkInteractiveCardConfig(config.interactiveCards);
|
|
720
|
-
const rawAggregateBackgroundAgentResponses = config.aggregateBackgroundAgentResponses;
|
|
721
|
-
if (rawAggregateBackgroundAgentResponses !== undefined &&
|
|
722
|
-
typeof rawAggregateBackgroundAgentResponses !== 'boolean') {
|
|
723
|
-
throw new Error(`Channel "${name}" aggregateBackgroundAgentResponses must be a boolean.`);
|
|
724
|
-
}
|
|
725
|
-
this.aggregateBackgroundAgentResponses =
|
|
726
|
-
rawAggregateBackgroundAgentResponses === true;
|
|
727
688
|
if (!config.clientId || !config.clientSecret) {
|
|
728
689
|
throw new Error(`Channel "${name}" requires clientId and clientSecret for DingTalk.`);
|
|
729
690
|
}
|
|
@@ -1157,14 +1118,11 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1157
1118
|
}
|
|
1158
1119
|
return replaceImageMarkers(text, markers, replacements);
|
|
1159
1120
|
}
|
|
1160
|
-
async sendReply(chatId, text, atUserId, sourceLabel, prepared = false
|
|
1121
|
+
async sendReply(chatId, text, atUserId, sourceLabel, prepared = false) {
|
|
1161
1122
|
// chatId is a conversationId â resolve to the latest sessionWebhook.
|
|
1162
1123
|
const webhook = this.webhooks.get(chatId);
|
|
1163
1124
|
if (!webhook) {
|
|
1164
1125
|
process.stderr.write(`[DingTalk:${this.name}] No webhook for chatId ${chatId}, cannot send.\n`);
|
|
1165
|
-
if (failOnHttpError) {
|
|
1166
|
-
throw new Error('DingTalk session webhook unavailable');
|
|
1167
|
-
}
|
|
1168
1126
|
return;
|
|
1169
1127
|
}
|
|
1170
1128
|
const outgoingText = prepared
|
|
@@ -1173,12 +1131,7 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1173
1131
|
if (!outgoingText.trim())
|
|
1174
1132
|
return;
|
|
1175
1133
|
const plan = this.createReplyTextDelivery(outgoingText, atUserId, sourceLabel);
|
|
1176
|
-
|
|
1177
|
-
await this.deliverReplyText(chatId, plan, failOnHttpError);
|
|
1178
|
-
}
|
|
1179
|
-
catch (error) {
|
|
1180
|
-
throw new ReplyTextDeliveryError(plan, error);
|
|
1181
|
-
}
|
|
1134
|
+
await this.deliverReplyText(chatId, plan);
|
|
1182
1135
|
}
|
|
1183
1136
|
createReplyTextDelivery(outgoingText, atUserId, sourceLabel) {
|
|
1184
1137
|
const mentionPrefix = atUserId ? `@${atUserId}\n\n` : '';
|
|
@@ -1197,12 +1150,9 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1197
1150
|
...(atUserId ? { atUserId } : {}),
|
|
1198
1151
|
};
|
|
1199
1152
|
}
|
|
1200
|
-
async deliverReplyText(chatId, plan
|
|
1153
|
+
async deliverReplyText(chatId, plan) {
|
|
1201
1154
|
const webhook = this.webhooks.get(chatId);
|
|
1202
1155
|
if (!webhook) {
|
|
1203
|
-
if (failOnHttpError) {
|
|
1204
|
-
throw new Error('DingTalk session webhook unavailable');
|
|
1205
|
-
}
|
|
1206
1156
|
return;
|
|
1207
1157
|
}
|
|
1208
1158
|
while (plan.nextChunk < plan.chunks.length) {
|
|
@@ -1247,24 +1197,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1247
1197
|
if (!resp.ok) {
|
|
1248
1198
|
const detail = await resp.text().catch(() => '');
|
|
1249
1199
|
process.stderr.write(`[DingTalk:${this.name}] sendMessage failed: HTTP ${resp.status} ${detail}\n`);
|
|
1250
|
-
if (failOnHttpError) {
|
|
1251
|
-
throw new Error(`DingTalk reply send failed: HTTP ${resp.status} ${detail}`);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
else if (failOnHttpError) {
|
|
1255
|
-
const payload = (await resp
|
|
1256
|
-
.clone()
|
|
1257
|
-
.json()
|
|
1258
|
-
.catch(() => undefined));
|
|
1259
|
-
const response = payload && typeof payload === 'object'
|
|
1260
|
-
? payload
|
|
1261
|
-
: undefined;
|
|
1262
|
-
const value = response?.['errcode'] ?? response?.['code'];
|
|
1263
|
-
if (value !== undefined && String(value) !== '0') {
|
|
1264
|
-
const detail = sanitizeLogText(String(response?.['errmsg'] ?? response?.['message'] ?? value), 300);
|
|
1265
|
-
process.stderr.write(`[DingTalk:${this.name}] sendMessage failed: ${detail}\n`);
|
|
1266
|
-
throw new Error(`DingTalk reply send failed: ${detail}`);
|
|
1267
|
-
}
|
|
1268
1200
|
}
|
|
1269
1201
|
plan.nextChunk++;
|
|
1270
1202
|
}
|
|
@@ -1305,12 +1237,7 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1305
1237
|
const plan = await this.createProactiveTextDelivery(target, text, sourceLabel);
|
|
1306
1238
|
if (!plan)
|
|
1307
1239
|
return;
|
|
1308
|
-
|
|
1309
|
-
await this.deliverProactiveText(target, plan);
|
|
1310
|
-
}
|
|
1311
|
-
catch (error) {
|
|
1312
|
-
throw new ProactiveTextDeliveryError(plan, error);
|
|
1313
|
-
}
|
|
1240
|
+
await this.deliverProactiveText(target, plan);
|
|
1314
1241
|
}
|
|
1315
1242
|
async createProactiveTextDelivery(target, text, sourceLabel) {
|
|
1316
1243
|
const outgoingText = await this.prepareFileOutput(text, (file, mediaId) => this.sendProactiveFile(target, file, mediaId));
|
|
@@ -1555,7 +1482,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1555
1482
|
this.finishReaction(state.chatId, state.messageId, state.sessionId);
|
|
1556
1483
|
}
|
|
1557
1484
|
this.statusCardController?.dispose();
|
|
1558
|
-
this.drainBackgroundResponseAggregations();
|
|
1559
1485
|
this.activeReactionKeys.clear();
|
|
1560
1486
|
this.sessionReactionKeys.clear();
|
|
1561
1487
|
this.reactionStates.clear();
|
|
@@ -1835,11 +1761,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1835
1761
|
}
|
|
1836
1762
|
}
|
|
1837
1763
|
this.sessionMentionTargets.delete(sessionId);
|
|
1838
|
-
// A session dying after segments arrived but before the terminal signal is
|
|
1839
|
-
// precisely the case the partial-card fallback exists for; dropping the
|
|
1840
|
-
// buffer here would lose text the agent already produced, which the
|
|
1841
|
-
// pre-aggregation code always delivered on arrival.
|
|
1842
|
-
this.drainBackgroundResponseAggregations(sessionId);
|
|
1843
1764
|
const cardRunId = this.cardRunBySession.get(sessionId);
|
|
1844
1765
|
if (cardRunId) {
|
|
1845
1766
|
this.cardRunBySession.delete(sessionId);
|
|
@@ -1875,9 +1796,6 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
1875
1796
|
this.cardRuns.delete(runId);
|
|
1876
1797
|
}
|
|
1877
1798
|
}
|
|
1878
|
-
onSessionRetiring(sessionId) {
|
|
1879
|
-
this.drainBackgroundResponseAggregations(sessionId);
|
|
1880
|
-
}
|
|
1881
1799
|
onTaskLifecycle(event) {
|
|
1882
1800
|
if (event.type === 'started') {
|
|
1883
1801
|
this.startReaction(event.chatId, event.messageId, event.sessionId);
|
|
@@ -2023,636 +1941,15 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
2023
1941
|
this.sessionMentionTargets.delete(sessionId);
|
|
2024
1942
|
this.stopReaction(chatId, messageId, sessionId);
|
|
2025
1943
|
}
|
|
2026
|
-
/** Deliver every Agent segment immediately unless aggregation is enabled. */
|
|
2027
1944
|
async dispatchBackgroundResponse(sessionId, text, context) {
|
|
2028
1945
|
const target = this.router.getTarget(sessionId);
|
|
2029
1946
|
if (!target ||
|
|
2030
1947
|
target.channelName !== this.name ||
|
|
2031
|
-
(context !== undefined && context.kind !== 'agent')
|
|
1948
|
+
(context !== undefined && context.kind !== 'agent') ||
|
|
1949
|
+
!text.trim()) {
|
|
2032
1950
|
return super.dispatchBackgroundResponse(sessionId, text, context);
|
|
2033
1951
|
}
|
|
2034
|
-
|
|
2035
|
-
context?.kind === 'agent' &&
|
|
2036
|
-
typeof context.turnComplete === 'boolean';
|
|
2037
|
-
if (!canAggregate) {
|
|
2038
|
-
if (text.trim().length === 0) {
|
|
2039
|
-
return super.dispatchBackgroundResponse(sessionId, text, context);
|
|
2040
|
-
}
|
|
2041
|
-
return super.dispatchBackgroundResponse(sessionId, this.formatBackgroundAgentResponse(text, context?.label), context);
|
|
2042
|
-
}
|
|
2043
|
-
const key = JSON.stringify([sessionId, context.taskId, context.turnId]);
|
|
2044
|
-
let current = this.backgroundResponseAggregations.get(key);
|
|
2045
|
-
let parked = this.pendingBackgroundResponseTerminals.get(key);
|
|
2046
|
-
if (current?.turnComplete === true) {
|
|
2047
|
-
this.detachedBackgroundResponseAggregations.add(current);
|
|
2048
|
-
this.backgroundResponseAggregations.delete(key);
|
|
2049
|
-
current = undefined;
|
|
2050
|
-
}
|
|
2051
|
-
if (!current &&
|
|
2052
|
-
(parked?.turnEnded === true ||
|
|
2053
|
-
(parked?.turnComplete === true &&
|
|
2054
|
-
(parked.retryTimer || parked.retryInFlight || parked.resolvers > 0)))) {
|
|
2055
|
-
if (!parked.turnEnded) {
|
|
2056
|
-
this.detachedPendingBackgroundResponseTerminals.add(parked);
|
|
2057
|
-
}
|
|
2058
|
-
parked = { sessionId, target, resolvers: 0, held: [] };
|
|
2059
|
-
this.pendingBackgroundResponseTerminals.set(key, parked);
|
|
2060
|
-
}
|
|
2061
|
-
if (!current && text.trim().length === 0) {
|
|
2062
|
-
// The first segment's target resolution can suspend (named-session owner
|
|
2063
|
-
// lock), so a turn's terminal marker may arrive before the aggregation
|
|
2064
|
-
// exists. Park it instead of routing it to the empty-text early return,
|
|
2065
|
-
// or the completed turn only surfaces via the bounded wait, mislabeled.
|
|
2066
|
-
if (!parked ||
|
|
2067
|
-
(parked.resolvers === 0 &&
|
|
2068
|
-
!parked.retryTimer &&
|
|
2069
|
-
!parked.retryInFlight &&
|
|
2070
|
-
!parked.resolutionDropped &&
|
|
2071
|
-
!parked.turnComplete)) {
|
|
2072
|
-
if (parked)
|
|
2073
|
-
this.pendingBackgroundResponseTerminals.delete(key);
|
|
2074
|
-
return super.dispatchBackgroundResponse(sessionId, text, context);
|
|
2075
|
-
}
|
|
2076
|
-
if (context.turnComplete) {
|
|
2077
|
-
parked.turnComplete = true;
|
|
2078
|
-
parked.status = context.status;
|
|
2079
|
-
parked.label = context.label ?? parked.label;
|
|
2080
|
-
parked.completionPartial = context.partial === true;
|
|
2081
|
-
if (parked.resolvers === 0 &&
|
|
2082
|
-
!parked.retryTimer &&
|
|
2083
|
-
!parked.retryInFlight &&
|
|
2084
|
-
(parked.retryAttempts ?? 0) >=
|
|
2085
|
-
BACKGROUND_RESPONSE_AGGREGATION_MAX_RETRIES) {
|
|
2086
|
-
parked.turnEnded = true;
|
|
2087
|
-
this.pendingBackgroundResponseTerminals.delete(key);
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
return;
|
|
2091
|
-
}
|
|
2092
|
-
if (!current) {
|
|
2093
|
-
parked ??= { sessionId, target, resolvers: 0, held: [] };
|
|
2094
|
-
this.pendingBackgroundResponseTerminals.set(key, parked);
|
|
2095
|
-
this.holdPendingBackgroundResponse(parked, text, context);
|
|
2096
|
-
parked.resolvers++;
|
|
2097
|
-
try {
|
|
2098
|
-
let delivery;
|
|
2099
|
-
try {
|
|
2100
|
-
delivery = await this.resolveBackgroundResponseDelivery(sessionId);
|
|
2101
|
-
}
|
|
2102
|
-
catch (error) {
|
|
2103
|
-
if (parked.resolvers === 1 &&
|
|
2104
|
-
!this.backgroundResponseAggregations.has(key)) {
|
|
2105
|
-
this.scheduleBackgroundResponseResolutionRetry(key, sessionId, parked);
|
|
2106
|
-
}
|
|
2107
|
-
else {
|
|
2108
|
-
const existing = this.backgroundResponseAggregations.get(key);
|
|
2109
|
-
if (existing) {
|
|
2110
|
-
if (parked.held.length > 0) {
|
|
2111
|
-
this.applyHeldBackgroundResponses(existing, parked);
|
|
2112
|
-
}
|
|
2113
|
-
this.applyPendingBackgroundResponseTerminal(existing, parked);
|
|
2114
|
-
if (parked.resolvers === 1) {
|
|
2115
|
-
if (existing.turnComplete) {
|
|
2116
|
-
await this.completeBackgroundResponseAggregation(key, existing);
|
|
2117
|
-
}
|
|
2118
|
-
else {
|
|
2119
|
-
this.scheduleBackgroundResponseAggregationFlush(key, existing);
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
throw error;
|
|
2125
|
-
}
|
|
2126
|
-
if (!delivery || this.router.getTarget(sessionId) !== delivery.target) {
|
|
2127
|
-
if (parked.resolvers === 1 &&
|
|
2128
|
-
!this.backgroundResponseAggregations.has(key)) {
|
|
2129
|
-
this.scheduleBackgroundResponseResolutionRetry(key, sessionId, parked);
|
|
2130
|
-
}
|
|
2131
|
-
else {
|
|
2132
|
-
const existing = this.backgroundResponseAggregations.get(key);
|
|
2133
|
-
if (existing) {
|
|
2134
|
-
if (parked.held.length > 0) {
|
|
2135
|
-
this.applyHeldBackgroundResponses(existing, parked);
|
|
2136
|
-
}
|
|
2137
|
-
this.applyPendingBackgroundResponseTerminal(existing, parked);
|
|
2138
|
-
if (parked.resolvers === 1) {
|
|
2139
|
-
if (existing.turnComplete) {
|
|
2140
|
-
await this.completeBackgroundResponseAggregation(key, existing);
|
|
2141
|
-
}
|
|
2142
|
-
else {
|
|
2143
|
-
this.scheduleBackgroundResponseAggregationFlush(key, existing);
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
return;
|
|
2149
|
-
}
|
|
2150
|
-
if (parked.retiring ||
|
|
2151
|
-
this.pendingBackgroundResponseTerminals.get(key) !== parked) {
|
|
2152
|
-
await this.flushDetachedBackgroundResponse(key, sessionId, parked, delivery);
|
|
2153
|
-
return;
|
|
2154
|
-
}
|
|
2155
|
-
if (parked.retryTimer) {
|
|
2156
|
-
clearTimeout(parked.retryTimer);
|
|
2157
|
-
parked.retryTimer = undefined;
|
|
2158
|
-
}
|
|
2159
|
-
current =
|
|
2160
|
-
this.backgroundResponseAggregations.get(key) ??
|
|
2161
|
-
this.createBackgroundResponseAggregation(key, sessionId, parked.held[0]?.context ?? context, delivery.target, delivery.sourceLabel);
|
|
2162
|
-
this.applyHeldBackgroundResponses(current, parked);
|
|
2163
|
-
this.applyPendingBackgroundResponseTerminal(current, parked);
|
|
2164
|
-
if (parked.resolutionDropped) {
|
|
2165
|
-
current.resolutionDropped = true;
|
|
2166
|
-
parked.resolutionDropped = undefined;
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
finally {
|
|
2170
|
-
parked.resolvers--;
|
|
2171
|
-
if (this.pendingBackgroundResponseTerminals.get(key) === parked &&
|
|
2172
|
-
parked.resolvers === 0 &&
|
|
2173
|
-
!parked.retryTimer &&
|
|
2174
|
-
parked.held.length === 0 &&
|
|
2175
|
-
(!parked.resolutionDropped || parked.turnEnded)) {
|
|
2176
|
-
this.pendingBackgroundResponseTerminals.delete(key);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
if (!current)
|
|
2180
|
-
return;
|
|
2181
|
-
if (current.turnComplete && parked.resolvers > 0)
|
|
2182
|
-
return;
|
|
2183
|
-
if (!current.turnComplete) {
|
|
2184
|
-
this.scheduleBackgroundResponseAggregationFlush(key, current);
|
|
2185
|
-
}
|
|
2186
|
-
else {
|
|
2187
|
-
await this.completeBackgroundResponseAggregation(key, current);
|
|
2188
|
-
}
|
|
2189
|
-
return;
|
|
2190
|
-
}
|
|
2191
|
-
current.status = context.status;
|
|
2192
|
-
current.label = context.label ?? current.label;
|
|
2193
|
-
if (text.trim().length > 0)
|
|
2194
|
-
current.parts.push(text);
|
|
2195
|
-
if (context.turnComplete && parked && parked.resolvers > 0) {
|
|
2196
|
-
parked.turnComplete = true;
|
|
2197
|
-
parked.status = context.status;
|
|
2198
|
-
parked.label = context.label ?? parked.label;
|
|
2199
|
-
parked.completionPartial = context.partial === true;
|
|
2200
|
-
}
|
|
2201
|
-
else if (context.turnComplete) {
|
|
2202
|
-
current.turnComplete = true;
|
|
2203
|
-
current.completionPartial = context.partial === true;
|
|
2204
|
-
}
|
|
2205
|
-
else if (parked?.turnComplete && parked.resolvers === 0) {
|
|
2206
|
-
current.turnComplete = true;
|
|
2207
|
-
current.status = parked.status ?? current.status;
|
|
2208
|
-
current.label = parked.label ?? current.label;
|
|
2209
|
-
current.completionPartial = parked.completionPartial === true;
|
|
2210
|
-
}
|
|
2211
|
-
current.resolutionDropped ||= parked?.resolutionDropped;
|
|
2212
|
-
if (!current.turnComplete) {
|
|
2213
|
-
this.scheduleBackgroundResponseAggregationFlush(key, current);
|
|
2214
|
-
return;
|
|
2215
|
-
}
|
|
2216
|
-
await this.completeBackgroundResponseAggregation(key, current);
|
|
2217
|
-
}
|
|
2218
|
-
async flushBackgroundResponseAggregation(key, aggregation) {
|
|
2219
|
-
if ((this.backgroundResponseAggregations.get(key) !== aggregation &&
|
|
2220
|
-
!this.detachedBackgroundResponseAggregations.has(aggregation)) ||
|
|
2221
|
-
aggregation.flushing) {
|
|
2222
|
-
return;
|
|
2223
|
-
}
|
|
2224
|
-
this.refreshBackgroundResponseDelivery(aggregation);
|
|
2225
|
-
if (aggregation.retryTimer)
|
|
2226
|
-
clearTimeout(aggregation.retryTimer);
|
|
2227
|
-
aggregation.retryTimer = undefined;
|
|
2228
|
-
let delivery = aggregation.delivery;
|
|
2229
|
-
if (!delivery) {
|
|
2230
|
-
if (aggregation.parts.length === 0) {
|
|
2231
|
-
// A turn whose text was already drained by the bounded wait still owes
|
|
2232
|
-
// the user its completion: the last card it saw reads `īŧé¨åīŧ`.
|
|
2233
|
-
if (!this.owesTerminalBackgroundResponseCard(aggregation)) {
|
|
2234
|
-
if (aggregation.retiring || aggregation.turnComplete) {
|
|
2235
|
-
this.removeBackgroundResponseAggregation(key, aggregation);
|
|
2236
|
-
}
|
|
2237
|
-
else {
|
|
2238
|
-
this.scheduleBackgroundResponseAggregationFlush(key, aggregation);
|
|
2239
|
-
}
|
|
2240
|
-
return;
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
if (aggregation.timeoutTimer)
|
|
2244
|
-
clearTimeout(aggregation.timeoutTimer);
|
|
2245
|
-
aggregation.timeoutTimer = undefined;
|
|
2246
|
-
const parts = aggregation.parts.splice(0);
|
|
2247
|
-
delivery = {
|
|
2248
|
-
status: aggregation.status,
|
|
2249
|
-
label: aggregation.label,
|
|
2250
|
-
parts,
|
|
2251
|
-
partial: this.isPartialBackgroundResponseDelivery(aggregation, parts.length),
|
|
2252
|
-
attempts: 0,
|
|
2253
|
-
composedTurnComplete: aggregation.turnComplete === true,
|
|
2254
|
-
};
|
|
2255
|
-
aggregation.delivery = delivery;
|
|
2256
|
-
}
|
|
2257
|
-
const body = this.formatBackgroundResponseAggregation(delivery);
|
|
2258
|
-
aggregation.flushing = true;
|
|
2259
|
-
let error;
|
|
2260
|
-
try {
|
|
2261
|
-
if (delivery.proactivePlan) {
|
|
2262
|
-
await this.deliverProactiveText(aggregation.target, delivery.proactivePlan);
|
|
2263
|
-
}
|
|
2264
|
-
else if (delivery.replyPlan) {
|
|
2265
|
-
await this.deliverReplyText(aggregation.target.chatId, delivery.replyPlan, true);
|
|
2266
|
-
}
|
|
2267
|
-
else if (this.supportsProactiveSend() &&
|
|
2268
|
-
this.supportsProactiveTarget(aggregation.target)) {
|
|
2269
|
-
await this.deliverBackgroundResponseToTarget(aggregation.sessionId, body, {
|
|
2270
|
-
target: aggregation.target,
|
|
2271
|
-
sourceLabel: aggregation.sourceLabel,
|
|
2272
|
-
});
|
|
2273
|
-
}
|
|
2274
|
-
else {
|
|
2275
|
-
delivery.preparedReplyBody ??= await this.prepareReplyOutput(aggregation.target.chatId, body);
|
|
2276
|
-
await this.deliverBackgroundReply(aggregation.target.chatId, delivery.preparedReplyBody, aggregation.sessionId, aggregation.sourceLabel, true, true);
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
catch (caught) {
|
|
2280
|
-
error = caught;
|
|
2281
|
-
if (caught instanceof ProactiveTextDeliveryError) {
|
|
2282
|
-
delivery.proactivePlan = caught.plan;
|
|
2283
|
-
}
|
|
2284
|
-
else if (caught instanceof ReplyTextDeliveryError) {
|
|
2285
|
-
delivery.replyPlan = caught.plan;
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
finally {
|
|
2289
|
-
aggregation.flushing = false;
|
|
2290
|
-
}
|
|
2291
|
-
if (error === undefined) {
|
|
2292
|
-
aggregation.delivery = undefined;
|
|
2293
|
-
aggregation.delivered = true;
|
|
2294
|
-
if (delivery.composedTurnComplete === true &&
|
|
2295
|
-
aggregation.turnComplete &&
|
|
2296
|
-
delivery.partial !== true &&
|
|
2297
|
-
!aggregation.retiring) {
|
|
2298
|
-
aggregation.completionDelivered = true;
|
|
2299
|
-
}
|
|
2300
|
-
if (aggregation.retiring || aggregation.turnComplete) {
|
|
2301
|
-
await this.flushBackgroundResponseAggregation(key, aggregation);
|
|
2302
|
-
}
|
|
2303
|
-
else {
|
|
2304
|
-
// The turn is still open: keep the entry so its later segments re-join
|
|
2305
|
-
// this one (and stay labelled `īŧé¨åīŧ`), and re-arm the bounded wait
|
|
2306
|
-
// so a silent turn is still reaped.
|
|
2307
|
-
this.scheduleBackgroundResponseAggregationFlush(key, aggregation);
|
|
2308
|
-
}
|
|
2309
|
-
return;
|
|
2310
|
-
}
|
|
2311
|
-
delivery.attempts++;
|
|
2312
|
-
process.stderr.write(`[DingTalk:${this.name}] background response delivery failed (attempt ${delivery.attempts}): ${sanitizeLogText(error instanceof Error ? error.message : String(error), 300)}\n`);
|
|
2313
|
-
if (delivery.attempts >= BACKGROUND_RESPONSE_AGGREGATION_MAX_RETRIES ||
|
|
2314
|
-
// A permanently rejected send (an invalid appkey, a missing app) cannot
|
|
2315
|
-
// succeed later; retrying it only spends the chat's send quota.
|
|
2316
|
-
(error instanceof ProactiveTextDeliveryError && error.retryable === false)) {
|
|
2317
|
-
aggregation.delivery = undefined;
|
|
2318
|
-
aggregation.dropped = true;
|
|
2319
|
-
if (aggregation.parts.length === 0) {
|
|
2320
|
-
if (aggregation.retiring || aggregation.turnComplete) {
|
|
2321
|
-
this.removeBackgroundResponseAggregation(key, aggregation);
|
|
2322
|
-
}
|
|
2323
|
-
else {
|
|
2324
|
-
this.scheduleBackgroundResponseAggregationFlush(key, aggregation);
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
else if (aggregation.retiring || aggregation.turnComplete) {
|
|
2328
|
-
await this.flushBackgroundResponseAggregation(key, aggregation);
|
|
2329
|
-
}
|
|
2330
|
-
else {
|
|
2331
|
-
this.scheduleBackgroundResponseAggregationFlush(key, aggregation);
|
|
2332
|
-
}
|
|
2333
|
-
return;
|
|
2334
|
-
}
|
|
2335
|
-
aggregation.retryTimer = setTimeout(() => {
|
|
2336
|
-
aggregation.retryTimer = undefined;
|
|
2337
|
-
void this.flushBackgroundResponseAggregation(key, aggregation);
|
|
2338
|
-
}, BACKGROUND_RESPONSE_AGGREGATION_RETRY_MS);
|
|
2339
|
-
aggregation.retryTimer.unref?.();
|
|
2340
|
-
}
|
|
2341
|
-
/**
|
|
2342
|
-
* A card carries `īŧé¨åīŧ` whenever it is not the turn's whole output: the
|
|
2343
|
-
* turn is still open, earlier text already went out (or was given up on),
|
|
2344
|
-
* or the turn itself ended early.
|
|
2345
|
-
*/
|
|
2346
|
-
isPartialBackgroundResponseDelivery(aggregation, partCount) {
|
|
2347
|
-
return (partCount > 0 &&
|
|
2348
|
-
(aggregation.delivered === true ||
|
|
2349
|
-
aggregation.dropped === true ||
|
|
2350
|
-
aggregation.resolutionDropped === true ||
|
|
2351
|
-
aggregation.retiring === true ||
|
|
2352
|
-
aggregation.completionPartial === true ||
|
|
2353
|
-
aggregation.turnComplete !== true));
|
|
2354
|
-
}
|
|
2355
|
-
/**
|
|
2356
|
-
* A turn that outran the bounded wait had its text delivered under the
|
|
2357
|
-
* `īŧé¨åīŧ` label. When it then completes normally with nothing buffered,
|
|
2358
|
-
* a header-only card is the only way the chat ever learns it finished.
|
|
2359
|
-
*/
|
|
2360
|
-
owesTerminalBackgroundResponseCard(aggregation) {
|
|
2361
|
-
return (aggregation.turnComplete === true &&
|
|
2362
|
-
aggregation.delivered === true &&
|
|
2363
|
-
aggregation.completionDelivered !== true &&
|
|
2364
|
-
aggregation.retiring !== true &&
|
|
2365
|
-
aggregation.completionPartial !== true &&
|
|
2366
|
-
aggregation.dropped !== true &&
|
|
2367
|
-
aggregation.resolutionDropped !== true);
|
|
2368
|
-
}
|
|
2369
|
-
refreshBackgroundResponseDelivery(aggregation) {
|
|
2370
|
-
const delivery = aggregation.delivery;
|
|
2371
|
-
if (!delivery || aggregation.flushing)
|
|
2372
|
-
return;
|
|
2373
|
-
const plan = delivery.proactivePlan ?? delivery.replyPlan;
|
|
2374
|
-
if (plan && plan.nextChunk > 0)
|
|
2375
|
-
return;
|
|
2376
|
-
const body = this.formatBackgroundResponseAggregation(delivery);
|
|
2377
|
-
const header = body.split('\n', 1)[0];
|
|
2378
|
-
const replaceHeader = (text) => text.replace(/## (?:â
|â|âšī¸) Agent ¡ [^\n]+/, () => header);
|
|
2379
|
-
if (plan) {
|
|
2380
|
-
plan.title = extractTitle(body);
|
|
2381
|
-
plan.chunks[0] = replaceHeader(plan.chunks[0]);
|
|
2382
|
-
}
|
|
2383
|
-
if (delivery.preparedReplyBody) {
|
|
2384
|
-
delivery.preparedReplyBody = replaceHeader(delivery.preparedReplyBody);
|
|
2385
|
-
}
|
|
2386
|
-
delivery.composedTurnComplete = aggregation.turnComplete === true;
|
|
2387
|
-
}
|
|
2388
|
-
removeBackgroundResponseAggregation(key, aggregation) {
|
|
2389
|
-
if (this.backgroundResponseAggregations.get(key) === aggregation) {
|
|
2390
|
-
this.backgroundResponseAggregations.delete(key);
|
|
2391
|
-
}
|
|
2392
|
-
this.detachedBackgroundResponseAggregations.delete(aggregation);
|
|
2393
|
-
}
|
|
2394
|
-
drainBackgroundResponseAggregations(sessionId) {
|
|
2395
|
-
for (const [key, pending] of this.pendingBackgroundResponseTerminals) {
|
|
2396
|
-
if (sessionId !== undefined && pending.sessionId !== sessionId)
|
|
2397
|
-
continue;
|
|
2398
|
-
if (pending.retryTimer)
|
|
2399
|
-
clearTimeout(pending.retryTimer);
|
|
2400
|
-
pending.retryTimer = undefined;
|
|
2401
|
-
pending.retiring = true;
|
|
2402
|
-
pending.turnComplete = true;
|
|
2403
|
-
pending.completionPartial = true;
|
|
2404
|
-
this.pendingBackgroundResponseTerminals.delete(key);
|
|
2405
|
-
this.detachedPendingBackgroundResponseTerminals.add(pending);
|
|
2406
|
-
}
|
|
2407
|
-
for (const pending of this.detachedPendingBackgroundResponseTerminals) {
|
|
2408
|
-
if (sessionId !== undefined && pending.sessionId !== sessionId)
|
|
2409
|
-
continue;
|
|
2410
|
-
if (pending.retryTimer)
|
|
2411
|
-
clearTimeout(pending.retryTimer);
|
|
2412
|
-
pending.retryTimer = undefined;
|
|
2413
|
-
pending.retiring = true;
|
|
2414
|
-
pending.turnComplete = true;
|
|
2415
|
-
pending.completionPartial = true;
|
|
2416
|
-
if (pending.held.length > 0 &&
|
|
2417
|
-
this.router.getTarget(pending.sessionId) === pending.target) {
|
|
2418
|
-
void this.flushDetachedBackgroundResponse('', pending.sessionId, pending, { target: pending.target }).catch((error) => {
|
|
2419
|
-
process.stderr.write(`[DingTalk:${this.name}] background response delivery failed during drain: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 300)}\n`);
|
|
2420
|
-
});
|
|
2421
|
-
}
|
|
2422
|
-
else if (pending.held.length > 0) {
|
|
2423
|
-
process.stderr.write(`[DingTalk:${this.name}] background response target unavailable during drain; ${pending.held.length} buffered segment(s) discarded\n`);
|
|
2424
|
-
pending.held.length = 0;
|
|
2425
|
-
this.detachedPendingBackgroundResponseTerminals.delete(pending);
|
|
2426
|
-
}
|
|
2427
|
-
else if (pending.held.length === 0) {
|
|
2428
|
-
this.detachedPendingBackgroundResponseTerminals.delete(pending);
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
const aggregations = new Set([
|
|
2432
|
-
...this.backgroundResponseAggregations.values(),
|
|
2433
|
-
...this.detachedBackgroundResponseAggregations,
|
|
2434
|
-
]);
|
|
2435
|
-
for (const aggregation of aggregations) {
|
|
2436
|
-
if (sessionId !== undefined && aggregation.sessionId !== sessionId) {
|
|
2437
|
-
continue;
|
|
2438
|
-
}
|
|
2439
|
-
if (aggregation.timeoutTimer)
|
|
2440
|
-
clearTimeout(aggregation.timeoutTimer);
|
|
2441
|
-
if (aggregation.retryTimer)
|
|
2442
|
-
clearTimeout(aggregation.retryTimer);
|
|
2443
|
-
aggregation.timeoutTimer = undefined;
|
|
2444
|
-
aggregation.retryTimer = undefined;
|
|
2445
|
-
aggregation.retiring = true;
|
|
2446
|
-
aggregation.turnComplete = true;
|
|
2447
|
-
aggregation.completionPartial = true;
|
|
2448
|
-
if (!aggregation.flushing) {
|
|
2449
|
-
void this.flushBackgroundResponseAggregation(aggregation.key, aggregation);
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
createBackgroundResponseAggregation(key, sessionId, context, target, sourceLabel) {
|
|
2454
|
-
const aggregation = {
|
|
2455
|
-
key,
|
|
2456
|
-
sessionId,
|
|
2457
|
-
target,
|
|
2458
|
-
sourceLabel,
|
|
2459
|
-
status: context.status,
|
|
2460
|
-
label: context.label,
|
|
2461
|
-
parts: [],
|
|
2462
|
-
};
|
|
2463
|
-
this.backgroundResponseAggregations.set(key, aggregation);
|
|
2464
|
-
return aggregation;
|
|
2465
|
-
}
|
|
2466
|
-
scheduleBackgroundResponseResolutionRetry(key, sessionId, pending) {
|
|
2467
|
-
if (pending.retiring)
|
|
2468
|
-
return;
|
|
2469
|
-
if (pending.retryTimer)
|
|
2470
|
-
return;
|
|
2471
|
-
pending.retryAttempts = (pending.retryAttempts ?? 0) + 1;
|
|
2472
|
-
if (pending.retryAttempts >= BACKGROUND_RESPONSE_AGGREGATION_MAX_RETRIES) {
|
|
2473
|
-
pending.resolutionDropped = true;
|
|
2474
|
-
pending.turnEnded = pending.turnComplete === true;
|
|
2475
|
-
pending.held.length = 0;
|
|
2476
|
-
pending.turnComplete = undefined;
|
|
2477
|
-
pending.status = undefined;
|
|
2478
|
-
pending.label = undefined;
|
|
2479
|
-
pending.completionPartial = undefined;
|
|
2480
|
-
if (pending.turnEnded) {
|
|
2481
|
-
this.detachedPendingBackgroundResponseTerminals.delete(pending);
|
|
2482
|
-
}
|
|
2483
|
-
return;
|
|
2484
|
-
}
|
|
2485
|
-
pending.retryTimer = setTimeout(() => {
|
|
2486
|
-
pending.retryTimer = undefined;
|
|
2487
|
-
pending.retryInFlight = true;
|
|
2488
|
-
void this.retryBackgroundResponseResolution(key, sessionId, pending)
|
|
2489
|
-
.catch((error) => {
|
|
2490
|
-
process.stderr.write(`[DingTalk:${this.name}] background response target resolution failed: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 300)}\n`);
|
|
2491
|
-
})
|
|
2492
|
-
.finally(() => {
|
|
2493
|
-
pending.retryInFlight = false;
|
|
2494
|
-
if (pending.retiring ||
|
|
2495
|
-
(!pending.retryTimer && !pending.resolutionDropped)) {
|
|
2496
|
-
this.detachedPendingBackgroundResponseTerminals.delete(pending);
|
|
2497
|
-
}
|
|
2498
|
-
if (this.pendingBackgroundResponseTerminals.get(key) === pending &&
|
|
2499
|
-
pending.resolvers === 0 &&
|
|
2500
|
-
!pending.retryTimer &&
|
|
2501
|
-
pending.held.length === 0 &&
|
|
2502
|
-
(!pending.resolutionDropped || pending.turnEnded)) {
|
|
2503
|
-
this.pendingBackgroundResponseTerminals.delete(key);
|
|
2504
|
-
}
|
|
2505
|
-
});
|
|
2506
|
-
}, BACKGROUND_RESPONSE_AGGREGATION_RETRY_MS);
|
|
2507
|
-
pending.retryTimer.unref?.();
|
|
2508
|
-
const active = this.pendingBackgroundResponseTerminals.get(key);
|
|
2509
|
-
if (!active || active === pending) {
|
|
2510
|
-
this.pendingBackgroundResponseTerminals.set(key, pending);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
async retryBackgroundResponseResolution(key, sessionId, pending) {
|
|
2514
|
-
let delivery;
|
|
2515
|
-
try {
|
|
2516
|
-
delivery = await this.resolveBackgroundResponseDelivery(sessionId);
|
|
2517
|
-
}
|
|
2518
|
-
catch (error) {
|
|
2519
|
-
if ((this.pendingBackgroundResponseTerminals.get(key) === pending &&
|
|
2520
|
-
!this.backgroundResponseAggregations.has(key)) ||
|
|
2521
|
-
this.detachedPendingBackgroundResponseTerminals.has(pending)) {
|
|
2522
|
-
this.scheduleBackgroundResponseResolutionRetry(key, sessionId, pending);
|
|
2523
|
-
}
|
|
2524
|
-
throw error;
|
|
2525
|
-
}
|
|
2526
|
-
if (!delivery || this.router.getTarget(sessionId) !== delivery.target) {
|
|
2527
|
-
if (pending.retiring) {
|
|
2528
|
-
if (pending.held.length > 0) {
|
|
2529
|
-
process.stderr.write(`[DingTalk:${this.name}] background response target unavailable during drain; ${pending.held.length} buffered segment(s) discarded\n`);
|
|
2530
|
-
}
|
|
2531
|
-
pending.held.length = 0;
|
|
2532
|
-
this.detachedPendingBackgroundResponseTerminals.delete(pending);
|
|
2533
|
-
return;
|
|
2534
|
-
}
|
|
2535
|
-
if ((this.pendingBackgroundResponseTerminals.get(key) === pending &&
|
|
2536
|
-
!this.backgroundResponseAggregations.has(key)) ||
|
|
2537
|
-
this.detachedPendingBackgroundResponseTerminals.has(pending)) {
|
|
2538
|
-
this.scheduleBackgroundResponseResolutionRetry(key, sessionId, pending);
|
|
2539
|
-
}
|
|
2540
|
-
return;
|
|
2541
|
-
}
|
|
2542
|
-
if (pending.retiring ||
|
|
2543
|
-
this.pendingBackgroundResponseTerminals.get(key) !== pending ||
|
|
2544
|
-
pending.turnComplete) {
|
|
2545
|
-
await this.flushDetachedBackgroundResponse(key, sessionId, pending, delivery);
|
|
2546
|
-
return;
|
|
2547
|
-
}
|
|
2548
|
-
const first = pending.held[0];
|
|
2549
|
-
if (!first)
|
|
2550
|
-
return;
|
|
2551
|
-
const aggregation = this.createBackgroundResponseAggregation(key, sessionId, first.context, delivery.target, delivery.sourceLabel);
|
|
2552
|
-
this.applyHeldBackgroundResponses(aggregation, pending);
|
|
2553
|
-
if (pending.resolutionDropped) {
|
|
2554
|
-
aggregation.resolutionDropped = true;
|
|
2555
|
-
pending.resolutionDropped = undefined;
|
|
2556
|
-
}
|
|
2557
|
-
if (this.pendingBackgroundResponseTerminals.get(key) === pending) {
|
|
2558
|
-
this.pendingBackgroundResponseTerminals.delete(key);
|
|
2559
|
-
}
|
|
2560
|
-
this.scheduleBackgroundResponseAggregationFlush(key, aggregation);
|
|
2561
|
-
}
|
|
2562
|
-
holdPendingBackgroundResponse(pending, text, context) {
|
|
2563
|
-
if (text.trim().length > 0)
|
|
2564
|
-
pending.held.push({ text, context });
|
|
2565
|
-
if (context.turnComplete) {
|
|
2566
|
-
pending.turnComplete = true;
|
|
2567
|
-
pending.status = context.status;
|
|
2568
|
-
pending.label = context.label ?? pending.label;
|
|
2569
|
-
pending.completionPartial = context.partial === true;
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
applyHeldBackgroundResponses(aggregation, pending) {
|
|
2573
|
-
for (const { text, context } of pending.held.splice(0)) {
|
|
2574
|
-
aggregation.status = context.status;
|
|
2575
|
-
aggregation.label = context.label ?? aggregation.label;
|
|
2576
|
-
aggregation.parts.push(text);
|
|
2577
|
-
if (context.turnComplete) {
|
|
2578
|
-
aggregation.turnComplete = true;
|
|
2579
|
-
aggregation.completionPartial = context.partial === true;
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
applyPendingBackgroundResponseTerminal(aggregation, pending) {
|
|
2584
|
-
if (!pending.turnComplete)
|
|
2585
|
-
return;
|
|
2586
|
-
aggregation.turnComplete = true;
|
|
2587
|
-
aggregation.status = pending.status ?? aggregation.status;
|
|
2588
|
-
aggregation.label = pending.label ?? aggregation.label;
|
|
2589
|
-
aggregation.completionPartial = pending.completionPartial === true;
|
|
2590
|
-
pending.turnComplete = undefined;
|
|
2591
|
-
pending.status = undefined;
|
|
2592
|
-
pending.label = undefined;
|
|
2593
|
-
pending.completionPartial = undefined;
|
|
2594
|
-
}
|
|
2595
|
-
async completeBackgroundResponseAggregation(key, aggregation) {
|
|
2596
|
-
if (aggregation.delivery) {
|
|
2597
|
-
aggregation.delivery.status = aggregation.status;
|
|
2598
|
-
aggregation.delivery.label =
|
|
2599
|
-
aggregation.label ?? aggregation.delivery.label;
|
|
2600
|
-
aggregation.delivery.partial =
|
|
2601
|
-
this.isPartialBackgroundResponseDelivery(aggregation, aggregation.delivery.parts.length) || aggregation.parts.length > 0;
|
|
2602
|
-
}
|
|
2603
|
-
if (aggregation.timeoutTimer)
|
|
2604
|
-
clearTimeout(aggregation.timeoutTimer);
|
|
2605
|
-
aggregation.timeoutTimer = undefined;
|
|
2606
|
-
await this.flushBackgroundResponseAggregation(key, aggregation);
|
|
2607
|
-
}
|
|
2608
|
-
async flushDetachedBackgroundResponse(key, sessionId, pending, delivery) {
|
|
2609
|
-
const first = pending.held[0];
|
|
2610
|
-
if (!first) {
|
|
2611
|
-
this.detachedPendingBackgroundResponseTerminals.delete(pending);
|
|
2612
|
-
return;
|
|
2613
|
-
}
|
|
2614
|
-
const aggregation = {
|
|
2615
|
-
key,
|
|
2616
|
-
sessionId,
|
|
2617
|
-
target: delivery.target,
|
|
2618
|
-
sourceLabel: delivery.sourceLabel,
|
|
2619
|
-
status: first.context.status,
|
|
2620
|
-
label: first.context.label,
|
|
2621
|
-
parts: [],
|
|
2622
|
-
turnComplete: pending.turnComplete,
|
|
2623
|
-
completionPartial: pending.completionPartial,
|
|
2624
|
-
resolutionDropped: pending.resolutionDropped,
|
|
2625
|
-
};
|
|
2626
|
-
this.applyHeldBackgroundResponses(aggregation, pending);
|
|
2627
|
-
aggregation.status = pending.status ?? aggregation.status;
|
|
2628
|
-
aggregation.label = pending.label ?? aggregation.label;
|
|
2629
|
-
if (this.pendingBackgroundResponseTerminals.get(key) === pending) {
|
|
2630
|
-
this.pendingBackgroundResponseTerminals.delete(key);
|
|
2631
|
-
}
|
|
2632
|
-
this.detachedPendingBackgroundResponseTerminals.delete(pending);
|
|
2633
|
-
this.detachedBackgroundResponseAggregations.add(aggregation);
|
|
2634
|
-
await this.flushBackgroundResponseAggregation(key, aggregation);
|
|
2635
|
-
}
|
|
2636
|
-
scheduleBackgroundResponseAggregationFlush(key, aggregation) {
|
|
2637
|
-
if (aggregation.timeoutTimer || aggregation.delivery)
|
|
2638
|
-
return;
|
|
2639
|
-
aggregation.timeoutTimer = setTimeout(() => {
|
|
2640
|
-
aggregation.timeoutTimer = undefined;
|
|
2641
|
-
void this.flushBackgroundResponseAggregation(key, aggregation);
|
|
2642
|
-
}, BACKGROUND_RESPONSE_AGGREGATION_TIMEOUT_MS);
|
|
2643
|
-
aggregation.timeoutTimer.unref?.();
|
|
2644
|
-
}
|
|
2645
|
-
formatBackgroundResponseAggregation(delivery) {
|
|
2646
|
-
const icon = delivery.status === 'completed'
|
|
2647
|
-
? 'â
'
|
|
2648
|
-
: delivery.status === 'failed'
|
|
2649
|
-
? 'â'
|
|
2650
|
-
: 'âšī¸';
|
|
2651
|
-
const label = this.formatBackgroundAgentLabel(delivery.label);
|
|
2652
|
-
const header = `## ${icon} Agent ¡ ${label}${delivery.partial ? 'īŧé¨åīŧ' : ''}`;
|
|
2653
|
-
if (delivery.parts.length === 0)
|
|
2654
|
-
return header;
|
|
2655
|
-
return `${header}\n\n${delivery.parts.join('\n\n')}`;
|
|
1952
|
+
return super.dispatchBackgroundResponse(sessionId, this.formatBackgroundAgentResponse(text, context?.label), context);
|
|
2656
1953
|
}
|
|
2657
1954
|
formatBackgroundAgentResponse(text, label) {
|
|
2658
1955
|
return `## đ¤ Agent ¡ ${this.formatBackgroundAgentLabel(label)}\n\n${text}`;
|
|
@@ -2664,24 +1961,13 @@ export class DingtalkChannel extends ChannelBase {
|
|
|
2664
1961
|
.trim();
|
|
2665
1962
|
return escapeDingTalkMarkdown(normalized || 'åå°äģģåĄ');
|
|
2666
1963
|
}
|
|
2667
|
-
|
|
2668
|
-
* Body-identical to the base implementation â this override exists only to
|
|
2669
|
-
* widen the signature, which the base seam does not declare. The background
|
|
2670
|
-
* aggregation flush passes `prepared` so the body it already projected isn't
|
|
2671
|
-
* projected a second time (that would re-upload its files), and
|
|
2672
|
-
* `failOnHttpError` so a failed send throws instead of being swallowed,
|
|
2673
|
-
* letting the flush capture the delivery plan for the next retry.
|
|
2674
|
-
*/
|
|
2675
|
-
async deliverBackgroundReply(chatId, text, sessionId, sourceLabel, prepared = false, failOnHttpError = false) {
|
|
2676
|
-
await this.sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared, failOnHttpError);
|
|
2677
|
-
}
|
|
2678
|
-
async sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared = false, failOnHttpError = false) {
|
|
1964
|
+
async sendResponseMessage(chatId, text, sessionId, sourceLabel, prepared = false) {
|
|
2679
1965
|
const atUserId = this.atSender
|
|
2680
1966
|
? this.sessionMentionTargets.get(sessionId)
|
|
2681
1967
|
: undefined;
|
|
2682
1968
|
if (atUserId)
|
|
2683
1969
|
this.sessionMentionTargets.delete(sessionId);
|
|
2684
|
-
await this.sendReply(chatId, text, atUserId, sourceLabel ?? this.getResponseSourceLabel(sessionId), prepared
|
|
1970
|
+
await this.sendReply(chatId, text, atUserId, sourceLabel ?? this.getResponseSourceLabel(sessionId), prepared);
|
|
2685
1971
|
}
|
|
2686
1972
|
async sendFallbackReply(chatId, text, sessionId, sourceLabel) {
|
|
2687
1973
|
// Mid-run fallbacks must not consume the prompt's mention target: the
|