@soimy/dingtalk 3.5.0 → 3.5.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/README.md +7 -1
- package/package.json +14 -3
- package/src/ack-reaction-service.ts +1 -1
- package/src/auth.ts +1 -1
- package/src/card/card-action-handler.ts +62 -0
- package/src/card/card-run-registry.ts +118 -0
- package/src/card/card-stop-handler.ts +94 -0
- package/src/card/card-template.ts +20 -0
- package/src/card/reasoning-block-assembler.ts +157 -0
- package/src/card-callback-service.ts +90 -8
- package/src/card-draft-controller.ts +32 -0
- package/src/card-service.ts +189 -128
- package/src/channel.ts +82 -53
- package/src/command/card-stop-command.ts +78 -0
- package/src/command/inbound-command-dispatch-service.ts +464 -0
- package/src/docs-service.ts +5 -5
- package/src/http-client.ts +20 -0
- package/src/inbound-handler.ts +162 -465
- package/src/logger-context.ts +16 -2
- package/src/media-utils.ts +3 -3
- package/src/{attachment-text-extractor.ts → messaging/attachment-text-extractor.ts} +1 -1
- package/src/{quoted-file-service.ts → messaging/quoted-file-service.ts} +5 -5
- package/src/onboarding.ts +4 -32
- package/src/reply-strategy-card.ts +85 -17
- package/src/reply-strategy-markdown.ts +123 -18
- package/src/reply-strategy.ts +5 -0
- package/src/send-service.ts +80 -17
- package/src/targeting/agent-routing.ts +11 -4
- package/src/{group-members-store.ts → targeting/group-members-store.ts} +1 -1
- package/src/types.ts +6 -1
- package/src/utils.ts +165 -0
package/src/channel.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { readStringParam } from "openclaw/plugin-sdk/param-readers";
|
|
|
7
7
|
import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
|
|
8
8
|
import { getAccessToken } from "./auth";
|
|
9
9
|
import { analyzeCardCallback } from "./card-callback-service";
|
|
10
|
+
import { handleCardAction } from "./card/card-action-handler";
|
|
10
11
|
import {
|
|
11
12
|
createAICard,
|
|
12
13
|
streamAICard,
|
|
@@ -32,7 +33,7 @@ import {
|
|
|
32
33
|
recordExplicitFeedbackLearning,
|
|
33
34
|
} from "./feedback-learning-service";
|
|
34
35
|
import { handleDingTalkMessage } from "./inbound-handler";
|
|
35
|
-
import { getLogger } from "./logger-context";
|
|
36
|
+
import { getLogger, setCurrentLogger } from "./logger-context";
|
|
36
37
|
import { prepareMediaInput, resolveOutboundMediaType } from "./media-utils";
|
|
37
38
|
import { dingtalkSetupAdapter, dingtalkSetupWizard } from "./onboarding.js";
|
|
38
39
|
import { resolveOriginalPeerId, preloadPeerIdsFromSessions } from "./peer-id-registry";
|
|
@@ -61,11 +62,13 @@ import type {
|
|
|
61
62
|
} from "./types";
|
|
62
63
|
import { ConnectionState } from "./types";
|
|
63
64
|
import {
|
|
65
|
+
closePluginDebugLog,
|
|
64
66
|
cleanupOrphanedTempFiles,
|
|
65
67
|
createResolve4FallbackLookup,
|
|
66
68
|
formatDingTalkConnectionErrorLog,
|
|
67
69
|
formatDingTalkErrorPayloadLog,
|
|
68
70
|
getCurrentTimestamp,
|
|
71
|
+
resolvePluginDebugLog,
|
|
69
72
|
} from "./utils";
|
|
70
73
|
|
|
71
74
|
type InstrumentedDWClient = {
|
|
@@ -459,14 +462,15 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
459
462
|
const storePath = rt.channel.session.resolveStorePath(cfg.session?.store, {
|
|
460
463
|
agentId: accountId,
|
|
461
464
|
});
|
|
465
|
+
const effectiveLog = getLogger(accountId) || log;
|
|
462
466
|
try {
|
|
463
467
|
const result = await sendMessage(config, to, text, {
|
|
464
|
-
log,
|
|
468
|
+
log: effectiveLog,
|
|
465
469
|
accountId,
|
|
466
470
|
storePath,
|
|
467
471
|
conversationId: to,
|
|
468
472
|
});
|
|
469
|
-
|
|
473
|
+
effectiveLog?.debug?.(`[DingTalk] sendText: "${text}" result: ${JSON.stringify(result)}`);
|
|
470
474
|
if (!result.ok) {
|
|
471
475
|
throw new Error(result.error || "sendText failed");
|
|
472
476
|
}
|
|
@@ -486,7 +490,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
486
490
|
};
|
|
487
491
|
} catch (err: any) {
|
|
488
492
|
if (err?.response?.data !== undefined) {
|
|
489
|
-
|
|
493
|
+
effectiveLog?.error?.(formatDingTalkErrorPayloadLog("outbound.sendText", err.response.data));
|
|
490
494
|
}
|
|
491
495
|
throw new Error(
|
|
492
496
|
typeof err?.response?.data === "string"
|
|
@@ -513,6 +517,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
513
517
|
const storePath = rt.channel.session.resolveStorePath(cfg.session?.store, {
|
|
514
518
|
agentId: accountId,
|
|
515
519
|
});
|
|
520
|
+
const effectiveLog = getLogger(accountId) || log;
|
|
516
521
|
if (!config.clientId) {
|
|
517
522
|
throw new Error("DingTalk not configured");
|
|
518
523
|
}
|
|
@@ -520,7 +525,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
520
525
|
// Support mediaPath/filePath/mediaUrl aliases for better CLI compatibility.
|
|
521
526
|
const rawMediaPath = mediaPath || filePath || mediaUrl;
|
|
522
527
|
|
|
523
|
-
|
|
528
|
+
effectiveLog?.debug?.(
|
|
524
529
|
`[DingTalk] sendMedia called: to=${to}, mediaPath=${mediaPath}, filePath=${filePath}, mediaUrl=${mediaUrl}, rawMediaPath=${rawMediaPath}`,
|
|
525
530
|
);
|
|
526
531
|
|
|
@@ -538,10 +543,10 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
538
543
|
let preparedMedia;
|
|
539
544
|
try {
|
|
540
545
|
try {
|
|
541
|
-
preparedMedia = await prepareMediaInput(rawMediaPath,
|
|
546
|
+
preparedMedia = await prepareMediaInput(rawMediaPath, effectiveLog, config.mediaUrlAllowlist);
|
|
542
547
|
} catch (err: any) {
|
|
543
548
|
if (err?.response?.data !== undefined) {
|
|
544
|
-
|
|
549
|
+
effectiveLog?.error?.(
|
|
545
550
|
formatDingTalkErrorPayloadLog("outbound.sendMedia.prepare", err.response.data),
|
|
546
551
|
);
|
|
547
552
|
}
|
|
@@ -558,7 +563,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
558
563
|
? preparedMedia.path
|
|
559
564
|
: resolveRelativePath(preparedMedia.path);
|
|
560
565
|
|
|
561
|
-
|
|
566
|
+
effectiveLog?.debug?.(
|
|
562
567
|
`[DingTalk] sendMedia resolved path: rawMediaPath=${rawMediaPath}, actualMediaPath=${actualMediaPath}`,
|
|
563
568
|
);
|
|
564
569
|
|
|
@@ -570,7 +575,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
570
575
|
let result;
|
|
571
576
|
try {
|
|
572
577
|
result = await sendProactiveMedia(config, to, actualMediaPath, mediaType, {
|
|
573
|
-
log,
|
|
578
|
+
log: effectiveLog,
|
|
574
579
|
accountId,
|
|
575
580
|
storePath,
|
|
576
581
|
conversationId: to,
|
|
@@ -578,7 +583,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
578
583
|
});
|
|
579
584
|
} catch (err: any) {
|
|
580
585
|
if (err?.response?.data !== undefined) {
|
|
581
|
-
|
|
586
|
+
effectiveLog?.error?.(
|
|
582
587
|
formatDingTalkErrorPayloadLog("outbound.sendMedia.send", err.response.data),
|
|
583
588
|
);
|
|
584
589
|
}
|
|
@@ -586,7 +591,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
586
591
|
cause: err,
|
|
587
592
|
});
|
|
588
593
|
}
|
|
589
|
-
|
|
594
|
+
effectiveLog?.debug?.(
|
|
590
595
|
`[DingTalk] sendMedia: ${mediaType} file=${actualMediaPath} result: ${JSON.stringify(result)}`,
|
|
591
596
|
);
|
|
592
597
|
|
|
@@ -608,7 +613,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
608
613
|
);
|
|
609
614
|
} catch (err: any) {
|
|
610
615
|
if (err?.response?.data !== undefined) {
|
|
611
|
-
|
|
616
|
+
effectiveLog?.error?.(formatDingTalkErrorPayloadLog("outbound.sendMedia", err.response.data));
|
|
612
617
|
}
|
|
613
618
|
throw new Error(
|
|
614
619
|
typeof err?.response?.data === "string"
|
|
@@ -638,29 +643,37 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
638
643
|
accountStorePath = undefined;
|
|
639
644
|
}
|
|
640
645
|
|
|
641
|
-
|
|
646
|
+
const pluginLog = resolvePluginDebugLog({
|
|
647
|
+
accountId: account.accountId,
|
|
648
|
+
storePath: accountStorePath,
|
|
649
|
+
debug: config.debug,
|
|
650
|
+
baseLog: ctx.log,
|
|
651
|
+
});
|
|
652
|
+
setCurrentLogger(pluginLog, account.accountId);
|
|
653
|
+
|
|
654
|
+
pluginLog?.info?.(`[${account.accountId}] Initializing DingTalk Stream client...`);
|
|
642
655
|
|
|
643
656
|
// Preload known peer IDs from sessions so outbound delivery (e.g. cron
|
|
644
657
|
// jobs that fire immediately after startup) can resolve the original
|
|
645
658
|
// case-sensitive conversationId before any inbound message has arrived.
|
|
646
659
|
preloadPeerIdsFromSessions();
|
|
647
|
-
|
|
660
|
+
pluginLog?.debug?.(`[${account.accountId}] Peer ID registry preloaded from sessions`);
|
|
648
661
|
|
|
649
|
-
cleanupOrphanedTempFiles(
|
|
662
|
+
cleanupOrphanedTempFiles(pluginLog);
|
|
650
663
|
try {
|
|
651
664
|
const recovered = await recoverPendingCardsForAccount(
|
|
652
665
|
config,
|
|
653
666
|
account.accountId,
|
|
654
667
|
accountStorePath,
|
|
655
|
-
|
|
668
|
+
pluginLog,
|
|
656
669
|
);
|
|
657
670
|
if (recovered > 0) {
|
|
658
|
-
|
|
671
|
+
pluginLog?.info?.(
|
|
659
672
|
`[${account.accountId}] Recovered and finalized ${recovered} unfinished card(s) from previous runtime`,
|
|
660
673
|
);
|
|
661
674
|
}
|
|
662
675
|
} catch (err: any) {
|
|
663
|
-
|
|
676
|
+
pluginLog?.warn?.(
|
|
664
677
|
`[${account.accountId}] Failed to recover unfinished cards: ${err.message}`,
|
|
665
678
|
);
|
|
666
679
|
}
|
|
@@ -682,7 +695,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
682
695
|
});
|
|
683
696
|
(c as any).sslopts = {
|
|
684
697
|
...(c as any).sslopts,
|
|
685
|
-
lookup: createResolve4FallbackLookup(
|
|
698
|
+
lookup: createResolve4FallbackLookup(pluginLog, account.accountId),
|
|
686
699
|
};
|
|
687
700
|
|
|
688
701
|
instrumentConnectionStages(c);
|
|
@@ -701,7 +714,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
701
714
|
c.socketCallBackResponse(messageId, { success: true });
|
|
702
715
|
stats.acked += 1;
|
|
703
716
|
} catch (ackError: any) {
|
|
704
|
-
|
|
717
|
+
pluginLog?.warn?.(
|
|
705
718
|
`[${account.accountId}] Failed to acknowledge callback ${messageId}: ${ackError.message}`,
|
|
706
719
|
);
|
|
707
720
|
}
|
|
@@ -722,38 +735,38 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
722
735
|
accountId: account.accountId,
|
|
723
736
|
data,
|
|
724
737
|
sessionWebhook: data.sessionWebhook,
|
|
725
|
-
log:
|
|
738
|
+
log: pluginLog,
|
|
726
739
|
dingtalkConfig: config,
|
|
727
740
|
});
|
|
728
741
|
stats.processed += 1;
|
|
729
742
|
if (stats.received % INBOUND_COUNTER_LOG_EVERY === 0) {
|
|
730
|
-
logInboundCounters(
|
|
743
|
+
logInboundCounters(pluginLog, account.accountId, "periodic");
|
|
731
744
|
}
|
|
732
745
|
return;
|
|
733
746
|
}
|
|
734
747
|
|
|
735
748
|
if (isMessageProcessed(dedupKey)) {
|
|
736
|
-
|
|
749
|
+
pluginLog?.debug?.(`[${account.accountId}] Skipping duplicate message: ${dedupKey}`);
|
|
737
750
|
stats.dedupSkipped += 1;
|
|
738
751
|
acknowledge();
|
|
739
|
-
logInboundCounters(
|
|
752
|
+
logInboundCounters(pluginLog, account.accountId, "dedup-skipped");
|
|
740
753
|
return;
|
|
741
754
|
}
|
|
742
755
|
|
|
743
756
|
const inflightSince = processingDedupKeys.get(dedupKey);
|
|
744
757
|
if (inflightSince !== undefined) {
|
|
745
758
|
if (Date.now() - inflightSince > INFLIGHT_TTL_MS) {
|
|
746
|
-
|
|
759
|
+
pluginLog?.warn?.(
|
|
747
760
|
`[${account.accountId}] Releasing stale in-flight lock for ${dedupKey} (held ${Date.now() - inflightSince}ms > TTL ${INFLIGHT_TTL_MS}ms)`,
|
|
748
761
|
);
|
|
749
762
|
processingDedupKeys.delete(dedupKey);
|
|
750
763
|
} else {
|
|
751
|
-
|
|
764
|
+
pluginLog?.debug?.(
|
|
752
765
|
`[${account.accountId}] Skipping in-flight duplicate message: ${dedupKey}`,
|
|
753
766
|
);
|
|
754
767
|
stats.inflightSkipped += 1;
|
|
755
768
|
acknowledge();
|
|
756
|
-
logInboundCounters(
|
|
769
|
+
logInboundCounters(pluginLog, account.accountId, "inflight-skipped");
|
|
757
770
|
return;
|
|
758
771
|
}
|
|
759
772
|
}
|
|
@@ -766,21 +779,21 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
766
779
|
accountId: account.accountId,
|
|
767
780
|
data,
|
|
768
781
|
sessionWebhook: data.sessionWebhook,
|
|
769
|
-
log:
|
|
782
|
+
log: pluginLog,
|
|
770
783
|
dingtalkConfig: config,
|
|
771
784
|
});
|
|
772
785
|
stats.processed += 1;
|
|
773
786
|
markMessageProcessed(dedupKey);
|
|
774
787
|
if (stats.received % INBOUND_COUNTER_LOG_EVERY === 0) {
|
|
775
|
-
logInboundCounters(
|
|
788
|
+
logInboundCounters(pluginLog, account.accountId, "periodic");
|
|
776
789
|
}
|
|
777
790
|
} finally {
|
|
778
791
|
processingDedupKeys.delete(dedupKey);
|
|
779
792
|
}
|
|
780
793
|
} catch (error: any) {
|
|
781
794
|
stats.failed += 1;
|
|
782
|
-
logInboundCounters(
|
|
783
|
-
|
|
795
|
+
logInboundCounters(pluginLog, account.accountId, "failed");
|
|
796
|
+
pluginLog?.error?.(`[${account.accountId}] Error processing message: ${error.message}`);
|
|
784
797
|
}
|
|
785
798
|
});
|
|
786
799
|
|
|
@@ -793,7 +806,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
793
806
|
try {
|
|
794
807
|
c.socketCallBackResponse(messageId, { success: true });
|
|
795
808
|
} catch (ackError: any) {
|
|
796
|
-
|
|
809
|
+
pluginLog?.warn?.(
|
|
797
810
|
`[${account.accountId}] Failed to acknowledge card callback ${messageId}: ${ackError.message}`,
|
|
798
811
|
);
|
|
799
812
|
}
|
|
@@ -802,7 +815,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
802
815
|
try {
|
|
803
816
|
const payload = JSON.parse(res.data);
|
|
804
817
|
const analysis = analyzeCardCallback(payload);
|
|
805
|
-
|
|
818
|
+
pluginLog?.info?.(
|
|
806
819
|
`[${account.accountId}] [DingTalk][CardCallback] action=${analysis.summary} raw=${JSON.stringify(payload)}`,
|
|
807
820
|
);
|
|
808
821
|
|
|
@@ -825,20 +838,32 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
825
838
|
analysis.feedbackAckText,
|
|
826
839
|
{
|
|
827
840
|
accountId: account.accountId,
|
|
828
|
-
log:
|
|
841
|
+
log: pluginLog,
|
|
829
842
|
},
|
|
830
843
|
);
|
|
831
|
-
|
|
844
|
+
pluginLog?.info?.(
|
|
832
845
|
`[${account.accountId}] [DingTalk][CardCallback] feedback ack sent to ${analysis.feedbackTarget}`,
|
|
833
846
|
);
|
|
834
847
|
} catch (sendErr: any) {
|
|
835
|
-
|
|
848
|
+
pluginLog?.warn?.(
|
|
836
849
|
`[${account.accountId}] [DingTalk][CardCallback] Failed to send feedback ack: ${sendErr?.message || String(sendErr)}`,
|
|
837
850
|
);
|
|
838
851
|
}
|
|
839
852
|
}
|
|
853
|
+
const actionResult = await handleCardAction({
|
|
854
|
+
analysis,
|
|
855
|
+
cfg,
|
|
856
|
+
accountId: account.accountId,
|
|
857
|
+
config,
|
|
858
|
+
log: pluginLog,
|
|
859
|
+
});
|
|
860
|
+
if (!actionResult.handled && analysis.actionId && analysis.actionId !== "feedback_up" && analysis.actionId !== "feedback_down") {
|
|
861
|
+
pluginLog?.debug?.(
|
|
862
|
+
`[${account.accountId}] [DingTalk][CardCallback] Unhandled actionId=${analysis.actionId}`,
|
|
863
|
+
);
|
|
864
|
+
}
|
|
840
865
|
} catch (error: any) {
|
|
841
|
-
|
|
866
|
+
pluginLog?.error?.(
|
|
842
867
|
`[${account.accountId}] [DingTalk][CardCallback] Failed to parse callback: ${error.message}`,
|
|
843
868
|
);
|
|
844
869
|
} finally {
|
|
@@ -864,15 +889,15 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
864
889
|
return;
|
|
865
890
|
}
|
|
866
891
|
stopped = true;
|
|
867
|
-
|
|
892
|
+
pluginLog?.info?.(`[${account.accountId}] Stopping DingTalk Stream client...`);
|
|
868
893
|
void finalizeActiveCardsForAccount(
|
|
869
894
|
config,
|
|
870
895
|
account.accountId,
|
|
871
896
|
"⚠️ 服务正在重启,当前回复已中断。请重新发送你的问题。",
|
|
872
897
|
accountStorePath,
|
|
873
|
-
|
|
898
|
+
pluginLog,
|
|
874
899
|
).catch((err: any) => {
|
|
875
|
-
|
|
900
|
+
pluginLog?.debug?.(
|
|
876
901
|
`[${account.accountId}] Failed to finalize active cards during stop: ${err.message}`,
|
|
877
902
|
);
|
|
878
903
|
});
|
|
@@ -882,7 +907,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
882
907
|
try {
|
|
883
908
|
client.disconnect();
|
|
884
909
|
} catch (err: any) {
|
|
885
|
-
|
|
910
|
+
pluginLog?.warn?.(`[${account.accountId}] Error during disconnect: ${err.message}`);
|
|
886
911
|
}
|
|
887
912
|
nativeStopResolve?.();
|
|
888
913
|
}
|
|
@@ -893,12 +918,16 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
893
918
|
lastStopAt: getCurrentTimestamp(),
|
|
894
919
|
});
|
|
895
920
|
|
|
896
|
-
|
|
921
|
+
pluginLog?.info?.(`[${account.accountId}] DingTalk Stream client stopped`);
|
|
922
|
+
closePluginDebugLog({
|
|
923
|
+
accountId: account.accountId,
|
|
924
|
+
storePath: accountStorePath,
|
|
925
|
+
});
|
|
897
926
|
};
|
|
898
927
|
|
|
899
928
|
if (abortSignal) {
|
|
900
929
|
if (abortSignal.aborted) {
|
|
901
|
-
|
|
930
|
+
pluginLog?.warn?.(
|
|
902
931
|
`[${account.accountId}] Abort signal already active, skipping connection`,
|
|
903
932
|
);
|
|
904
933
|
|
|
@@ -916,7 +945,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
916
945
|
if (stopped) {
|
|
917
946
|
return;
|
|
918
947
|
}
|
|
919
|
-
|
|
948
|
+
pluginLog?.info?.(
|
|
920
949
|
`[${account.accountId}] Abort signal received, stopping DingTalk Stream client...`,
|
|
921
950
|
);
|
|
922
951
|
stopClient();
|
|
@@ -933,11 +962,11 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
933
962
|
lastStartAt: getCurrentTimestamp(),
|
|
934
963
|
lastError: null,
|
|
935
964
|
});
|
|
936
|
-
|
|
965
|
+
pluginLog?.info?.(`[${account.accountId}] DingTalk Stream client connected successfully`);
|
|
937
966
|
await nativeStopPromise;
|
|
938
967
|
}
|
|
939
968
|
} catch (err: any) {
|
|
940
|
-
|
|
969
|
+
pluginLog?.error?.(
|
|
941
970
|
formatDingTalkConnectionErrorLog(
|
|
942
971
|
// Use connect.open as base scope; instrumentation can override to connect.websocket
|
|
943
972
|
"connect.open",
|
|
@@ -971,7 +1000,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
971
1000
|
if (stopped) {
|
|
972
1001
|
return;
|
|
973
1002
|
}
|
|
974
|
-
|
|
1003
|
+
pluginLog?.debug?.(
|
|
975
1004
|
`[${account.accountId}] Connection state changed to: ${state}${error ? ` (${error})` : ""}`,
|
|
976
1005
|
);
|
|
977
1006
|
if (state === ConnectionState.CONNECTED) {
|
|
@@ -994,7 +1023,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
994
1023
|
}
|
|
995
1024
|
}
|
|
996
1025
|
if (cleared > 0) {
|
|
997
|
-
|
|
1026
|
+
pluginLog?.info?.(
|
|
998
1027
|
`[${account.accountId}] Cleared ${cleared} stale in-flight lock(s) on disconnect`,
|
|
999
1028
|
);
|
|
1000
1029
|
}
|
|
@@ -1007,7 +1036,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1007
1036
|
},
|
|
1008
1037
|
};
|
|
1009
1038
|
|
|
1010
|
-
|
|
1039
|
+
pluginLog?.debug?.(
|
|
1011
1040
|
`[${account.accountId}] Connection config: maxAttempts=${connectionConfig.maxAttempts}, ` +
|
|
1012
1041
|
`initialDelay=${connectionConfig.initialDelay}ms, maxDelay=${connectionConfig.maxDelay}ms, ` +
|
|
1013
1042
|
`jitter=${connectionConfig.jitter}`,
|
|
@@ -1017,7 +1046,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1017
1046
|
client,
|
|
1018
1047
|
account.accountId,
|
|
1019
1048
|
connectionConfig,
|
|
1020
|
-
|
|
1049
|
+
pluginLog,
|
|
1021
1050
|
createStreamClient,
|
|
1022
1051
|
);
|
|
1023
1052
|
|
|
@@ -1031,17 +1060,17 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1031
1060
|
lastStartAt: getCurrentTimestamp(),
|
|
1032
1061
|
lastError: null,
|
|
1033
1062
|
});
|
|
1034
|
-
|
|
1063
|
+
pluginLog?.info?.(`[${account.accountId}] DingTalk Stream client connected successfully`);
|
|
1035
1064
|
|
|
1036
1065
|
await connectionManager.waitForStop();
|
|
1037
1066
|
} else {
|
|
1038
|
-
|
|
1067
|
+
pluginLog?.info?.(
|
|
1039
1068
|
`[${account.accountId}] DingTalk Stream client connect() completed but channel is ` +
|
|
1040
1069
|
`not running (stopped=${stopped}, connected=${connectionManager.isConnected()})`,
|
|
1041
1070
|
);
|
|
1042
1071
|
}
|
|
1043
1072
|
} catch (err: any) {
|
|
1044
|
-
|
|
1073
|
+
pluginLog?.error?.(
|
|
1045
1074
|
formatDingTalkConnectionErrorLog(
|
|
1046
1075
|
// Use connect.open as base scope; instrumentation can override to connect.websocket
|
|
1047
1076
|
"connect.open",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { resolveNativeCommandSessionTargets } from "openclaw/plugin-sdk/command-auth";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
3
|
+
import { getDingTalkRuntime } from "../runtime";
|
|
4
|
+
import type { Logger } from "../types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dispatch a native targeted `/stop` command through the OpenClaw SDK,
|
|
8
|
+
* replacing the previous self-built Gateway WebSocket `chat.abort` approach.
|
|
9
|
+
*
|
|
10
|
+
* Uses the same `resolveNativeCommandSessionTargets` + `CommandSource: "native"`
|
|
11
|
+
* model as Telegram / Discord / Slack slash commands, producing:
|
|
12
|
+
* - A dedicated command SessionKey (`agent:<agentId>:dingtalk:card-stop:<userId>`)
|
|
13
|
+
* - A CommandTargetSessionKey pointing at the real conversation session
|
|
14
|
+
*
|
|
15
|
+
* Inside the SDK, `dispatch-from-config` → `tryFastAbortFromMessage` picks up
|
|
16
|
+
* the `/stop` body, resolves the target session via `CommandTargetSessionKey`,
|
|
17
|
+
* and executes `abortEmbeddedPiRun` + `clearSessionQueues`.
|
|
18
|
+
*
|
|
19
|
+
* Accesses SDK functions via `getDingTalkRuntime().channel.reply` — the same
|
|
20
|
+
* pattern used by `inbound-handler.ts` — while reusing the upstream native
|
|
21
|
+
* command session-target helper exported by the plugin SDK.
|
|
22
|
+
*/
|
|
23
|
+
export async function dispatchDingTalkCardStopCommand(params: {
|
|
24
|
+
cfg: OpenClawConfig;
|
|
25
|
+
accountId: string;
|
|
26
|
+
agentId: string;
|
|
27
|
+
targetSessionKey: string;
|
|
28
|
+
clickerUserId: string;
|
|
29
|
+
log?: Logger;
|
|
30
|
+
}): Promise<{ ok: boolean }> {
|
|
31
|
+
const rt = getDingTalkRuntime();
|
|
32
|
+
|
|
33
|
+
const { sessionKey: commandSessionKey, commandTargetSessionKey } =
|
|
34
|
+
resolveNativeCommandSessionTargets({
|
|
35
|
+
agentId: params.agentId,
|
|
36
|
+
sessionPrefix: "dingtalk:card-stop",
|
|
37
|
+
userId: params.clickerUserId,
|
|
38
|
+
targetSessionKey: params.targetSessionKey,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const ctx = rt.channel.reply.finalizeInboundContext({
|
|
42
|
+
Body: "/stop",
|
|
43
|
+
RawBody: "/stop",
|
|
44
|
+
CommandBody: "/stop",
|
|
45
|
+
SessionKey: commandSessionKey,
|
|
46
|
+
CommandTargetSessionKey: commandTargetSessionKey,
|
|
47
|
+
CommandSource: "native" as const,
|
|
48
|
+
CommandAuthorized: true,
|
|
49
|
+
AccountId: params.accountId,
|
|
50
|
+
Provider: "dingtalk",
|
|
51
|
+
Surface: "dingtalk",
|
|
52
|
+
// "direct" because the synthetic /stop body contains no @mentions to strip.
|
|
53
|
+
// The actual chat type of the target session is irrelevant for abort routing.
|
|
54
|
+
ChatType: "direct",
|
|
55
|
+
From: `dingtalk:card-stop:${params.clickerUserId}`,
|
|
56
|
+
To: `card-stop:${params.clickerUserId}`,
|
|
57
|
+
SenderId: params.clickerUserId,
|
|
58
|
+
OriginatingChannel: "dingtalk",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// DispatchInboundResult = { queuedFinal, counts } — the return value does
|
|
62
|
+
// not expose whether tryFastAbortFromMessage took the fast-abort path.
|
|
63
|
+
// Treat successful dispatch as best-effort abort, consistent with the
|
|
64
|
+
// previous gateway chat.abort approach.
|
|
65
|
+
await rt.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
66
|
+
ctx,
|
|
67
|
+
cfg: params.cfg,
|
|
68
|
+
dispatcherOptions: {
|
|
69
|
+
responsePrefix: "",
|
|
70
|
+
deliver: async () => {
|
|
71
|
+
// SDK abort confirmation text is swallowed here; the card
|
|
72
|
+
// finalize path handles stopped content independently.
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return { ok: true };
|
|
78
|
+
}
|