@xmanrui/dsh-im 2.4.0 → 2.6.0
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/README.en.md +2 -2
- package/README.md +2 -2
- package/lib/client.js +322 -109
- package/lib/index.js +218 -211
- package/package.json +1 -1
- package/plugin-src/client/channel-card-meta.js +36 -1
- package/plugin-src/client/channels/dingtalk/api.js +2 -0
- package/plugin-src/client/channels/dingtalk/index.js +9 -1
- package/plugin-src/client/channels/feishu/api.js +3 -0
- package/plugin-src/client/channels/feishu/index.js +9 -1
- package/plugin-src/client/channels/qq/api.js +2 -0
- package/plugin-src/client/channels/qq/index.js +9 -1
- package/plugin-src/client/channels/shared/token-api.js +2 -0
- package/plugin-src/client/channels/shared/token-channel.js +9 -1
- package/plugin-src/client/channels/wecom/api.js +2 -0
- package/plugin-src/client/channels/wecom/index.js +9 -1
- package/plugin-src/client/channels/weixin/api.js +2 -10
- package/plugin-src/client/channels/weixin/index.js +9 -5
- package/plugin-src/client/channels/whatsapp/api.js +2 -0
- package/plugin-src/client/channels/whatsapp/index.js +9 -1
- package/plugin-src/client/i18n.js +4 -0
- package/plugin-src/client/index.js +16 -2
- package/plugin-src/client/last-message-error.js +17 -0
- package/plugin-src/client/styles.js +5 -1
- package/plugin-src/host/channels/feishu/rpc.mjs +3 -0
- package/src/channels/dingtalk/dingtalk-api.mjs +102 -6
- package/src/channels/dingtalk/dingtalk-bridge.mjs +247 -38
- package/src/channels/dingtalk/dingtalk-card-stream.mjs +2 -2
- package/src/channels/dingtalk/dingtalk-controller.mjs +2 -0
- package/src/channels/discord/discord-api.mjs +35 -0
- package/src/channels/discord/discord-bridge.mjs +1 -0
- package/src/channels/discord/discord-runtime.mjs +26 -0
- package/src/channels/feishu/bridge.mjs +115 -69
- package/src/channels/feishu/multi-bot-controller.mjs +2 -0
- package/src/channels/qq/qq-bridge.mjs +83 -28
- package/src/channels/qq/qq-controller.mjs +2 -0
- package/src/channels/shared/harness-client.mjs +40 -4
- package/src/channels/shared/i18n-en/shared-a.mjs +77 -0
- package/src/channels/shared/message-failure.mjs +244 -0
- package/src/channels/shared/semantic/artifact-delivery.mjs +9 -2
- package/src/channels/shared/status-reaction.mjs +107 -0
- package/src/channels/shared/text-harness-bridge.mjs +123 -66
- package/src/channels/shared/token-bot-controller.mjs +2 -0
- package/src/channels/slack/manifest.mjs +1 -0
- package/src/channels/slack/slack-api.mjs +28 -0
- package/src/channels/slack/slack-bridge.mjs +5 -0
- package/src/channels/slack/slack-controller.mjs +2 -0
- package/src/channels/slack/slack-runtime.mjs +24 -0
- package/src/channels/telegram/telegram-api.mjs +9 -0
- package/src/channels/telegram/telegram-bridge.mjs +1 -0
- package/src/channels/telegram/telegram-runtime.mjs +20 -0
- package/src/channels/wecom/wecom-bridge.mjs +49 -15
- package/src/channels/wecom/wecom-controller.mjs +2 -0
- package/src/channels/weixin/weixin-api.mjs +47 -0
- package/src/channels/weixin/weixin-bridge.mjs +261 -43
- package/src/channels/weixin/weixin-controller.mjs +2 -15
- package/src/channels/weixin/weixin-runtime.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-bridge.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +2 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +36 -0
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from './batch-input.mjs';
|
|
28
28
|
import {
|
|
29
29
|
hasInboundImages,
|
|
30
|
+
imagePromptDiagnostic,
|
|
30
31
|
imagePromptUserMessage,
|
|
31
32
|
promptContentForMessage,
|
|
32
33
|
} from './image-prompt.mjs';
|
|
@@ -45,6 +46,13 @@ import {
|
|
|
45
46
|
createTextDeliveryBlock,
|
|
46
47
|
providerMessageIdsFor,
|
|
47
48
|
} from './semantic/delivery.mjs';
|
|
49
|
+
import {
|
|
50
|
+
channelDeliveryFailure,
|
|
51
|
+
clearLastMessageFailure,
|
|
52
|
+
messageFailureText,
|
|
53
|
+
setLastMessageFailure,
|
|
54
|
+
} from './message-failure.mjs';
|
|
55
|
+
import { beginStatusReaction } from './status-reaction.mjs';
|
|
48
56
|
|
|
49
57
|
const INTERACTION_RESOLVED_TEXT = '这个问题已在其他客户端处理,无需再次回答。';
|
|
50
58
|
const FILE_ONLY_COMPLETION_TEXT = '任务已完成。';
|
|
@@ -106,6 +114,10 @@ export function createTextBridgeStatus() {
|
|
|
106
114
|
lastReplyAt: null,
|
|
107
115
|
lastRejectedAt: null,
|
|
108
116
|
lastError: null,
|
|
117
|
+
lastMessageError: null,
|
|
118
|
+
reactionsAdded: 0,
|
|
119
|
+
reactionsRemoved: 0,
|
|
120
|
+
reactionErrors: 0,
|
|
109
121
|
};
|
|
110
122
|
}
|
|
111
123
|
|
|
@@ -170,6 +182,27 @@ export class TextHarnessBridge {
|
|
|
170
182
|
return Promise.resolve();
|
|
171
183
|
}
|
|
172
184
|
this.#acceptedMessageIds.add(messageId);
|
|
185
|
+
const statusReaction = beginStatusReaction({
|
|
186
|
+
adapter: this.#bot,
|
|
187
|
+
target: normalized.kind === 'direct' || normalized.addressed === true
|
|
188
|
+
? normalized.reactionTarget
|
|
189
|
+
: null,
|
|
190
|
+
reactions: this.#descriptor.reactions,
|
|
191
|
+
status: this.#status,
|
|
192
|
+
logger: this.#logger,
|
|
193
|
+
label: this.#descriptor.key,
|
|
194
|
+
});
|
|
195
|
+
normalized.statusReaction = statusReaction;
|
|
196
|
+
|
|
197
|
+
const processing = this.#acceptAcceptedMessage(normalized, messageId, senderId);
|
|
198
|
+
void processing.then(
|
|
199
|
+
() => statusReaction.success(),
|
|
200
|
+
() => statusReaction.error(),
|
|
201
|
+
);
|
|
202
|
+
return processing;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#acceptAcceptedMessage(normalized, messageId, senderId) {
|
|
173
206
|
if (normalized.kind === 'direct') {
|
|
174
207
|
rememberConnectionTestTarget(
|
|
175
208
|
this.#state,
|
|
@@ -177,7 +210,7 @@ export class TextHarnessBridge {
|
|
|
177
210
|
);
|
|
178
211
|
}
|
|
179
212
|
|
|
180
|
-
const key = `${kind}:${conversationId}`;
|
|
213
|
+
const key = `${normalized.kind}:${normalized.conversationId}`;
|
|
181
214
|
const pending = this.#pendingInteractions.get(key);
|
|
182
215
|
const text = cleanText(normalized.content);
|
|
183
216
|
const batchCommand = isBatchInputCommand(text);
|
|
@@ -318,10 +351,15 @@ export class TextHarnessBridge {
|
|
|
318
351
|
if (reply) await this.#bot.sendText(message.replyTarget, reply);
|
|
319
352
|
this.#status.lastError = null;
|
|
320
353
|
})().catch(async (error) => {
|
|
321
|
-
if (this.#signal?.aborted)
|
|
354
|
+
if (this.#signal?.aborted) {
|
|
355
|
+
message.statusReaction?.clear();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
message.statusReaction?.error();
|
|
322
359
|
this.#status.lastError = error?.message ?? String(error);
|
|
360
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
323
361
|
this.#logger.error?.(
|
|
324
|
-
`[dsh-im:${this.#descriptor.key}] failed to process a batch input message:`,
|
|
362
|
+
`[dsh-im:${this.#descriptor.key}] failed to process a batch input message [${failure.referenceId}]:`,
|
|
325
363
|
error,
|
|
326
364
|
);
|
|
327
365
|
}).finally(() => {
|
|
@@ -397,10 +435,18 @@ export class TextHarnessBridge {
|
|
|
397
435
|
}
|
|
398
436
|
this.#status.lastError = null;
|
|
399
437
|
} catch (error) {
|
|
400
|
-
if (error?.code === 'turn-stopped' || this.#signal?.aborted)
|
|
438
|
+
if (error?.code === 'turn-stopped' || this.#signal?.aborted) {
|
|
439
|
+
message.statusReaction?.clear();
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
message.statusReaction?.error();
|
|
401
443
|
this.#status.lastError = error?.message ?? String(error);
|
|
402
|
-
|
|
403
|
-
|
|
444
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
445
|
+
this.#logger.error?.(
|
|
446
|
+
`[dsh-im:${this.#descriptor.key}] failed to process a command [${failure.referenceId}]:`,
|
|
447
|
+
error,
|
|
448
|
+
);
|
|
449
|
+
await this.#bot.sendText(target, messageFailureText(failure)).catch(() => undefined);
|
|
404
450
|
}
|
|
405
451
|
}
|
|
406
452
|
|
|
@@ -426,9 +472,13 @@ export class TextHarnessBridge {
|
|
|
426
472
|
sendFile: typeof this.#bot.sendFile === 'function'
|
|
427
473
|
? (file) => this.#bot.sendFile(target, file)
|
|
428
474
|
: undefined,
|
|
429
|
-
|
|
475
|
+
onFailure: (artifact, error) => setLastMessageFailure(this.#status, error, {
|
|
476
|
+
userMessage: artifactFailureText(artifact?.fileName, error, this.#descriptor),
|
|
477
|
+
reason: error?.code,
|
|
478
|
+
}),
|
|
479
|
+
sendFailureNotice: (_artifact, _error, failure) => this.#bot.sendText(
|
|
430
480
|
target,
|
|
431
|
-
|
|
481
|
+
messageFailureText(failure),
|
|
432
482
|
),
|
|
433
483
|
logger: this.#logger,
|
|
434
484
|
});
|
|
@@ -436,7 +486,11 @@ export class TextHarnessBridge {
|
|
|
436
486
|
+ delivery.artifactsSent;
|
|
437
487
|
this.#status.artifactSendErrors = (this.#status.artifactSendErrors ?? 0)
|
|
438
488
|
+ delivery.artifactSendErrors;
|
|
439
|
-
return {
|
|
489
|
+
return {
|
|
490
|
+
receipt: delivery.receipt,
|
|
491
|
+
userVisible: delivery.userVisible,
|
|
492
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
493
|
+
};
|
|
440
494
|
}
|
|
441
495
|
|
|
442
496
|
async #process(message, messageId, senderId, conversationKey, {
|
|
@@ -643,25 +697,37 @@ export class TextHarnessBridge {
|
|
|
643
697
|
reason: result?.reason,
|
|
644
698
|
});
|
|
645
699
|
} catch (error) {
|
|
646
|
-
textDeliveryError = error;
|
|
700
|
+
textDeliveryError = channelDeliveryFailure(error);
|
|
647
701
|
}
|
|
648
702
|
}
|
|
703
|
+
const finalDeliveryUnknown = textReceipt?.deliveryOutcome === 'unknown';
|
|
649
704
|
if (textReceipt?.deliveryOutcome === 'failed') {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
705
|
+
textDeliveryError = channelDeliveryFailure(
|
|
706
|
+
new Error(`Final text delivery failed (${textReceipt.reason ?? 'unknown'})`),
|
|
707
|
+
{ uncertain: false },
|
|
708
|
+
);
|
|
709
|
+
} else if (finalDeliveryUnknown) {
|
|
710
|
+
textDeliveryError = channelDeliveryFailure(
|
|
711
|
+
new Error(`Final text delivery outcome is unknown (${textReceipt.reason ?? 'unknown'})`),
|
|
712
|
+
);
|
|
653
713
|
}
|
|
654
714
|
// A failed final text must not discard an already registered result file.
|
|
655
715
|
// Settle the independent attachment path before surfacing the text error.
|
|
656
716
|
const delivery = await this.#deliverArtifacts(target, messageId, artifacts, textReceipt);
|
|
657
|
-
if (textDeliveryError && !delivery.userVisible) {
|
|
717
|
+
if (textDeliveryError && (!delivery.userVisible || finalDeliveryUnknown)) {
|
|
658
718
|
textDeliveryError.deliveryReceipt = delivery.receipt;
|
|
659
719
|
throw textDeliveryError;
|
|
660
720
|
}
|
|
721
|
+
if (textDeliveryError && delivery.artifactSendErrors === 0) {
|
|
722
|
+
setLastMessageFailure(this.#status, textDeliveryError);
|
|
723
|
+
}
|
|
661
724
|
if (delivery.userVisible) {
|
|
662
725
|
this.#status.messagesReplied += 1;
|
|
663
726
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
664
727
|
this.#status.lastError = null;
|
|
728
|
+
if (!textDeliveryError && delivery.artifactSendErrors === 0) {
|
|
729
|
+
clearLastMessageFailure(this.#status);
|
|
730
|
+
}
|
|
665
731
|
}
|
|
666
732
|
return delivery.receipt;
|
|
667
733
|
} catch (error) {
|
|
@@ -673,6 +739,7 @@ export class TextHarnessBridge {
|
|
|
673
739
|
? this.#batches.fail(conversationKey, batchSubmission.token)
|
|
674
740
|
: null;
|
|
675
741
|
if (turnStopped) {
|
|
742
|
+
message.statusReaction?.clear();
|
|
676
743
|
if (stream) {
|
|
677
744
|
try {
|
|
678
745
|
await stream.finish(t('已停止。'));
|
|
@@ -683,15 +750,22 @@ export class TextHarnessBridge {
|
|
|
683
750
|
return;
|
|
684
751
|
}
|
|
685
752
|
if (this.#signal?.aborted) {
|
|
753
|
+
message.statusReaction?.clear();
|
|
686
754
|
stream?.cancel?.();
|
|
687
755
|
return;
|
|
688
756
|
}
|
|
757
|
+
message.statusReaction?.error();
|
|
689
758
|
this.#status.lastError = error?.message ?? String(error);
|
|
690
759
|
const presentStreamFailure = async (text) => {
|
|
691
|
-
|
|
760
|
+
const method = typeof stream?.fail === 'function'
|
|
761
|
+
? 'fail'
|
|
762
|
+
: (typeof stream?.finish === 'function' ? 'finish' : null);
|
|
763
|
+
if (!method) return false;
|
|
692
764
|
try {
|
|
693
|
-
const result = await stream
|
|
694
|
-
return
|
|
765
|
+
const result = await stream[method](text);
|
|
766
|
+
return method === 'fail'
|
|
767
|
+
? Boolean(result) && result.deliveryOutcome !== 'failed'
|
|
768
|
+
: result !== false && result?.deliveryOutcome !== 'failed';
|
|
695
769
|
} catch (streamError) {
|
|
696
770
|
this.#logger.warn?.(
|
|
697
771
|
`[dsh-im:${this.#descriptor.key}] unable to finalize the failed stream:`,
|
|
@@ -700,58 +774,25 @@ export class TextHarnessBridge {
|
|
|
700
774
|
return false;
|
|
701
775
|
}
|
|
702
776
|
};
|
|
703
|
-
if (failedBatch?.retained) {
|
|
704
|
-
this.#logger.error?.(
|
|
705
|
-
`[dsh-im:${this.#descriptor.key}] failed to submit a batch input:`,
|
|
706
|
-
error,
|
|
707
|
-
);
|
|
708
|
-
if (await presentStreamFailure(failedBatch.message)) return;
|
|
709
|
-
stream?.cancel?.();
|
|
710
|
-
try {
|
|
711
|
-
await this.#bot.sendText(target, failedBatch.message);
|
|
712
|
-
} catch (sendError) {
|
|
713
|
-
this.#logger.error?.(
|
|
714
|
-
`[dsh-im:${this.#descriptor.key}] failed to send the batch retry notice:`,
|
|
715
|
-
sendError,
|
|
716
|
-
);
|
|
717
|
-
}
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
777
|
const imageErrorMessage = imagePromptUserMessage(error);
|
|
721
|
-
if (imageErrorMessage) {
|
|
722
|
-
if (await presentStreamFailure(imageErrorMessage)) return;
|
|
723
|
-
stream?.cancel?.();
|
|
724
|
-
try {
|
|
725
|
-
await this.#bot.sendText(target, imageErrorMessage);
|
|
726
|
-
} catch (sendError) {
|
|
727
|
-
this.#logger.error?.(
|
|
728
|
-
`[dsh-im:${this.#descriptor.key}] failed to send the image error reply:`,
|
|
729
|
-
sendError,
|
|
730
|
-
);
|
|
731
|
-
}
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
734
778
|
const fileErrorMessage = inboundFileUserMessage(error);
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
this.#logger.error?.(`[dsh-im:${this.#descriptor.key}] failed to process a message:`, error);
|
|
749
|
-
if (await presentStreamFailure('消息处理失败,请稍后重试。')) {
|
|
779
|
+
const failure = setLastMessageFailure(this.#status, error, {
|
|
780
|
+
userMessage: fileErrorMessage ?? imageErrorMessage,
|
|
781
|
+
reason: imagePromptDiagnostic(error)?.reason,
|
|
782
|
+
});
|
|
783
|
+
const failureText = failedBatch?.retained
|
|
784
|
+
? `${messageFailureText(failure)}\n\n${failedBatch.message}`
|
|
785
|
+
: messageFailureText(failure);
|
|
786
|
+
this.#logger.error?.(
|
|
787
|
+
`[dsh-im:${this.#descriptor.key}] failed to process a message [${failure.referenceId}]:`,
|
|
788
|
+
error,
|
|
789
|
+
);
|
|
790
|
+
if (await presentStreamFailure(failureText)) {
|
|
750
791
|
return error.deliveryReceipt;
|
|
751
792
|
}
|
|
752
793
|
stream?.cancel?.();
|
|
753
794
|
try {
|
|
754
|
-
await this.#bot.sendText(target,
|
|
795
|
+
await this.#bot.sendText(target, failureText);
|
|
755
796
|
} catch (sendError) {
|
|
756
797
|
this.#logger.error?.(
|
|
757
798
|
`[dsh-im:${this.#descriptor.key}] failed to send the safe error reply:`,
|
|
@@ -768,7 +809,10 @@ export class TextHarnessBridge {
|
|
|
768
809
|
}
|
|
769
810
|
|
|
770
811
|
async #processInteractionReply(message, messageId, senderId, key, expected) {
|
|
771
|
-
if (this.#signal?.aborted)
|
|
812
|
+
if (this.#signal?.aborted) {
|
|
813
|
+
message.statusReaction?.clear();
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
772
816
|
const current = this.#pendingInteractions.get(key);
|
|
773
817
|
const claimed = expected.claimedReplyMessageId === messageId;
|
|
774
818
|
if (!current || current !== expected || current.submitting) {
|
|
@@ -822,6 +866,7 @@ export class TextHarnessBridge {
|
|
|
822
866
|
try {
|
|
823
867
|
await this.#presentInteraction(pending);
|
|
824
868
|
} catch (error) {
|
|
869
|
+
message.statusReaction?.error();
|
|
825
870
|
this.#status.lastError = t('{label}交互问题发送失败。', { label: this.#descriptor.label });
|
|
826
871
|
this.#logger.error?.(
|
|
827
872
|
`[dsh-im:${this.#descriptor.key}] failed to retry an interaction question:`,
|
|
@@ -860,6 +905,7 @@ export class TextHarnessBridge {
|
|
|
860
905
|
try {
|
|
861
906
|
await this.#presentInteraction(pending);
|
|
862
907
|
} catch (error) {
|
|
908
|
+
message.statusReaction?.error();
|
|
863
909
|
this.#status.lastError = t('{label}交互问题发送失败。', { label: this.#descriptor.label });
|
|
864
910
|
this.#logger.error?.(
|
|
865
911
|
`[dsh-im:${this.#descriptor.key}] failed to send the next interaction question:`,
|
|
@@ -884,7 +930,10 @@ export class TextHarnessBridge {
|
|
|
884
930
|
} catch (error) {
|
|
885
931
|
if (error?.code === 'interaction-not-pending') {
|
|
886
932
|
this.#clearPendingInteraction(key, pending.interactionId);
|
|
887
|
-
if (this.#signal?.aborted)
|
|
933
|
+
if (this.#signal?.aborted) {
|
|
934
|
+
message.statusReaction?.clear();
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
888
937
|
try {
|
|
889
938
|
await this.#bot.sendText(target, t(INTERACTION_RESOLVED_TEXT));
|
|
890
939
|
} catch (sendError) {
|
|
@@ -895,7 +944,15 @@ export class TextHarnessBridge {
|
|
|
895
944
|
}
|
|
896
945
|
return;
|
|
897
946
|
}
|
|
898
|
-
if (this.#signal?.aborted
|
|
947
|
+
if (this.#signal?.aborted) {
|
|
948
|
+
message.statusReaction?.clear();
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
if (this.#pendingInteractions.get(key) !== pending) {
|
|
952
|
+
message.statusReaction?.clear();
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
message.statusReaction?.error();
|
|
899
956
|
pending.submitting = false;
|
|
900
957
|
pending.answers.pop();
|
|
901
958
|
pending.index -= 1;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { connectionTestMessage } from './connection-test.mjs';
|
|
2
2
|
import { t } from './i18n.mjs';
|
|
3
|
+
import { publicMessageFailure } from './message-failure.mjs';
|
|
3
4
|
|
|
4
5
|
function cleanString(value) {
|
|
5
6
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
@@ -291,6 +292,7 @@ export class TokenBotController {
|
|
|
291
292
|
messagesReceived: runtimeStatus?.messagesReceived ?? 0,
|
|
292
293
|
messagesReplied: runtimeStatus?.messagesReplied ?? 0,
|
|
293
294
|
},
|
|
295
|
+
lastMessageError: publicMessageFailure(runtimeStatus?.lastMessageError),
|
|
294
296
|
error: structuredClone(this.#errors.get(config.botId) ?? null),
|
|
295
297
|
};
|
|
296
298
|
});
|
|
@@ -276,6 +276,34 @@ export class SlackApi {
|
|
|
276
276
|
});
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
addReaction({ channelId, messageTs, emojiName, signal, timeoutMs }) {
|
|
280
|
+
return this.#request('reactions.add', {
|
|
281
|
+
tokenKind: 'bot',
|
|
282
|
+
signal,
|
|
283
|
+
timeoutMs,
|
|
284
|
+
retry: false,
|
|
285
|
+
body: {
|
|
286
|
+
channel: slackId(channelId, 'channel id'),
|
|
287
|
+
timestamp: requiredString(messageTs, 'message timestamp'),
|
|
288
|
+
name: requiredString(emojiName, 'reaction name'),
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
removeReaction({ channelId, messageTs, emojiName, signal, timeoutMs }) {
|
|
294
|
+
return this.#request('reactions.remove', {
|
|
295
|
+
tokenKind: 'bot',
|
|
296
|
+
signal,
|
|
297
|
+
timeoutMs,
|
|
298
|
+
retry: false,
|
|
299
|
+
body: {
|
|
300
|
+
channel: slackId(channelId, 'channel id'),
|
|
301
|
+
timestamp: requiredString(messageTs, 'message timestamp'),
|
|
302
|
+
name: requiredString(emojiName, 'reaction name'),
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
279
307
|
startStream({ channelId, threadTs, recipientTeamId, recipientUserId, markdownText, signal }) {
|
|
280
308
|
return this.#request('chat.startStream', {
|
|
281
309
|
tokenKind: 'bot',
|
|
@@ -4,6 +4,11 @@ export const SLACK_DESCRIPTOR = Object.freeze({
|
|
|
4
4
|
key: 'slack',
|
|
5
5
|
label: 'Slack',
|
|
6
6
|
connectionLabel: ' Socket Mode 长连接',
|
|
7
|
+
reactions: Object.freeze({
|
|
8
|
+
processing: 'eyes',
|
|
9
|
+
success: 'white_check_mark',
|
|
10
|
+
error: 'x',
|
|
11
|
+
}),
|
|
7
12
|
});
|
|
8
13
|
|
|
9
14
|
export class SlackHarnessBridge extends TextHarnessBridge {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { connectionTestMessage } from '../shared/connection-test.mjs';
|
|
2
|
+
import { publicMessageFailure } from '../shared/message-failure.mjs';
|
|
2
3
|
import { t } from '../shared/i18n.mjs';
|
|
3
4
|
import { deriveSlackBotIdentity, maskSlackBotId } from './config-store.mjs';
|
|
4
5
|
import { inspectSlackCredentials } from './slack-api.mjs';
|
|
@@ -254,6 +255,7 @@ export class SlackController {
|
|
|
254
255
|
messagesReceived: runtimeStatus?.messagesReceived ?? 0,
|
|
255
256
|
messagesReplied: runtimeStatus?.messagesReplied ?? 0,
|
|
256
257
|
},
|
|
258
|
+
lastMessageError: publicMessageFailure(runtimeStatus?.lastMessageError),
|
|
257
259
|
error: structuredClone(this.#errors.get(config.botId) ?? null),
|
|
258
260
|
};
|
|
259
261
|
});
|
|
@@ -127,6 +127,10 @@ export function normalizeSlackEvent(payload, botUserId, {
|
|
|
127
127
|
? event.files.map((file) => slackFileSource(file, loadFileStream, loadFileInfo)).filter(Boolean)
|
|
128
128
|
: [],
|
|
129
129
|
addressed: direct || mentioned,
|
|
130
|
+
reactionTarget: {
|
|
131
|
+
channelId: String(event.channel),
|
|
132
|
+
messageTs: String(event.ts),
|
|
133
|
+
},
|
|
130
134
|
replyTarget: {
|
|
131
135
|
channelId: String(event.channel),
|
|
132
136
|
threadTs,
|
|
@@ -284,6 +288,26 @@ export class SlackBotClient {
|
|
|
284
288
|
return { providerMessageIds };
|
|
285
289
|
}
|
|
286
290
|
|
|
291
|
+
async addReaction(target, emoji, { signal } = {}) {
|
|
292
|
+
const reactionKey = String(emoji ?? '').trim();
|
|
293
|
+
await this.#api.addReaction({
|
|
294
|
+
channelId: target.channelId,
|
|
295
|
+
messageTs: target.messageTs,
|
|
296
|
+
emojiName: reactionKey,
|
|
297
|
+
signal: signal ?? this.#signal,
|
|
298
|
+
});
|
|
299
|
+
return reactionKey;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
removeReaction(target, reactionKey, { signal } = {}) {
|
|
303
|
+
return this.#api.removeReaction({
|
|
304
|
+
channelId: target.channelId,
|
|
305
|
+
messageTs: target.messageTs,
|
|
306
|
+
emojiName: reactionKey,
|
|
307
|
+
signal: signal ?? this.#signal,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
287
311
|
openStream(target) {
|
|
288
312
|
return createSlackMessageStream({
|
|
289
313
|
api: this.#api,
|
|
@@ -203,6 +203,15 @@ export class TelegramApi {
|
|
|
203
203
|
}, { signal });
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
async setMessageReaction({ chatId, messageId, emoji, signal, timeoutMs }) {
|
|
207
|
+
const normalizedEmoji = cleanString(emoji);
|
|
208
|
+
return this.#call('setMessageReaction', {
|
|
209
|
+
chat_id: chatId,
|
|
210
|
+
message_id: messageId,
|
|
211
|
+
reaction: normalizedEmoji ? [{ type: 'emoji', emoji: normalizedEmoji }] : [],
|
|
212
|
+
}, { signal, timeoutMs });
|
|
213
|
+
}
|
|
214
|
+
|
|
206
215
|
async sendRichMessage({
|
|
207
216
|
chatId,
|
|
208
217
|
richMessage,
|
|
@@ -4,6 +4,7 @@ export const TELEGRAM_DESCRIPTOR = Object.freeze({
|
|
|
4
4
|
key: 'telegram',
|
|
5
5
|
label: 'Telegram',
|
|
6
6
|
connectionLabel: ' Bot API 长轮询',
|
|
7
|
+
reactions: Object.freeze({ processing: '👀', success: '👍', error: '👎' }),
|
|
7
8
|
});
|
|
8
9
|
|
|
9
10
|
export class TelegramHarnessBridge extends TextHarnessBridge {
|
|
@@ -168,6 +168,7 @@ export function normalizeTelegramUpdate(update, {
|
|
|
168
168
|
images: image ? [image] : [],
|
|
169
169
|
files: file ? [file] : [],
|
|
170
170
|
addressed,
|
|
171
|
+
reactionTarget: { chatId, messageId },
|
|
171
172
|
replyTarget: {
|
|
172
173
|
chatId,
|
|
173
174
|
chatType: message.chat.type,
|
|
@@ -303,6 +304,25 @@ export class TelegramBotClient {
|
|
|
303
304
|
return { providerMessageIds };
|
|
304
305
|
}
|
|
305
306
|
|
|
307
|
+
async addReaction(target, emoji, { signal } = {}) {
|
|
308
|
+
const reactionKey = String(emoji ?? '').trim();
|
|
309
|
+
await this.#api.setMessageReaction({
|
|
310
|
+
chatId: target.chatId,
|
|
311
|
+
messageId: target.messageId,
|
|
312
|
+
emoji: reactionKey,
|
|
313
|
+
signal: signal ?? this.#signal,
|
|
314
|
+
});
|
|
315
|
+
return reactionKey;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
removeReaction(target, _reactionKey, { signal } = {}) {
|
|
319
|
+
return this.#api.setMessageReaction({
|
|
320
|
+
chatId: target.chatId,
|
|
321
|
+
messageId: target.messageId,
|
|
322
|
+
signal: signal ?? this.#signal,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
306
326
|
sendTyping(target) {
|
|
307
327
|
return this.#api.sendChatAction({
|
|
308
328
|
chatId: target.chatId,
|