@soimy/dingtalk 3.5.1 → 3.5.3

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.
Files changed (36) hide show
  1. package/README.md +13 -24
  2. package/openclaw.plugin.json +695 -0
  3. package/package.json +12 -7
  4. package/src/ack-reaction-service.ts +1 -1
  5. package/src/auth.ts +1 -1
  6. package/src/card/card-action-handler.ts +1 -1
  7. package/src/card/card-stop-handler.ts +1 -1
  8. package/src/card/card-streaming-mode.ts +30 -0
  9. package/src/card/reasoning-answer-split.ts +162 -0
  10. package/src/card/reasoning-block-assembler.ts +157 -0
  11. package/src/card-callback-service.ts +1 -1
  12. package/src/card-draft-controller.ts +117 -6
  13. package/src/card-service.ts +112 -1
  14. package/src/channel.ts +131 -96
  15. package/src/command/card-stop-command.ts +4 -22
  16. package/src/command/inbound-command-dispatch-service.ts +464 -0
  17. package/src/config-schema.ts +62 -38
  18. package/src/config.ts +25 -3
  19. package/src/docs-service.ts +5 -5
  20. package/src/http-client.ts +20 -0
  21. package/src/inbound-handler.ts +475 -501
  22. package/src/logger-context.ts +16 -2
  23. package/src/media-utils.ts +166 -10
  24. package/src/message-utils.ts +33 -5
  25. package/src/{attachment-text-extractor.ts → messaging/attachment-text-extractor.ts} +1 -1
  26. package/src/{quoted-file-service.ts → messaging/quoted-file-service.ts} +14 -9
  27. package/src/onboarding.ts +29 -0
  28. package/src/plugin-sdk-channel-actions-augment.ts +11 -0
  29. package/src/reply-strategy-card.ts +294 -28
  30. package/src/reply-strategy-markdown.ts +124 -19
  31. package/src/reply-strategy.ts +22 -2
  32. package/src/send-service.ts +178 -7
  33. package/src/targeting/agent-routing.ts +55 -32
  34. package/src/{group-members-store.ts → targeting/group-members-store.ts} +1 -1
  35. package/src/types.ts +60 -4
  36. package/src/utils.ts +190 -0
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
- import axios from "axios";
4
+ import axios from "./http-client";
5
5
  import { getAccessToken } from "./auth";
6
6
  import { updateCardVariables } from "./card-callback-service";
7
7
  import { DINGTALK_CARD_TEMPLATE, STOP_ACTION_VISIBLE, STOP_ACTION_HIDDEN } from "./card/card-template";
@@ -904,6 +904,117 @@ export async function finishAICard(
904
904
  }
905
905
  }
906
906
 
