@qwen-code/channel-dws 0.23.1-preview.0 → 0.23.1-preview.2
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/dws-channel.d.ts +25 -10
- package/dist/dws-channel.d.ts.map +1 -1
- package/dist/dws-channel.js +349 -165
- package/dist/dws-channel.js.map +1 -1
- package/dist/dws-client.d.ts.map +1 -1
- package/dist/dws-client.js +0 -2
- package/dist/dws-client.js.map +1 -1
- package/package.json +2 -2
package/dist/dws-channel.js
CHANGED
|
@@ -12,7 +12,7 @@ const MAX_DOCUMENT_CONTEXT_CHARS = 12_000;
|
|
|
12
12
|
const MAX_TODO_CONTEXT_CHARS = 12_000;
|
|
13
13
|
const MAX_COMMENT_CHARS = 4_000;
|
|
14
14
|
const MAX_PROCESSED_ITEMS = 5_000;
|
|
15
|
-
const
|
|
15
|
+
const MAX_REPLAY_CONVERSATIONS = 16;
|
|
16
16
|
/**
|
|
17
17
|
* How many times one inbound message may fail its turn before it is dropped.
|
|
18
18
|
*
|
|
@@ -164,7 +164,8 @@ function isPendingMessage(value) {
|
|
|
164
164
|
return false;
|
|
165
165
|
}
|
|
166
166
|
const pending = value;
|
|
167
|
-
const sourceValid = pending.source?.kind === '
|
|
167
|
+
const sourceValid = pending.source?.kind === 'at' ||
|
|
168
|
+
pending.source?.kind === 'direct' ||
|
|
168
169
|
pending.source?.kind === 'group-all' ||
|
|
169
170
|
(pending.source?.kind === 'group' &&
|
|
170
171
|
typeof pending.source.conversationId === 'string' &&
|
|
@@ -173,7 +174,8 @@ function isPendingMessage(value) {
|
|
|
173
174
|
if (!sourceValid || typeof message !== 'object' || message === null) {
|
|
174
175
|
return false;
|
|
175
176
|
}
|
|
176
|
-
return ((message.type === '
|
|
177
|
+
return ((message.type === 'user_im_message_receive_at' ||
|
|
178
|
+
message.type === 'user_im_message_receive_o2o' ||
|
|
177
179
|
message.type === 'user_im_message_receive_o2o_all' ||
|
|
178
180
|
message.type === 'user_im_message_receive_group' ||
|
|
179
181
|
message.type === 'user_im_message_receive_group_all') &&
|
|
@@ -262,6 +264,13 @@ function sourceLabel(source) {
|
|
|
262
264
|
return 'all group messages';
|
|
263
265
|
return 'group messages';
|
|
264
266
|
}
|
|
267
|
+
function sameImSource(left, right) {
|
|
268
|
+
if (left.kind !== right.kind)
|
|
269
|
+
return false;
|
|
270
|
+
if (left.kind !== 'group')
|
|
271
|
+
return true;
|
|
272
|
+
return right.kind === 'group' && left.conversationId === right.conversationId;
|
|
273
|
+
}
|
|
265
274
|
function retryLimit(error) {
|
|
266
275
|
if (!(error instanceof DwsEventProcessError))
|
|
267
276
|
return 1;
|
|
@@ -326,13 +335,17 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
326
335
|
endReactionKeys = new Set();
|
|
327
336
|
notifiedSenderPairingNotifications = new Set();
|
|
328
337
|
processingMessages = new Map();
|
|
329
|
-
|
|
330
|
-
// Replay-started
|
|
338
|
+
queuedMessages = new Map();
|
|
339
|
+
// Replay-started dispatches only. The cap must not be consumed by
|
|
331
340
|
// live or followup traffic, whose queue entries outlive their turn.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
341
|
+
replayDispatches = new Map();
|
|
342
|
+
attemptedPendingMessages = new Set();
|
|
343
|
+
conversationTails = new Map();
|
|
344
|
+
messageStartResolvers = new Map();
|
|
345
|
+
processingMessageGenerations = new Map();
|
|
335
346
|
pendingMessageCapacityWaiters = new Set();
|
|
347
|
+
drainingImSubscriptions = new Set();
|
|
348
|
+
pendingImStartups = new Set();
|
|
336
349
|
pollAbortController = new AbortController();
|
|
337
350
|
lifecycleGeneration = 0;
|
|
338
351
|
connectionStartedAt = 0;
|
|
@@ -467,7 +480,7 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
467
480
|
notificationCheckpoint: cursor.notificationCheckpoint,
|
|
468
481
|
mentionCheckpoint: cursor.mentionCheckpoint,
|
|
469
482
|
pendingDocumentNotifications: (cursor.pendingDocumentNotifications ?? []).slice(-MAX_PROCESSED_ITEMS),
|
|
470
|
-
pendingMessages: (cursor.pendingMessages ?? []).slice(
|
|
483
|
+
pendingMessages: (cursor.pendingMessages ?? []).slice(),
|
|
471
484
|
processedMessages: cursor.processedMessages.slice(-MAX_PROCESSED_ITEMS),
|
|
472
485
|
imTargets: cursor.imTargets.slice(-MAX_IM_TARGETS),
|
|
473
486
|
todosInitialized: cursor.todosInitialized ?? false,
|
|
@@ -480,9 +493,13 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
480
493
|
if (this.connected)
|
|
481
494
|
return;
|
|
482
495
|
const generation = ++this.lifecycleGeneration;
|
|
483
|
-
this.connectionStartedAt = Date.now();
|
|
484
496
|
this.pollAbortController.abort();
|
|
485
497
|
this.pollAbortController = new AbortController();
|
|
498
|
+
await this.waitForDisconnect();
|
|
499
|
+
if (generation !== this.lifecycleGeneration) {
|
|
500
|
+
throw new Error('DWS channel connection was cancelled.');
|
|
501
|
+
}
|
|
502
|
+
this.connectionStartedAt = Date.now();
|
|
486
503
|
await this.client.assertCompatible?.(this.pollAbortController.signal);
|
|
487
504
|
if (generation !== this.lifecycleGeneration) {
|
|
488
505
|
throw new Error('DWS channel connection was cancelled.');
|
|
@@ -542,7 +559,10 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
542
559
|
}
|
|
543
560
|
this.connected = true;
|
|
544
561
|
try {
|
|
545
|
-
|
|
562
|
+
const startups = this.imStates.map((state) => this.startImSourceWithRetry(state, generation));
|
|
563
|
+
for (const startup of startups)
|
|
564
|
+
this.trackImStartup(startup);
|
|
565
|
+
await Promise.all(startups);
|
|
546
566
|
if (generation !== this.lifecycleGeneration || !this.connected) {
|
|
547
567
|
throw new Error('DWS channel connection was cancelled.');
|
|
548
568
|
}
|
|
@@ -576,12 +596,13 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
576
596
|
this.cleanupReaction(key, 'disconnect reaction removal');
|
|
577
597
|
}
|
|
578
598
|
this.sessionReactionKeys.clear();
|
|
579
|
-
this.
|
|
580
|
-
this.
|
|
581
|
-
|
|
599
|
+
this.queuedMessages.clear();
|
|
600
|
+
this.replayDispatches.clear();
|
|
601
|
+
this.attemptedPendingMessages.clear();
|
|
602
|
+
for (const { resolve } of this.messageStartResolvers.values())
|
|
582
603
|
resolve();
|
|
583
|
-
this.
|
|
584
|
-
this.
|
|
604
|
+
this.messageStartResolvers.clear();
|
|
605
|
+
this.conversationTails.clear();
|
|
585
606
|
for (const resolve of this.pendingMessageCapacityWaiters)
|
|
586
607
|
resolve();
|
|
587
608
|
this.pendingMessageCapacityWaiters.clear();
|
|
@@ -590,11 +611,35 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
590
611
|
if (state.retryTimer)
|
|
591
612
|
clearTimeout(state.retryTimer);
|
|
592
613
|
state.retryTimer = undefined;
|
|
593
|
-
state.subscription
|
|
614
|
+
const subscription = state.subscription;
|
|
615
|
+
if (subscription) {
|
|
616
|
+
void this.drainImSubscription(subscription);
|
|
617
|
+
}
|
|
594
618
|
state.subscription = undefined;
|
|
595
619
|
state.restartAttempts = 0;
|
|
596
620
|
}
|
|
597
621
|
}
|
|
622
|
+
async waitForDisconnect() {
|
|
623
|
+
while (this.pendingImStartups.size > 0 ||
|
|
624
|
+
this.drainingImSubscriptions.size > 0) {
|
|
625
|
+
await Promise.all([
|
|
626
|
+
...this.pendingImStartups,
|
|
627
|
+
...this.drainingImSubscriptions,
|
|
628
|
+
]);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
trackImStartup(startup) {
|
|
632
|
+
const settled = startup.then(() => undefined, () => undefined);
|
|
633
|
+
this.pendingImStartups.add(settled);
|
|
634
|
+
void settled.then(() => this.pendingImStartups.delete(settled));
|
|
635
|
+
}
|
|
636
|
+
drainImSubscription(subscription) {
|
|
637
|
+
const drain = subscription.closed.then(() => undefined, () => undefined);
|
|
638
|
+
this.drainingImSubscriptions.add(drain);
|
|
639
|
+
void drain.then(() => this.drainingImSubscriptions.delete(drain));
|
|
640
|
+
subscription.stop();
|
|
641
|
+
return drain;
|
|
642
|
+
}
|
|
598
643
|
supportsProactiveSend() {
|
|
599
644
|
return true;
|
|
600
645
|
}
|
|
@@ -777,6 +822,7 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
777
822
|
const signal = this.pollAbortController.signal;
|
|
778
823
|
if (!this.connected || signal.aborted)
|
|
779
824
|
return;
|
|
825
|
+
this.attemptedPendingMessages.clear();
|
|
780
826
|
const endTime = Date.now();
|
|
781
827
|
await this.replayPendingMessages(signal);
|
|
782
828
|
if (signal.aborted || !this.connected)
|
|
@@ -796,8 +842,16 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
796
842
|
for (const message of mentions.messages) {
|
|
797
843
|
if (signal.aborted || !this.connected)
|
|
798
844
|
return;
|
|
799
|
-
|
|
845
|
+
const key = messageKey(message);
|
|
846
|
+
if (this.cursor.processedMessages.includes(key))
|
|
847
|
+
continue;
|
|
848
|
+
if (this.hasPendingMessage(key))
|
|
849
|
+
continue;
|
|
850
|
+
this.enqueuePendingConversation(message.conversationId);
|
|
851
|
+
await this.admitHistoryMessage({ kind: 'at' }, message);
|
|
800
852
|
}
|
|
853
|
+
if (signal.aborted || !this.connected)
|
|
854
|
+
return;
|
|
801
855
|
if (mentions.nextCursor) {
|
|
802
856
|
this.cursor.mentionCheckpoint = {
|
|
803
857
|
...mentionCheckpoint,
|
|
@@ -836,13 +890,14 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
836
890
|
// shared retry budget twice per poll.
|
|
837
891
|
if (this.hasPendingMessage(key))
|
|
838
892
|
continue;
|
|
839
|
-
|
|
893
|
+
this.enqueuePendingConversation(message.conversationId);
|
|
894
|
+
await this.admitHistoryMessage({ kind: 'direct' }, message);
|
|
840
895
|
}
|
|
841
896
|
if (signal.aborted || !this.connected)
|
|
842
897
|
return;
|
|
843
898
|
if (this.notificationWatermarkPulledBack) {
|
|
844
899
|
// R4-4: a stale direct message replayed while this window's
|
|
845
|
-
// fetch was in flight, and `
|
|
900
|
+
// fetch was in flight, and `admitReceivedMessage` pulled the
|
|
846
901
|
// watermark back
|
|
847
902
|
// to cover it. That replay was left UNMARKED on purpose for history
|
|
848
903
|
// polling, so finishing this window normally would undo the rescue:
|
|
@@ -1005,19 +1060,21 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1005
1060
|
this.saveCursor();
|
|
1006
1061
|
}
|
|
1007
1062
|
async startImSource(state, generation) {
|
|
1063
|
+
const connectionStartedAt = this.connectionStartedAt;
|
|
1008
1064
|
const subscription = await this.client.subscribeToIm(state.source, (message) => {
|
|
1009
1065
|
state.lastError = undefined;
|
|
1010
1066
|
state.restartAttempts = 0;
|
|
1011
|
-
return state.source
|
|
1012
|
-
|
|
1013
|
-
|
|
1067
|
+
return this.receiveImMessage(state.source, message, false, false, {
|
|
1068
|
+
generation,
|
|
1069
|
+
connectionStartedAt,
|
|
1070
|
+
});
|
|
1014
1071
|
}, (error) => {
|
|
1015
1072
|
if (error instanceof DwsEventProcessError)
|
|
1016
1073
|
state.lastError = error;
|
|
1017
1074
|
this.logImError(state.source, error);
|
|
1018
1075
|
});
|
|
1019
1076
|
if (!this.connected || generation !== this.lifecycleGeneration) {
|
|
1020
|
-
|
|
1077
|
+
await this.drainImSubscription(subscription);
|
|
1021
1078
|
return;
|
|
1022
1079
|
}
|
|
1023
1080
|
state.lastError = undefined;
|
|
@@ -1096,13 +1153,14 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1096
1153
|
if (!this.connected)
|
|
1097
1154
|
return;
|
|
1098
1155
|
state.lastError = undefined;
|
|
1099
|
-
|
|
1156
|
+
const startup = this.startImSource(state, this.lifecycleGeneration).catch((error) => {
|
|
1100
1157
|
const resolvedError = error instanceof Error ? error : new Error(String(error));
|
|
1101
1158
|
this.logImError(state.source, resolvedError);
|
|
1102
1159
|
this.scheduleImRestart(state, resolvedError instanceof DwsEventProcessError
|
|
1103
1160
|
? resolvedError
|
|
1104
1161
|
: undefined);
|
|
1105
1162
|
});
|
|
1163
|
+
this.trackImStartup(startup);
|
|
1106
1164
|
}, delay);
|
|
1107
1165
|
state.retryTimer.unref?.();
|
|
1108
1166
|
}
|
|
@@ -1114,30 +1172,8 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1114
1172
|
this.config.groups['*']?.requireMention ??
|
|
1115
1173
|
true);
|
|
1116
1174
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
return;
|
|
1120
|
-
if (this.markSelfMessageProcessed(message))
|
|
1121
|
-
return;
|
|
1122
|
-
if (this.isStaleLiveMessage(message, fromHistory) &&
|
|
1123
|
-
message.eventTime !== undefined) {
|
|
1124
|
-
this.markProcessedMessage(messageKey(message));
|
|
1125
|
-
this.saveCursor();
|
|
1126
|
-
return;
|
|
1127
|
-
}
|
|
1128
|
-
if (source.kind === 'group-all' &&
|
|
1129
|
-
this.requiresMention(message.conversationId)) {
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
|
-
if ((source.kind === 'group' || source.kind === 'group-all') &&
|
|
1133
|
-
this.config.groupPolicy === 'pairing' &&
|
|
1134
|
-
!this.groupGate.isGroupApproved(message.conversationId)) {
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
await this.dispatchImMessage(source, message, messageKey(message));
|
|
1138
|
-
}
|
|
1139
|
-
receiveDirectMessage(message, fromHistory = false, reportFailure = fromHistory) {
|
|
1140
|
-
const admission = this.admitReceivedDirectMessage(message, fromHistory, reportFailure);
|
|
1175
|
+
receiveImMessage(source, message, fromHistory = false, reportFailure = fromHistory, admissionContext) {
|
|
1176
|
+
const admission = this.admitReceivedMessage(source, message, fromHistory, reportFailure, admissionContext);
|
|
1141
1177
|
const completed = admission.then(({ completion }) => completion);
|
|
1142
1178
|
void completed.catch(() => undefined);
|
|
1143
1179
|
return {
|
|
@@ -1145,68 +1181,145 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1145
1181
|
completed,
|
|
1146
1182
|
};
|
|
1147
1183
|
}
|
|
1148
|
-
async
|
|
1149
|
-
|
|
1150
|
-
|
|
1184
|
+
async admitReceivedMessage(source, message, fromHistory, reportFailure, admissionContext) {
|
|
1185
|
+
const isAmbientSource = source.kind === 'group' || source.kind === 'group-all';
|
|
1186
|
+
const isCurrentLifecycle = this.connected &&
|
|
1187
|
+
(admissionContext === undefined ||
|
|
1188
|
+
admissionContext.generation === this.lifecycleGeneration);
|
|
1189
|
+
if (!isCurrentLifecycle && (!admissionContext || !isAmbientSource)) {
|
|
1190
|
+
return { completion: Promise.resolve(), remembered: true };
|
|
1191
|
+
}
|
|
1151
1192
|
const key = messageKey(message);
|
|
1152
1193
|
if (this.markSelfMessageProcessed(message)) {
|
|
1153
|
-
return { completion: Promise.resolve() };
|
|
1194
|
+
return { completion: Promise.resolve(), remembered: true };
|
|
1154
1195
|
}
|
|
1155
|
-
if (this.isStaleLiveMessage(message, fromHistory) &&
|
|
1196
|
+
if (this.isStaleLiveMessage(message, fromHistory, admissionContext?.connectionStartedAt) &&
|
|
1156
1197
|
message.eventTime !== undefined) {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
// parks was strictly outside every window the watermark would ever
|
|
1163
|
-
// produce, now and forever, since the window only moves forward. Pull
|
|
1164
|
-
// the watermark back to cover what we just declined to handle.
|
|
1165
|
-
this.cursor.notificationWatermark = Math.min(this.cursor.notificationWatermark ?? this.connectionStartedAt, message.eventTime);
|
|
1166
|
-
this.notificationWatermarkPulledBack = true;
|
|
1167
|
-
// R6-1: the flag alone only covers a replay that lands while a poll's
|
|
1168
|
-
// fetch is in flight — `pollOnce` clears it before every fetch, so a
|
|
1169
|
-
// pullback arriving BETWEEN two polls is reset before it is ever read.
|
|
1170
|
-
// A persisted multi-page checkpoint would then resume its own window,
|
|
1171
|
-
// whose `startTime` is already past this replay, and finish by setting
|
|
1172
|
-
// the watermark to `checkpoint.endTime` — pulling it forward again over
|
|
1173
|
-
// a replay no window will ever cover. Drop the checkpoint here too, so
|
|
1174
|
-
// the pullback survives regardless of when it arrived.
|
|
1175
|
-
this.cursor.notificationCheckpoint = undefined;
|
|
1176
|
-
process.stderr.write(`[Channel:${this.name}] parked a stale direct message for history polling and pulled the watermark back to ${message.eventTime}: ${sanitizeLogText(message.messageId, 120)}\n`);
|
|
1198
|
+
if (source.kind === 'direct') {
|
|
1199
|
+
this.parkStaleDirectMessage(message);
|
|
1200
|
+
return { completion: Promise.resolve(), remembered: true };
|
|
1201
|
+
}
|
|
1202
|
+
this.markProcessedMessage(key);
|
|
1177
1203
|
this.saveCursor();
|
|
1178
|
-
return { completion: Promise.resolve() };
|
|
1204
|
+
return { completion: Promise.resolve(), remembered: true };
|
|
1205
|
+
}
|
|
1206
|
+
if (this.shouldFilterImMessage(source, message)) {
|
|
1207
|
+
if (source.kind === 'group' || source.kind === 'group-all') {
|
|
1208
|
+
const text = stripMessagePrefix(message.content.trim(), this.dwsMessagePrefix);
|
|
1209
|
+
if (!this.dwsMessagePrefix || text) {
|
|
1210
|
+
const envelope = this.createImEnvelope(source, message, text);
|
|
1211
|
+
if (this.groupGate.check(envelope, { createPairingRequest: false })
|
|
1212
|
+
.reason === 'mention_required' &&
|
|
1213
|
+
!this.queuedMessages.has(key) &&
|
|
1214
|
+
!this.cursor.processedMessages.includes(key) &&
|
|
1215
|
+
!this.hasPendingMessage(key)) {
|
|
1216
|
+
this.recordPendingGroupHistory(envelope);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
this.removePersistedPendingMessageForSource(key, source);
|
|
1221
|
+
return { completion: Promise.resolve(), remembered: true };
|
|
1222
|
+
}
|
|
1223
|
+
if (!isCurrentLifecycle) {
|
|
1224
|
+
if (this.cursor.processedMessages.includes(key)) {
|
|
1225
|
+
this.removePersistedPendingMessage(key);
|
|
1226
|
+
return { completion: Promise.resolve(), remembered: true };
|
|
1227
|
+
}
|
|
1228
|
+
this.rememberDrainingPendingMessage(source, message);
|
|
1229
|
+
return { completion: Promise.resolve(), remembered: true };
|
|
1179
1230
|
}
|
|
1180
|
-
return this.
|
|
1231
|
+
return this.admitMessage(source, message, key, reportFailure);
|
|
1232
|
+
}
|
|
1233
|
+
async admitHistoryMessage(source, message) {
|
|
1234
|
+
const { completion, remembered } = await this.admitReceivedMessage(source, message, true, true);
|
|
1235
|
+
if (remembered) {
|
|
1236
|
+
void completion.catch(() => undefined);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
await completion;
|
|
1240
|
+
}
|
|
1241
|
+
shouldFilterImMessage(source, message) {
|
|
1242
|
+
const groupConfig = this.config.groups[message.conversationId] ?? this.config.groups['*'];
|
|
1243
|
+
if ((source.kind === 'group' || source.kind === 'group-all') &&
|
|
1244
|
+
(groupConfig?.requireMention ?? true)) {
|
|
1245
|
+
return true;
|
|
1246
|
+
}
|
|
1247
|
+
return ((source.kind === 'group' || source.kind === 'group-all') &&
|
|
1248
|
+
this.config.groupPolicy === 'pairing' &&
|
|
1249
|
+
!this.groupGate.isGroupApproved(message.conversationId));
|
|
1250
|
+
}
|
|
1251
|
+
parkStaleDirectMessage(message) {
|
|
1252
|
+
// A replayed direct message is left UNMARKED on purpose, for history
|
|
1253
|
+
// polling to pick up. Pull the watermark back because the normal window
|
|
1254
|
+
// starts after this message and only moves forward.
|
|
1255
|
+
this.cursor.notificationWatermark = Math.min(this.cursor.notificationWatermark ?? this.connectionStartedAt, message.eventTime);
|
|
1256
|
+
this.notificationWatermarkPulledBack = true;
|
|
1257
|
+
// R6-1: also drop a persisted multi-page checkpoint so a replay that
|
|
1258
|
+
// arrives between polls cannot be skipped when that checkpoint resumes.
|
|
1259
|
+
this.cursor.notificationCheckpoint = undefined;
|
|
1260
|
+
process.stderr.write(`[Channel:${this.name}] parked a stale direct message for history polling and pulled the watermark back to ${message.eventTime}: ${sanitizeLogText(message.messageId, 120)}\n`);
|
|
1261
|
+
this.saveCursor();
|
|
1181
1262
|
}
|
|
1182
1263
|
markSelfMessageProcessed(message) {
|
|
1183
1264
|
if (!this.isSelfMessage(message))
|
|
1184
1265
|
return false;
|
|
1185
|
-
|
|
1266
|
+
const key = messageKey(message);
|
|
1267
|
+
this.markProcessedMessage(key);
|
|
1268
|
+
this.removePendingMessage(key);
|
|
1186
1269
|
this.saveCursor();
|
|
1187
1270
|
return true;
|
|
1188
1271
|
}
|
|
1189
|
-
isStaleLiveMessage(message, fromHistory) {
|
|
1272
|
+
isStaleLiveMessage(message, fromHistory, connectionStartedAt = this.connectionStartedAt) {
|
|
1190
1273
|
return (!fromHistory &&
|
|
1191
1274
|
message.eventTime !== undefined &&
|
|
1192
|
-
message.eventTime <
|
|
1275
|
+
message.eventTime < connectionStartedAt - 5_000);
|
|
1193
1276
|
}
|
|
1194
|
-
async
|
|
1277
|
+
async admitMessage(source, message, key, reportFailure) {
|
|
1195
1278
|
if (this.cursor.processedMessages.includes(key)) {
|
|
1196
1279
|
this.removePersistedPendingMessage(key);
|
|
1197
|
-
return { completion: Promise.resolve() };
|
|
1280
|
+
return { completion: Promise.resolve(), remembered: true };
|
|
1198
1281
|
}
|
|
1282
|
+
if (!reportFailure) {
|
|
1283
|
+
this.enqueuePendingConversation(message.conversationId);
|
|
1284
|
+
}
|
|
1285
|
+
let remembered = true;
|
|
1199
1286
|
if (!this.hasPendingMessage(key)) {
|
|
1200
|
-
|
|
1201
|
-
|
|
1287
|
+
const dispatchUnparkedAtCapacity = (source.kind === 'at' || source.kind === 'direct') &&
|
|
1288
|
+
this.connected &&
|
|
1289
|
+
(this.cursor.pendingMessages?.length ?? 0) >= MAX_PROCESSED_ITEMS;
|
|
1290
|
+
remembered = dispatchUnparkedAtCapacity
|
|
1291
|
+
? false
|
|
1292
|
+
: source.kind === 'group' || source.kind === 'group-all'
|
|
1293
|
+
? await this.rememberPendingMessageWhenAvailable(source, message)
|
|
1294
|
+
: await this.rememberPendingMessage(source, message);
|
|
1295
|
+
if (!remembered) {
|
|
1296
|
+
if (!dispatchUnparkedAtCapacity) {
|
|
1297
|
+
return { completion: Promise.resolve(), remembered };
|
|
1298
|
+
}
|
|
1202
1299
|
}
|
|
1203
1300
|
}
|
|
1204
1301
|
return {
|
|
1205
|
-
completion: this.
|
|
1302
|
+
completion: this.scheduleMessage(source, message, key, reportFailure),
|
|
1303
|
+
remembered,
|
|
1206
1304
|
};
|
|
1207
1305
|
}
|
|
1208
|
-
|
|
1209
|
-
const
|
|
1306
|
+
enqueuePendingConversation(conversationId) {
|
|
1307
|
+
for (const pending of [...(this.cursor.pendingMessages ?? [])]) {
|
|
1308
|
+
if (pending.message.conversationId !== conversationId)
|
|
1309
|
+
continue;
|
|
1310
|
+
const key = messageKey(pending.message);
|
|
1311
|
+
if (this.attemptedPendingMessages.has(key))
|
|
1312
|
+
continue;
|
|
1313
|
+
if (this.queuedMessages.has(key))
|
|
1314
|
+
continue;
|
|
1315
|
+
const dispatch = this.receiveImMessage(pending.source, pending.message, true, true);
|
|
1316
|
+
void dispatch.admitted.catch((error) => {
|
|
1317
|
+
process.stderr.write(`[Channel:${this.name}] pending DWS message remains degraded: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 300)}\n`);
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
scheduleMessage(source, message, key, reportFailure) {
|
|
1322
|
+
const queued = this.queuedMessages.get(key);
|
|
1210
1323
|
if (queued)
|
|
1211
1324
|
return queued;
|
|
1212
1325
|
if (this.cursor.processedMessages.includes(key)) {
|
|
@@ -1218,11 +1331,11 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1218
1331
|
try {
|
|
1219
1332
|
if (!this.connected || generation !== this.lifecycleGeneration)
|
|
1220
1333
|
return;
|
|
1221
|
-
await this.dispatchImMessage(source, message, key);
|
|
1334
|
+
await this.dispatchImMessage(source, message, key, generation);
|
|
1222
1335
|
}
|
|
1223
1336
|
finally {
|
|
1224
|
-
if (this.
|
|
1225
|
-
this.
|
|
1337
|
+
if (this.queuedMessages.get(key) === task) {
|
|
1338
|
+
this.queuedMessages.delete(key);
|
|
1226
1339
|
}
|
|
1227
1340
|
}
|
|
1228
1341
|
};
|
|
@@ -1230,24 +1343,30 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1230
1343
|
const started = new Promise((resolve) => {
|
|
1231
1344
|
resolveStarted = resolve;
|
|
1232
1345
|
});
|
|
1233
|
-
const previous = this.
|
|
1346
|
+
const previous = this.conversationTails.get(message.conversationId);
|
|
1347
|
+
const groupConfig = source.kind === 'direct'
|
|
1348
|
+
? undefined
|
|
1349
|
+
: (this.config.groups[message.conversationId] ??
|
|
1350
|
+
this.config.groups['*']);
|
|
1234
1351
|
// Live turns only need FIFO through ChannelBase registration; replay and
|
|
1235
1352
|
// followup turns must wait for the prior turn to finish.
|
|
1236
|
-
const predecessor = reportFailure ||
|
|
1353
|
+
const predecessor = reportFailure ||
|
|
1354
|
+
(groupConfig?.dispatchMode ?? this.config.dispatchMode) === 'followup'
|
|
1237
1355
|
? previous?.completed
|
|
1238
1356
|
: previous?.started;
|
|
1239
1357
|
const task = predecessor
|
|
1240
1358
|
? predecessor.catch(() => undefined).then(dispatch)
|
|
1241
1359
|
: Promise.resolve().then(dispatch);
|
|
1242
1360
|
const tail = { started, completed: task };
|
|
1243
|
-
this.
|
|
1244
|
-
|
|
1245
|
-
this.
|
|
1361
|
+
this.queuedMessages.set(key, task);
|
|
1362
|
+
const startResolver = { generation, resolve: resolveStarted };
|
|
1363
|
+
this.messageStartResolvers.set(key, startResolver);
|
|
1364
|
+
this.conversationTails.set(message.conversationId, tail);
|
|
1246
1365
|
void task
|
|
1247
1366
|
.finally(() => {
|
|
1248
|
-
this.
|
|
1249
|
-
if (this.
|
|
1250
|
-
this.
|
|
1367
|
+
this.releaseMessageStart(message.conversationId, message.messageId, startResolver);
|
|
1368
|
+
if (this.conversationTails.get(message.conversationId) === tail) {
|
|
1369
|
+
this.conversationTails.delete(message.conversationId);
|
|
1251
1370
|
}
|
|
1252
1371
|
})
|
|
1253
1372
|
.catch(() => undefined);
|
|
@@ -1258,25 +1377,33 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1258
1377
|
}
|
|
1259
1378
|
return task;
|
|
1260
1379
|
}
|
|
1261
|
-
async dispatchImMessage(source, message, key) {
|
|
1380
|
+
async dispatchImMessage(source, message, key, generation) {
|
|
1262
1381
|
let waitedOnInFlight = false;
|
|
1382
|
+
let waitedOnCurrentGeneration = false;
|
|
1263
1383
|
let inFlightError;
|
|
1264
1384
|
while (true) {
|
|
1265
1385
|
const existing = this.processingMessages.get(key);
|
|
1266
1386
|
if (!existing)
|
|
1267
1387
|
break;
|
|
1268
1388
|
waitedOnInFlight = true;
|
|
1389
|
+
if (this.processingMessageGenerations.get(key) === generation) {
|
|
1390
|
+
waitedOnCurrentGeneration = true;
|
|
1391
|
+
}
|
|
1269
1392
|
inFlightError = await existing.then(() => undefined, (error) => error);
|
|
1270
1393
|
}
|
|
1394
|
+
if (!this.connected || generation !== this.lifecycleGeneration)
|
|
1395
|
+
return;
|
|
1271
1396
|
if (this.cursor.processedMessages.includes(key))
|
|
1272
1397
|
return;
|
|
1273
|
-
//
|
|
1274
|
-
//
|
|
1275
|
-
//
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1398
|
+
// A duplicate in the same lifecycle must not spend the retry budget twice
|
|
1399
|
+
// in one poll. A replay from a later reconnect generation is different:
|
|
1400
|
+
// it owns the persisted retry and must run before newer conversation work.
|
|
1401
|
+
if (waitedOnInFlight &&
|
|
1402
|
+
waitedOnCurrentGeneration &&
|
|
1403
|
+
this.hasPendingMessage(key)) {
|
|
1279
1404
|
throw inFlightError;
|
|
1405
|
+
}
|
|
1406
|
+
this.processingMessageGenerations.set(key, generation);
|
|
1280
1407
|
const task = this.processImMessage(source, message, key);
|
|
1281
1408
|
this.processingMessages.set(key, task);
|
|
1282
1409
|
try {
|
|
@@ -1289,6 +1416,7 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1289
1416
|
finally {
|
|
1290
1417
|
if (this.processingMessages.get(key) === task) {
|
|
1291
1418
|
this.processingMessages.delete(key);
|
|
1419
|
+
this.processingMessageGenerations.delete(key);
|
|
1292
1420
|
}
|
|
1293
1421
|
}
|
|
1294
1422
|
}
|
|
@@ -1339,8 +1467,32 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1339
1467
|
await this.processDocumentNotification(message, key, documentNotification);
|
|
1340
1468
|
return;
|
|
1341
1469
|
}
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1470
|
+
const envelope = this.createImEnvelope(source, message, text);
|
|
1471
|
+
this.rememberInboundReactionTarget(message.conversationId, message.messageId);
|
|
1472
|
+
try {
|
|
1473
|
+
await this.handleInbound(envelope);
|
|
1474
|
+
}
|
|
1475
|
+
catch (error) {
|
|
1476
|
+
this.attemptedPendingMessages.add(key);
|
|
1477
|
+
// Under budget the throw propagates so redelivery observes the failure
|
|
1478
|
+
// while persisted replay can retry it. Once the budget is spent the
|
|
1479
|
+
// message is marked processed and the error is swallowed, which is the
|
|
1480
|
+
// only thing that lets the watermark move past it.
|
|
1481
|
+
if (this.recordInboundFailure(key, error, () => {
|
|
1482
|
+
this.markProcessedMessage(key);
|
|
1483
|
+
this.removePendingMessage(key);
|
|
1484
|
+
})) {
|
|
1485
|
+
throw error;
|
|
1486
|
+
}
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
this.clearInboundFailure(key);
|
|
1490
|
+
this.removePendingMessage(key);
|
|
1491
|
+
this.markProcessedMessage(key);
|
|
1492
|
+
this.saveCursor();
|
|
1493
|
+
}
|
|
1494
|
+
createImEnvelope(source, message, text = message.content.trim()) {
|
|
1495
|
+
return {
|
|
1344
1496
|
channelName: this.name,
|
|
1345
1497
|
senderId: message.senderId,
|
|
1346
1498
|
senderName: message.senderName,
|
|
@@ -1352,7 +1504,7 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1352
1504
|
...(message.referencedText
|
|
1353
1505
|
? { referencedText: message.referencedText }
|
|
1354
1506
|
: {}),
|
|
1355
|
-
isGroup,
|
|
1507
|
+
isGroup: source.kind !== 'direct',
|
|
1356
1508
|
isMentioned: source.kind === 'at',
|
|
1357
1509
|
isReplyToBot: false,
|
|
1358
1510
|
metadata: [
|
|
@@ -1361,34 +1513,6 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1361
1513
|
`DWS event ID: ${message.eventId}`,
|
|
1362
1514
|
].join('\n'),
|
|
1363
1515
|
};
|
|
1364
|
-
this.rememberInboundReactionTarget(message.conversationId, message.messageId);
|
|
1365
|
-
try {
|
|
1366
|
-
await this.handleInbound(envelope);
|
|
1367
|
-
}
|
|
1368
|
-
catch (error) {
|
|
1369
|
-
if (source.kind !== 'at') {
|
|
1370
|
-
// R12-1: park failed direct messages next to ambient group ones.
|
|
1371
|
-
// Direct-message history may be unavailable, and ambient group
|
|
1372
|
-
// messages have no history fallback. `at` messages need no parking:
|
|
1373
|
-
// the pinned mention checkpoint re-fetches them.
|
|
1374
|
-
await this.rememberFailedMessage(source, message);
|
|
1375
|
-
}
|
|
1376
|
-
// Under budget the throw propagates exactly as before, so redelivery
|
|
1377
|
-
// and concurrent-duplicate retry keep their contracts. Once the budget
|
|
1378
|
-
// is spent the message is marked processed and the error is swallowed,
|
|
1379
|
-
// which is the only thing that lets the watermark move past it.
|
|
1380
|
-
if (this.recordInboundFailure(key, error, () => {
|
|
1381
|
-
this.markProcessedMessage(key);
|
|
1382
|
-
this.removePendingMessage(key);
|
|
1383
|
-
})) {
|
|
1384
|
-
throw error;
|
|
1385
|
-
}
|
|
1386
|
-
return;
|
|
1387
|
-
}
|
|
1388
|
-
this.clearInboundFailure(key);
|
|
1389
|
-
this.removePendingMessage(key);
|
|
1390
|
-
this.markProcessedMessage(key);
|
|
1391
|
-
this.saveCursor();
|
|
1392
1516
|
}
|
|
1393
1517
|
/**
|
|
1394
1518
|
* Account one failed inbound turn, and drop the message once its budget is
|
|
@@ -1453,15 +1577,18 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1453
1577
|
this.saveCursor();
|
|
1454
1578
|
}
|
|
1455
1579
|
catch (error) {
|
|
1580
|
+
if (source.kind === 'group' || source.kind === 'group-all') {
|
|
1581
|
+
return true;
|
|
1582
|
+
}
|
|
1456
1583
|
this.removePendingMessage(key);
|
|
1457
1584
|
throw error;
|
|
1458
1585
|
}
|
|
1459
1586
|
return true;
|
|
1460
1587
|
}
|
|
1461
|
-
async
|
|
1588
|
+
async rememberPendingMessageWhenAvailable(source, message) {
|
|
1462
1589
|
const key = messageKey(message);
|
|
1463
1590
|
if (this.hasPendingMessage(key))
|
|
1464
|
-
return;
|
|
1591
|
+
return true;
|
|
1465
1592
|
const generation = this.lifecycleGeneration;
|
|
1466
1593
|
while (this.connected &&
|
|
1467
1594
|
generation === this.lifecycleGeneration &&
|
|
@@ -1470,6 +1597,19 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1470
1597
|
this.pendingMessageCapacityWaiters.add(resolve);
|
|
1471
1598
|
});
|
|
1472
1599
|
}
|
|
1600
|
+
if (this.hasPendingMessage(key))
|
|
1601
|
+
return true;
|
|
1602
|
+
if (!this.connected || generation !== this.lifecycleGeneration) {
|
|
1603
|
+
// Ambient group streams have no history fallback. A disconnect wakes
|
|
1604
|
+
// capacity waiters, so persist their already-read message before the
|
|
1605
|
+
// event process exits even when that temporarily exceeds the soft cap.
|
|
1606
|
+
this.rememberDrainingPendingMessage(source, message);
|
|
1607
|
+
return true;
|
|
1608
|
+
}
|
|
1609
|
+
return this.rememberPendingMessage(source, message);
|
|
1610
|
+
}
|
|
1611
|
+
rememberDrainingPendingMessage(source, message) {
|
|
1612
|
+
const key = messageKey(message);
|
|
1473
1613
|
if (this.hasPendingMessage(key))
|
|
1474
1614
|
return;
|
|
1475
1615
|
const pending = this.cursor.pendingMessages ?? [];
|
|
@@ -1478,14 +1618,20 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1478
1618
|
try {
|
|
1479
1619
|
this.saveCursor();
|
|
1480
1620
|
}
|
|
1481
|
-
catch {
|
|
1482
|
-
|
|
1621
|
+
catch (error) {
|
|
1622
|
+
process.stderr.write(`[Channel:${this.name}] could not persist a draining DWS message; keeping it in memory: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 300)}\n`);
|
|
1483
1623
|
}
|
|
1484
1624
|
}
|
|
1485
1625
|
removePersistedPendingMessage(key) {
|
|
1486
1626
|
if (this.removePendingMessage(key))
|
|
1487
1627
|
this.saveCursor();
|
|
1488
1628
|
}
|
|
1629
|
+
removePersistedPendingMessageForSource(key, source) {
|
|
1630
|
+
const ownsPendingMessage = (this.cursor.pendingMessages ?? []).some((pending) => messageKey(pending.message) === key &&
|
|
1631
|
+
sameImSource(pending.source, source));
|
|
1632
|
+
if (ownsPendingMessage)
|
|
1633
|
+
this.removePersistedPendingMessage(key);
|
|
1634
|
+
}
|
|
1489
1635
|
removePendingMessage(key) {
|
|
1490
1636
|
const pending = this.cursor.pendingMessages ?? [];
|
|
1491
1637
|
const remaining = pending.filter((item) => messageKey(item.message) !== key);
|
|
@@ -1611,37 +1757,71 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1611
1757
|
}
|
|
1612
1758
|
}
|
|
1613
1759
|
async replayPendingMessages(signal) {
|
|
1760
|
+
const activeBySource = new Map();
|
|
1761
|
+
const activeConversations = new Set();
|
|
1762
|
+
for (const { sourceKind, conversationId, } of this.replayDispatches.values()) {
|
|
1763
|
+
if (!activeConversations.has(conversationId)) {
|
|
1764
|
+
activeBySource.set(sourceKind, (activeBySource.get(sourceKind) ?? 0) + 1);
|
|
1765
|
+
activeConversations.add(conversationId);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
const selectedConversations = new Set();
|
|
1769
|
+
const blockedConversations = new Set();
|
|
1614
1770
|
for (const pending of [...(this.cursor.pendingMessages ?? [])]) {
|
|
1615
1771
|
if (signal.aborted || !this.connected)
|
|
1616
1772
|
return;
|
|
1617
1773
|
const key = messageKey(pending.message);
|
|
1618
|
-
if (
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1774
|
+
if (this.queuedMessages.has(key))
|
|
1775
|
+
continue;
|
|
1776
|
+
if (this.markSelfMessageProcessed(pending.message))
|
|
1777
|
+
continue;
|
|
1778
|
+
if (this.shouldFilterImMessage(pending.source, pending.message)) {
|
|
1779
|
+
if (pending.source.kind === 'group' ||
|
|
1780
|
+
pending.source.kind === 'group-all') {
|
|
1781
|
+
const text = stripMessagePrefix(pending.message.content.trim(), this.dwsMessagePrefix);
|
|
1782
|
+
if (!this.dwsMessagePrefix || text) {
|
|
1783
|
+
const envelope = this.createImEnvelope(pending.source, pending.message, text);
|
|
1784
|
+
if (this.groupGate.check(envelope, { createPairingRequest: false })
|
|
1785
|
+
.reason === 'mention_required') {
|
|
1786
|
+
this.recordPendingGroupHistory(envelope);
|
|
1787
|
+
}
|
|
1630
1788
|
}
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1789
|
+
}
|
|
1790
|
+
this.removePersistedPendingMessageForSource(key, pending.source);
|
|
1633
1791
|
continue;
|
|
1634
1792
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1793
|
+
if (activeConversations.has(pending.message.conversationId))
|
|
1794
|
+
continue;
|
|
1795
|
+
if (blockedConversations.has(pending.message.conversationId))
|
|
1796
|
+
continue;
|
|
1797
|
+
if (!selectedConversations.has(pending.message.conversationId)) {
|
|
1798
|
+
const activeForSource = activeBySource.get(pending.source.kind) ?? 0;
|
|
1799
|
+
if (activeForSource >= MAX_REPLAY_CONVERSATIONS) {
|
|
1800
|
+
blockedConversations.add(pending.message.conversationId);
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
activeBySource.set(pending.source.kind, activeForSource + 1);
|
|
1804
|
+
selectedConversations.add(pending.message.conversationId);
|
|
1639
1805
|
}
|
|
1640
|
-
|
|
1806
|
+
const dispatch = this.receiveImMessage(pending.source, pending.message, true, true);
|
|
1807
|
+
const completed = dispatch.completed;
|
|
1808
|
+
const replay = {
|
|
1809
|
+
sourceKind: pending.source.kind,
|
|
1810
|
+
conversationId: pending.message.conversationId,
|
|
1811
|
+
};
|
|
1812
|
+
this.replayDispatches.set(key, replay);
|
|
1813
|
+
void dispatch.admitted.catch((error) => {
|
|
1641
1814
|
if (signal.aborted || !this.connected)
|
|
1642
1815
|
return;
|
|
1643
1816
|
process.stderr.write(`[Channel:${this.name}] pending DWS message remains degraded: ${sanitizeLogText(error instanceof Error ? error.message : String(error), 300)}\n`);
|
|
1644
|
-
}
|
|
1817
|
+
});
|
|
1818
|
+
void completed
|
|
1819
|
+
.finally(() => {
|
|
1820
|
+
if (this.replayDispatches.get(key) === replay) {
|
|
1821
|
+
this.replayDispatches.delete(key);
|
|
1822
|
+
}
|
|
1823
|
+
})
|
|
1824
|
+
.catch(() => undefined);
|
|
1645
1825
|
}
|
|
1646
1826
|
}
|
|
1647
1827
|
async replayPendingDocumentNotifications(signal) {
|
|
@@ -1738,13 +1918,17 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1738
1918
|
reactionKey(conversationId, messageId) {
|
|
1739
1919
|
return `${conversationId}\0${messageId}`;
|
|
1740
1920
|
}
|
|
1741
|
-
|
|
1921
|
+
releaseMessageStart(conversationId, messageId, expected) {
|
|
1742
1922
|
const key = `${conversationId}\0${messageId}`;
|
|
1743
|
-
const
|
|
1744
|
-
if (!
|
|
1923
|
+
const resolver = this.messageStartResolvers.get(key);
|
|
1924
|
+
if (!resolver || (expected && resolver !== expected))
|
|
1745
1925
|
return;
|
|
1746
|
-
|
|
1747
|
-
|
|
1926
|
+
if (!expected &&
|
|
1927
|
+
this.processingMessageGenerations.get(key) !== resolver.generation) {
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
this.messageStartResolvers.delete(key);
|
|
1931
|
+
resolver.resolve();
|
|
1748
1932
|
}
|
|
1749
1933
|
rememberInboundReactionTarget(chatId, messageId, conversationId = chatId) {
|
|
1750
1934
|
const key = this.reactionKey(chatId, messageId);
|
|
@@ -1904,7 +2088,7 @@ export class DwsChannel extends PollingChannelBase {
|
|
|
1904
2088
|
onTaskLifecycle(event) {
|
|
1905
2089
|
if (event.type === 'started') {
|
|
1906
2090
|
if (event.messageId) {
|
|
1907
|
-
this.
|
|
2091
|
+
this.releaseMessageStart(event.chatId, event.messageId);
|
|
1908
2092
|
}
|
|
1909
2093
|
this.startReaction(event.chatId, event.messageId, event.sessionId);
|
|
1910
2094
|
return;
|