907
+ function getCardRecallTarget(card: AICardInstance): {
908
+ isGroup: boolean;
909
+ conversationId?: string;
910
+ } {
911
+ const { targetId, isExplicitUser } = stripTargetPrefix(card.conversationId);
912
+ const resolvedTarget = resolveOriginalPeerId(targetId);
913
+ const isGroup = !isExplicitUser && resolvedTarget.startsWith("cid");
914
+ return {
915
+ isGroup,
916
+ conversationId: resolvedTarget || undefined,
917
+ };
918
+ }
919
+
920
+ function parseRecallFailureEntries(payload: unknown): Array<[string, string]> {
921
+ if (!payload || typeof payload !== "object") {
922
+ return [];
923
+ }
924
+ return Object.entries(payload as Record<string, unknown>)
925
+ .map(([key, value]) => [String(key), String(value ?? "")] as [string, string]);
926
+ }
927
+
928
+ export async function recallAICardMessage(
929
+ card: AICardInstance,
930
+ log?: Logger,
931
+ ): Promise<boolean> {
932
+ const config = card.config;
933
+ const processQueryKey = card.processQueryKey?.trim();
934
+ const robotCode = config ? resolveRobotCode(config) : "";
935
+
936
+ if (!config || !processQueryKey || !robotCode) {
937
+ log?.warn?.(
938
+ `[DingTalk][AICard] Skip recall because required metadata is missing: ` +
939
+ `card=${card.cardInstanceId} hasConfig=${Boolean(config)} ` +
940
+ `processQueryKey=${processQueryKey || "(none)"} robotCode=${robotCode || "(none)"}`,
941
+ );
942
+ return false;
943
+ }
944
+
945
+ const target = getCardRecallTarget(card);
946
+ if (!target.conversationId) {
947
+ log?.warn?.(
948
+ `[DingTalk][AICard] Skip recall because conversationId is invalid: card=${card.cardInstanceId} conversationId=${card.conversationId}`,
949
+ );
950
+ return false;
951
+ }
952
+
953
+ const url = target.isGroup
954
+ ? `${DINGTALK_API}/v1.0/robot/groupMessages/recall`
955
+ : `${DINGTALK_API}/v1.0/robot/otoMessages/batchRecall`;
956
+ const body: Record<string, unknown> = {
957
+ robotCode,
958
+ processQueryKeys: [processQueryKey],
959
+ };
960
+ if (target.isGroup) {
961
+ body.openConversationId = target.conversationId;
962
+ }
963
+
964
+ try {
965
+ const token = await getAccessToken(config, log);
966
+ const response = await axios.post(url, body, {
967
+ headers: {
968
+ "x-acs-dingtalk-access-token": token,
969
+ "Content-Type": "application/json",
970
+ },
971
+ ...getProxyBypassOption(config),
972
+ });
973
+ const successResults = Array.isArray((response.data as Record<string, unknown> | undefined)?.successResult)
974
+ ? ((response.data as Record<string, unknown>).successResult as unknown[])
975
+ .map((item) => String(item))
976
+ : [];
977
+ const failedEntries = parseRecallFailureEntries(
978
+ (response.data as Record<string, unknown> | undefined)?.failedResult,
979
+ );
980
+ if (failedEntries.length > 0) {
981
+ log?.warn?.(
982
+ `[DingTalk][AICard] Recall reported failedResult: card=${card.cardInstanceId} ` +
983
+ `processQueryKey=${processQueryKey} failed=${JSON.stringify(failedEntries)}`,
984
+ );
985
+ return false;
986
+ }
987
+ if (!successResults.includes(processQueryKey)) {
988
+ log?.warn?.(
989
+ `[DingTalk][AICard] Recall response missing successResult for processQueryKey=${processQueryKey} ` +
990
+ `payload=${JSON.stringify(response.data)}`,
991
+ );
992
+ return false;
993
+ }
994
+
995
+ card.state = AICardStatus.FINISHED;
996
+ card.lastUpdated = Date.now();
997
+ removePendingCard(card, log);
998
+ log?.info?.(
999
+ `[DingTalk][AICard] Recalled empty card message: card=${card.cardInstanceId} ` +
1000
+ `conversationId=${target.conversationId} processQueryKey=${processQueryKey} mode=${target.isGroup ? "group" : "direct"}`,
1001
+ );
1002
+ return true;
1003
+ } catch (err: any) {
1004
+ log?.warn?.(`[DingTalk][AICard] Recall failed for card=${card.cardInstanceId}: ${err.message}`);
1005
+ if (err.response?.data !== undefined) {
1006
+ log?.warn?.(
1007
+ formatDingTalkErrorPayloadLog(
1008
+ target.isGroup ? "card.groupRecall" : "card.directRecall",
1009
+ err.response.data,
1010
+ "[DingTalk][AICard]",
1011
+ ),
1012
+ );
1013
+ }
1014
+ return false;
1015
+ }
1016
+ }
1017
+
907
1018
  export async function finishStoppedAICard(
908
1019
  card: AICardInstance,
909
1020
  content: string,