@soimy/dingtalk 3.6.11 → 3.7.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/dist/index.d.ts +1 -1
- package/dist/index.js +11684 -10583
- package/dist/src/{ack-reaction-service.d.ts → ack-reaction/ack-reaction-service.d.ts} +1 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts +3 -3
- package/dist/src/card/ask-user-question-context.d.ts +1 -1
- package/dist/src/card/ask-user-question-store.d.ts +1 -1
- package/dist/src/card/ask-user-question.d.ts +1 -1
- package/dist/src/card/card-action-handler.d.ts +2 -2
- package/dist/src/{card-draft-controller.d.ts → card/card-draft-controller.d.ts} +5 -1
- package/dist/src/card/card-run-registry.d.ts +2 -2
- package/dist/src/{card-service.d.ts → card/card-service.d.ts} +1 -1
- package/dist/src/card/card-stop-handler.d.ts +1 -1
- package/dist/src/card/card-streaming-mode.d.ts +1 -1
- package/dist/src/card/card-task-progress.d.ts +60 -0
- package/dist/src/channel.d.ts +6 -6
- package/dist/src/command/card-stop-command.d.ts +1 -1
- package/dist/src/{feedback-learning-service.d.ts → command/feedback-learning-service.d.ts} +1 -1
- package/dist/src/command/inbound-command-dispatch-service.d.ts +2 -2
- package/dist/src/{learning-command-service.d.ts → command/learning-command-service.d.ts} +1 -1
- package/dist/src/gateway/channel-gateway.d.ts +1 -1
- package/dist/src/{connection-manager.d.ts → gateway/connection-manager.d.ts} +1 -1
- package/dist/src/{docs-service.d.ts → gateway/docs-service.d.ts} +1 -1
- package/dist/src/{inbound-handler.d.ts → gateway/inbound-handler.d.ts} +1 -1
- package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts +1 -1
- package/dist/src/messaging/attachment-text-extractor.d.ts +1 -1
- package/dist/src/messaging/btw-deliver.d.ts +1 -1
- package/dist/src/messaging/channel-outbound.d.ts +1 -1
- package/dist/src/{media-utils.d.ts → messaging/media-utils.d.ts} +1 -1
- package/dist/src/{message-context-store.d.ts → messaging/message-context-store.d.ts} +7 -1
- package/dist/src/{message-utils.d.ts → messaging/message-utils.d.ts} +1 -1
- package/dist/src/messaging/quoted-context.d.ts +2 -2
- package/dist/src/messaging/quoted-file-service.d.ts +1 -1
- package/dist/src/messaging/quoted-ref.d.ts +2 -2
- package/dist/src/{reply-strategy-card.d.ts → messaging/reply-strategy-card.d.ts} +1 -1
- package/dist/src/{reply-strategy-types.d.ts → messaging/reply-strategy-types.d.ts} +10 -1
- package/dist/src/{reply-strategy-with-reaction.d.ts → messaging/reply-strategy-with-reaction.d.ts} +1 -1
- package/dist/src/{reply-strategy.d.ts → messaging/reply-strategy.d.ts} +1 -1
- package/dist/src/{send-service.d.ts → messaging/send-service.d.ts} +1 -1
- package/dist/src/platform/channel-status.d.ts +1 -1
- package/dist/src/platform/config.d.ts +110 -0
- package/dist/src/platform/runtime-events.d.ts +67 -0
- package/dist/src/{types.d.ts → platform/types.d.ts} +74 -1
- package/dist/src/{persistence-store.d.ts → shared/persistence-store.d.ts} +1 -1
- package/dist/src/{utils.d.ts → shared/utils.d.ts} +1 -1
- package/dist/src/targeting/agent-name-matcher.d.ts +1 -1
- package/dist/src/targeting/agent-routing.d.ts +3 -3
- package/dist/src/{session-routing.d.ts → targeting/session-routing.d.ts} +1 -1
- package/index.ts +193 -69
- package/openclaw.plugin.json +158 -2
- package/package.json +16 -16
- package/src/{ack-reaction-classifier.ts → ack-reaction/ack-reaction-classifier.ts} +85 -13
- package/src/{ack-reaction-service.ts → ack-reaction/ack-reaction-service.ts} +13 -7
- package/src/ack-reaction/dynamic-ack-reaction-controller.ts +46 -31
- package/src/ack-reaction/dynamic-ack-reaction-progress.ts +4 -1
- package/src/card/ask-user-question-context.ts +1 -1
- package/src/card/ask-user-question-store.ts +2 -2
- package/src/card/ask-user-question.ts +11 -7
- package/src/card/card-action-handler.ts +2 -2
- package/src/{card-callback-service.ts → card/card-callback-service.ts} +7 -9
- package/src/card/card-draft-controller.ts +770 -0
- package/src/card/card-markdown-image-reroute.ts +8 -2
- package/src/card/card-run-registry.ts +17 -7
- package/src/{card-service.ts → card/card-service.ts} +91 -61
- package/src/card/card-stop-handler.ts +12 -8
- package/src/card/card-streaming-mode.ts +1 -1
- package/src/card/card-task-progress.ts +330 -0
- package/src/card/draft-stream-loop.ts +119 -0
- package/src/card/reasoning-answer-split.ts +125 -121
- package/src/card/reasoning-block-assembler.ts +122 -123
- package/src/{run-usage-store.ts → card/run-usage-store.ts} +18 -6
- package/src/card/statusline-renderer.ts +14 -5
- package/src/card/task-model-metadata.ts +8 -3
- package/src/channel.ts +14 -16
- package/src/command/card-stop-command.ts +2 -2
- package/src/{feedback-learning-service.ts → command/feedback-learning-service.ts} +36 -16
- package/src/{feedback-learning-store.ts → command/feedback-learning-store.ts} +133 -81
- package/src/command/inbound-command-dispatch-service.ts +12 -6
- package/src/{learning-command-service.ts → command/learning-command-service.ts} +36 -29
- package/src/{session-command-service.ts → command/session-command-service.ts} +3 -1
- package/src/gateway/channel-gateway.ts +106 -28
- package/src/{connection-manager.ts → gateway/connection-manager.ts} +45 -30
- package/src/{docs-service.ts → gateway/docs-service.ts} +5 -5
- package/src/{inbound-handler.ts → gateway/inbound-handler.ts} +139 -84
- package/src/gateway/inbound-session-queue-dispatcher.ts +18 -14
- package/src/{session-lock.ts → gateway/session-lock.ts} +14 -14
- package/src/messaging/attachment-text-extractor.ts +21 -7
- package/src/messaging/btw-deliver.ts +2 -2
- package/src/messaging/channel-actions.ts +15 -7
- package/src/messaging/channel-outbound.ts +16 -11
- package/src/{media-utils.ts → messaging/media-utils.ts} +276 -72
- package/src/{message-context-store.ts → messaging/message-context-store.ts} +11 -2
- package/src/{message-utils.ts → messaging/message-utils.ts} +46 -11
- package/src/messaging/quoted-context.ts +2 -2
- package/src/messaging/quoted-file-service.ts +311 -294
- package/src/messaging/quoted-ref.ts +11 -7
- package/src/{reply-strategy-card.ts → messaging/reply-strategy-card.ts} +178 -101
- package/src/{reply-strategy-markdown.ts → messaging/reply-strategy-markdown.ts} +8 -2
- package/src/{reply-strategy-types.ts → messaging/reply-strategy-types.ts} +11 -1
- package/src/{reply-strategy-with-reaction.ts → messaging/reply-strategy-with-reaction.ts} +6 -1
- package/src/{reply-strategy.ts → messaging/reply-strategy.ts} +2 -2
- package/src/{send-service.ts → messaging/send-service.ts} +100 -65
- package/src/{auth.ts → platform/auth.ts} +2 -2
- package/src/platform/channel-status.ts +3 -3
- package/src/{config-schema.ts → platform/config-schema.ts} +81 -4
- package/src/{config.ts → platform/config.ts} +167 -19
- package/src/{device-registration.ts → platform/device-registration.ts} +5 -4
- package/src/{onboarding.ts → platform/onboarding.ts} +1 -4
- package/src/{plugin-sdk-channel-actions-augment.ts → platform/plugin-sdk-channel-actions-augment.ts} +6 -5
- package/src/platform/runtime-events.ts +202 -0
- package/src/{secret-input.ts → platform/secret-input.ts} +117 -11
- package/src/{types.ts → platform/types.ts} +76 -1
- package/src/{http-client.ts → shared/http-client.ts} +1 -3
- package/src/{persistence-store.ts → shared/persistence-store.ts} +6 -6
- package/src/{utils.ts → shared/utils.ts} +30 -13
- package/src/targeting/agent-name-matcher.ts +1 -1
- package/src/targeting/agent-routing.ts +7 -7
- package/src/targeting/group-members-store.ts +1 -1
- package/src/{session-peer-store.ts → targeting/session-peer-store.ts} +9 -4
- package/src/{session-routing.ts → targeting/session-routing.ts} +1 -1
- package/src/targeting/target-directory-adapter.ts +3 -3
- package/src/targeting/target-directory-store.ts +1 -1
- package/src/targeting/target-input.ts +1 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts +0 -35
- package/dist/src/config.d.ts +0 -59
- package/src/ack-reaction/dynamic-ack-reaction-events.ts +0 -123
- package/src/card-draft-controller.ts +0 -637
- package/src/draft-stream-loop.ts +0 -119
- /package/dist/src/{ack-reaction-classifier.d.ts → ack-reaction/ack-reaction-classifier.d.ts} +0 -0
- /package/dist/src/{card-callback-service.d.ts → card/card-callback-service.d.ts} +0 -0
- /package/dist/src/{draft-stream-loop.d.ts → card/draft-stream-loop.d.ts} +0 -0
- /package/dist/src/{run-usage-store.d.ts → card/run-usage-store.d.ts} +0 -0
- /package/dist/src/{feedback-learning-store.d.ts → command/feedback-learning-store.d.ts} +0 -0
- /package/dist/src/{session-command-service.d.ts → command/session-command-service.d.ts} +0 -0
- /package/dist/src/{session-lock.d.ts → gateway/session-lock.d.ts} +0 -0
- /package/dist/src/{proactive-risk-registry.d.ts → messaging/proactive-risk-registry.d.ts} +0 -0
- /package/dist/src/{reply-strategy-markdown.d.ts → messaging/reply-strategy-markdown.d.ts} +0 -0
- /package/dist/src/{access-control.d.ts → platform/access-control.d.ts} +0 -0
- /package/dist/src/{auth.d.ts → platform/auth.d.ts} +0 -0
- /package/dist/src/{config-schema.d.ts → platform/config-schema.d.ts} +0 -0
- /package/dist/src/{device-registration.d.ts → platform/device-registration.d.ts} +0 -0
- /package/dist/src/{logger-context.d.ts → platform/logger-context.d.ts} +0 -0
- /package/dist/src/{onboarding.d.ts → platform/onboarding.d.ts} +0 -0
- /package/dist/src/{plugin-sdk-channel-actions-augment.d.ts → platform/plugin-sdk-channel-actions-augment.d.ts} +0 -0
- /package/dist/src/{runtime.d.ts → platform/runtime.d.ts} +0 -0
- /package/dist/src/{secret-input.d.ts → platform/secret-input.d.ts} +0 -0
- /package/dist/src/{session-state.d.ts → platform/session-state.d.ts} +0 -0
- /package/dist/src/{signature.d.ts → platform/signature.d.ts} +0 -0
- /package/dist/src/{dedup.d.ts → shared/dedup.d.ts} +0 -0
- /package/dist/src/{http-client.d.ts → shared/http-client.d.ts} +0 -0
- /package/dist/src/{path-utils.d.ts → shared/path-utils.d.ts} +0 -0
- /package/dist/src/{peer-id-registry.d.ts → targeting/peer-id-registry.d.ts} +0 -0
- /package/dist/src/{session-peer-store.d.ts → targeting/session-peer-store.d.ts} +0 -0
- /package/src/{proactive-risk-registry.ts → messaging/proactive-risk-registry.ts} +0 -0
- /package/src/{access-control.ts → platform/access-control.ts} +0 -0
- /package/src/{logger-context.ts → platform/logger-context.ts} +0 -0
- /package/src/{runtime.ts → platform/runtime.ts} +0 -0
- /package/src/{session-state.ts → platform/session-state.ts} +0 -0
- /package/src/{signature.ts → platform/signature.ts} +0 -0
- /package/src/{dedup.ts → shared/dedup.ts} +0 -0
- /package/src/{path-utils.ts → shared/path-utils.ts} +0 -0
- /package/src/{peer-id-registry.ts → targeting/peer-id-registry.ts} +0 -0
|
@@ -2,109 +2,114 @@ import fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { formatInboundEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
4
4
|
import { isAbortRequestText, isBtwRequestText } from "openclaw/plugin-sdk/reply-runtime";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { attachNativeAckReaction } from "./ack-reaction-service";
|
|
9
|
-
import { createDynamicAckReactionController } from "./ack-reaction/dynamic-ack-reaction-controller";
|
|
10
|
-
import { getAccessToken } from "./auth";
|
|
11
|
-
import {
|
|
12
|
-
createAICard,
|
|
13
|
-
commitAICardBlocks,
|
|
14
|
-
isCardInTerminalState,
|
|
15
|
-
recallAICardMessage,
|
|
16
|
-
} from "./card-service";
|
|
5
|
+
import { classifyAckReactionEmoji } from "../ack-reaction/ack-reaction-classifier";
|
|
6
|
+
import { attachNativeAckReaction } from "../ack-reaction/ack-reaction-service";
|
|
7
|
+
import { createDynamicAckReactionController } from "../ack-reaction/dynamic-ack-reaction-controller";
|
|
17
8
|
import {
|
|
18
9
|
invalidateAskUserQuestionsForScope,
|
|
19
10
|
syncInvalidatedAskUserQuestionCards,
|
|
20
|
-
} from "
|
|
11
|
+
} from "../card/ask-user-question";
|
|
21
12
|
import {
|
|
22
13
|
getDingTalkQuestionContext,
|
|
23
14
|
withDingTalkQuestionContext,
|
|
24
|
-
} from "
|
|
25
|
-
import { isCardRunStopRequested, registerCardRun, removeCardRun } from "
|
|
26
|
-
import { renderStatusLine } from "./card/statusline-renderer";
|
|
27
|
-
import { resolveConfiguredTaskModelMetadata } from "./card/task-model-metadata";
|
|
28
|
-
import { dispatchDingTalkCardStopCommand } from "./command/card-stop-command";
|
|
29
|
-
import { handleInboundCommandDispatch } from "./command/inbound-command-dispatch-service";
|
|
15
|
+
} from "../card/ask-user-question-context";
|
|
16
|
+
import { isCardRunStopRequested, registerCardRun, removeCardRun } from "../card/card-run-registry";
|
|
30
17
|
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} from "
|
|
36
|
-
import {
|
|
37
|
-
import
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
18
|
+
createAICard,
|
|
19
|
+
commitAICardBlocks,
|
|
20
|
+
isCardInTerminalState,
|
|
21
|
+
recallAICardMessage,
|
|
22
|
+
} from "../card/card-service";
|
|
23
|
+
import { renderStatusLine } from "../card/statusline-renderer";
|
|
24
|
+
import { resolveConfiguredTaskModelMetadata } from "../card/task-model-metadata";
|
|
25
|
+
import { dispatchDingTalkCardStopCommand } from "../command/card-stop-command";
|
|
26
|
+
import { buildLearningContextBlock, isLearningEnabled } from "../command/feedback-learning-service";
|
|
27
|
+
import { handleInboundCommandDispatch } from "../command/inbound-command-dispatch-service";
|
|
28
|
+
import { extractAttachmentText } from "../messaging/attachment-text-extractor";
|
|
29
|
+
import { deliverBtwReply, stripLeadingMentions } from "../messaging/btw-deliver";
|
|
30
|
+
import { parseInlineDirectives } from "../messaging/inline-directives";
|
|
31
|
+
import { prepareMediaInput, resolveOutboundMediaType } from "../messaging/media-utils";
|
|
41
32
|
import {
|
|
42
33
|
DEFAULT_MEDIA_CONTEXT_TTL_MS,
|
|
43
34
|
DEFAULT_MESSAGE_CONTEXT_TTL_DAYS,
|
|
44
35
|
upsertInboundMessageContext,
|
|
45
|
-
} from "
|
|
46
|
-
import { extractMessageContent } from "
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
} from "../messaging/message-context-store";
|
|
37
|
+
import { extractMessageContent } from "../messaging/message-utils";
|
|
38
|
+
import {
|
|
39
|
+
clearProactiveRiskObservationsForTest,
|
|
40
|
+
getProactiveRiskObservationForAny,
|
|
41
|
+
} from "../messaging/proactive-risk-registry";
|
|
42
|
+
import { resolveQuotedRuntimeContext } from "../messaging/quoted-context";
|
|
50
43
|
import {
|
|
51
44
|
downloadGroupFile,
|
|
52
45
|
getUnionIdByStaffId,
|
|
53
46
|
resolveQuotedFile,
|
|
54
|
-
} from "
|
|
47
|
+
} from "../messaging/quoted-file-service";
|
|
55
48
|
import {
|
|
56
49
|
buildInboundQuotedRef,
|
|
57
50
|
createReplyQuotedRef,
|
|
58
51
|
resolveQuotedRecord,
|
|
59
|
-
} from "
|
|
60
|
-
import {
|
|
52
|
+
} from "../messaging/quoted-ref";
|
|
53
|
+
import { createReplyStrategy } from "../messaging/reply-strategy";
|
|
54
|
+
import type { DeliverPayload, ReplyStrategy } from "../messaging/reply-strategy-types";
|
|
55
|
+
import { sendBySession, sendMessage, sendProactiveMedia } from "../messaging/send-service";
|
|
61
56
|
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
withReplySessionConflictRetry,
|
|
68
|
-
} from "./gateway/reply-session-conflict";
|
|
69
|
-
import { createReplyStrategy } from "./reply-strategy";
|
|
70
|
-
import type { DeliverPayload } from "./reply-strategy-types";
|
|
71
|
-
import { getDingTalkRuntime } from "./runtime";
|
|
72
|
-
import { sendBySession, sendMessage, sendProactiveMedia } from "./send-service";
|
|
73
|
-
import { acquireSessionLock } from "./session-lock";
|
|
74
|
-
import {
|
|
75
|
-
clearSessionPeerOverride,
|
|
76
|
-
getSessionPeerOverride,
|
|
77
|
-
setSessionPeerOverride,
|
|
78
|
-
} from "./session-peer-store";
|
|
79
|
-
import { resolveDingTalkSessionPeer } from "./session-routing";
|
|
80
|
-
import { getSessionState, initSessionState } from "./session-state";
|
|
81
|
-
import { getAgentDisplayName } from "./targeting/agent-name-matcher";
|
|
82
|
-
import {
|
|
83
|
-
buildAgentSessionKey,
|
|
84
|
-
dispatchSubAgents,
|
|
85
|
-
resolveMessageTarget,
|
|
86
|
-
sendUnmatchedAgentNotice,
|
|
87
|
-
} from "./targeting/agent-routing";
|
|
88
|
-
import { formatGroupMembers, noteGroupMember } from "./targeting/group-members-store";
|
|
57
|
+
normalizeAllowFrom,
|
|
58
|
+
isSenderAllowed,
|
|
59
|
+
resolveGroupAccess,
|
|
60
|
+
} from "../platform/access-control";
|
|
61
|
+
import { getAccessToken } from "../platform/auth";
|
|
89
62
|
import {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
63
|
+
resolveAckReactionSetting,
|
|
64
|
+
resolveGroupConfig,
|
|
65
|
+
resolveRelativePath,
|
|
66
|
+
resolveRobotCode,
|
|
67
|
+
} from "../platform/config";
|
|
68
|
+
import { setCurrentLogger } from "../platform/logger-context";
|
|
69
|
+
import { getDingTalkRuntime } from "../platform/runtime";
|
|
70
|
+
import { createRuntimeEventsFanout } from "../platform/runtime-events";
|
|
71
|
+
import { getSessionState, initSessionState } from "../platform/session-state";
|
|
72
|
+
import { AICardStatus } from "../platform/types";
|
|
94
73
|
import type {
|
|
95
74
|
DingTalkConfig,
|
|
96
75
|
HandleDingTalkMessageParams,
|
|
97
76
|
Logger,
|
|
98
77
|
MediaFile,
|
|
99
78
|
ResolvedDingTalkRoute,
|
|
100
|
-
} from "
|
|
79
|
+
} from "../platform/types";
|
|
80
|
+
import axios from "../shared/http-client";
|
|
101
81
|
import {
|
|
102
82
|
formatDingTalkErrorPayloadLog,
|
|
103
83
|
getErrorMessage,
|
|
104
84
|
getErrorResponseData,
|
|
105
85
|
maskSensitiveData,
|
|
106
86
|
parseBooleanLike,
|
|
107
|
-
} from "
|
|
87
|
+
} from "../shared/utils";
|
|
88
|
+
import { getAgentDisplayName } from "../targeting/agent-name-matcher";
|
|
89
|
+
import {
|
|
90
|
+
buildAgentSessionKey,
|
|
91
|
+
dispatchSubAgents,
|
|
92
|
+
resolveMessageTarget,
|
|
93
|
+
sendUnmatchedAgentNotice,
|
|
94
|
+
} from "../targeting/agent-routing";
|
|
95
|
+
import { formatGroupMembers, noteGroupMember } from "../targeting/group-members-store";
|
|
96
|
+
import { registerPeerId } from "../targeting/peer-id-registry";
|
|
97
|
+
import {
|
|
98
|
+
clearSessionPeerOverride,
|
|
99
|
+
getSessionPeerOverride,
|
|
100
|
+
setSessionPeerOverride,
|
|
101
|
+
} from "../targeting/session-peer-store";
|
|
102
|
+
import { resolveDingTalkSessionPeer } from "../targeting/session-routing";
|
|
103
|
+
import {
|
|
104
|
+
upsertObservedGroupTarget,
|
|
105
|
+
upsertObservedUserTarget,
|
|
106
|
+
} from "../targeting/target-directory-store";
|
|
107
|
+
import { dispatchInboundViaSessionQueue } from "./inbound-session-queue-dispatcher";
|
|
108
|
+
import {
|
|
109
|
+
isReplySessionConflictError,
|
|
110
|
+
withReplySessionConflictRetry,
|
|
111
|
+
} from "./reply-session-conflict";
|
|
112
|
+
import { acquireSessionLock } from "./session-lock";
|
|
108
113
|
|
|
109
114
|
const DEFAULT_PROACTIVE_HINT_COOLDOWN_HOURS = 24;
|
|
110
115
|
const MIN_THINKING_REACTION_VISIBLE_MS = 1200;
|
|
@@ -432,14 +437,25 @@ function buildGroupTurnContextPrompt(params: {
|
|
|
432
437
|
conversationId: string;
|
|
433
438
|
senderDingtalkId: string;
|
|
434
439
|
senderName?: string;
|
|
440
|
+
mentionedDingtalkIds?: readonly string[];
|
|
435
441
|
}): string {
|
|
436
442
|
const sanitizedSenderName = sanitizeGroupPromptName(params.senderName) || "Unknown";
|
|
443
|
+
const mentionedDingtalkIds = [
|
|
444
|
+
...new Set(
|
|
445
|
+
(params.mentionedDingtalkIds || [])
|
|
446
|
+
.map((value) => value.replace(/[\r\n,=]/g, " ").trim())
|
|
447
|
+
.filter(Boolean),
|
|
448
|
+
),
|
|
449
|
+
];
|
|
437
450
|
return [
|
|
438
451
|
"Current DingTalk group turn context:",
|
|
439
452
|
`- conversationId: ${params.conversationId}`,
|
|
440
453
|
`- senderDingtalkId: ${params.senderDingtalkId}`,
|
|
441
454
|
`- senderName: ${sanitizedSenderName}`,
|
|
455
|
+
`- mentionedDingtalkIds: ${mentionedDingtalkIds.length > 0 ? mentionedDingtalkIds.join(", ") : "(none provided)"}`,
|
|
442
456
|
"Treat senderDingtalkId and senderName as the authoritative sender for this turn. Do not guess the current sender from GroupMembers.",
|
|
457
|
+
"Treat mentionedDingtalkIds as raw structured IDs only; do not infer a display name or map them to text mentions without payload evidence.",
|
|
458
|
+
"mentionedDingtalkIds are webhook atUsers/richText IDs (for example $:LWCP_v1:$...), not staff IDs; they are not comparable with senderDingtalkId or GroupMembers ids and must not be used as send targets.",
|
|
443
459
|
].join("\n");
|
|
444
460
|
}
|
|
445
461
|
|
|
@@ -1008,6 +1024,17 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
1008
1024
|
const quotedRef = buildInboundQuotedRef(data, extractedContent);
|
|
1009
1025
|
const replyQuotedRef = createReplyQuotedRef(data.msgId);
|
|
1010
1026
|
const content = extractedContent;
|
|
1027
|
+
const mentionUserIds = [
|
|
1028
|
+
...new Set(
|
|
1029
|
+
[
|
|
1030
|
+
...(content.atUserDingtalkIds || []),
|
|
1031
|
+
...(content.atMentions || []).map((mention) => mention.userId),
|
|
1032
|
+
]
|
|
1033
|
+
.filter((value): value is string => typeof value === "string")
|
|
1034
|
+
.map((value) => value.trim())
|
|
1035
|
+
.filter(Boolean),
|
|
1036
|
+
),
|
|
1037
|
+
];
|
|
1011
1038
|
// Decide control-message bypasses once from the user-authored text. Reusing
|
|
1012
1039
|
// the result below keeps /stop out of the FIFO queue without calling the SDK
|
|
1013
1040
|
// classifier a second time after attachment/OCR enrichment.
|
|
@@ -1082,7 +1109,7 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
1082
1109
|
// abort branch finalizes the card with the abort confirmation text instead of
|
|
1083
1110
|
// sending a separate plain-text message.
|
|
1084
1111
|
let useCardMode = dingtalkConfig.messageType === "card";
|
|
1085
|
-
let currentAICard: import("
|
|
1112
|
+
let currentAICard: import("../platform/types").AICardInstance | undefined;
|
|
1086
1113
|
let questionCardTookOver = false;
|
|
1087
1114
|
|
|
1088
1115
|
let cardFlightKey: string | undefined;
|
|
@@ -1248,6 +1275,9 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
1248
1275
|
quotedRef,
|
|
1249
1276
|
senderId,
|
|
1250
1277
|
senderName,
|
|
1278
|
+
mentions: content.atMentions?.map((mention) => mention.name),
|
|
1279
|
+
mentionUserIds,
|
|
1280
|
+
chatType: isDirect ? "direct" : "group",
|
|
1251
1281
|
createdAt: data.createAt,
|
|
1252
1282
|
ttlMs: ttlDaysToMs(journalTTLDays),
|
|
1253
1283
|
ttlReferenceMs: data.createAt,
|
|
@@ -1780,6 +1810,7 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
1780
1810
|
conversationId: groupId,
|
|
1781
1811
|
senderDingtalkId: senderId,
|
|
1782
1812
|
senderName,
|
|
1813
|
+
mentionedDingtalkIds: mentionUserIds,
|
|
1783
1814
|
}),
|
|
1784
1815
|
groupConfig?.systemPrompt?.trim(),
|
|
1785
1816
|
]
|
|
@@ -2264,7 +2295,12 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
2264
2295
|
};
|
|
2265
2296
|
}
|
|
2266
2297
|
).events;
|
|
2298
|
+
const replyRuntimeEvents = createRuntimeEventsFanout(runtimeEvents, { log });
|
|
2267
2299
|
const releaseSessionLock = await acquireSessionLock(route.sessionKey);
|
|
2300
|
+
// Declared outside the dispatch try-block so the finally below can always
|
|
2301
|
+
// release strategy-owned resources, including early returns that neither
|
|
2302
|
+
// finalize nor abort (ask-user question-card takeover).
|
|
2303
|
+
let strategyForCleanup: ReplyStrategy | undefined;
|
|
2268
2304
|
const dynamicAckReactionController = createDynamicAckReactionController({
|
|
2269
2305
|
enabled: shouldTrackDynamicAckReaction,
|
|
2270
2306
|
initialReaction: resolvedAckReaction || "",
|
|
@@ -2275,7 +2311,7 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
2275
2311
|
conversationId: groupId,
|
|
2276
2312
|
sessionKey: route.sessionKey,
|
|
2277
2313
|
log,
|
|
2278
|
-
runtimeEvents,
|
|
2314
|
+
runtimeEvents: replyRuntimeEvents,
|
|
2279
2315
|
onReactionDisposed: () => {
|
|
2280
2316
|
ackReactionAttached = false;
|
|
2281
2317
|
},
|
|
@@ -2350,7 +2386,9 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
2350
2386
|
isStopRequested: isCurrentCardStopRequested,
|
|
2351
2387
|
inboundText: rawInboundText,
|
|
2352
2388
|
taskMeta,
|
|
2389
|
+
runtimeEvents: replyRuntimeEvents,
|
|
2353
2390
|
});
|
|
2391
|
+
strategyForCleanup = strategy;
|
|
2354
2392
|
|
|
2355
2393
|
let deliveredFinalCount = 0;
|
|
2356
2394
|
// Extracted as a thunk so reply-session init conflicts (raised by the
|
|
@@ -2534,19 +2572,36 @@ async function handleDingTalkMessageInner(params: HandleDingTalkMessageParams):
|
|
|
2534
2572
|
}
|
|
2535
2573
|
await strategy.finalize();
|
|
2536
2574
|
} finally {
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2575
|
+
try {
|
|
2576
|
+
// Only remove the registry entry if no stop was requested. When a stop is
|
|
2577
|
+
// in progress, card-stop-handler may still be running async operations
|
|
2578
|
+
// (finalize card, hide button, gateway abort) that read the record.
|
|
2579
|
+
// In that case, let the 30-minute TTL sweep handle cleanup.
|
|
2580
|
+
if (currentOutTrackId && !isCardRunStopRequested(currentOutTrackId)) {
|
|
2581
|
+
removeCardRun(currentOutTrackId);
|
|
2582
|
+
}
|
|
2583
|
+
// Guarantee strategy-owned cleanup on every exit path, including the
|
|
2584
|
+
// question-card takeover return above. `dispose()` is idempotent, so a
|
|
2585
|
+
// prior finalize()/abort() already having disposed is harmless. A
|
|
2586
|
+
// failing dispose is logged and swallowed: it must not skip the
|
|
2587
|
+
// remaining cleanup below.
|
|
2588
|
+
if (strategyForCleanup) {
|
|
2589
|
+
await strategyForCleanup.dispose().catch((disposeErr: unknown) => {
|
|
2590
|
+
log?.warn?.(
|
|
2591
|
+
`[DingTalk] Reply strategy cleanup failed for session ${route.sessionKey}: ${getErrorMessage(disposeErr)}`,
|
|
2592
|
+
);
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
await waitForDynamicAckDispose({
|
|
2596
|
+
dispose: () => dynamicAckReactionController.dispose(MIN_THINKING_REACTION_VISIBLE_MS),
|
|
2597
|
+
log,
|
|
2598
|
+
sessionKey: route.sessionKey,
|
|
2599
|
+
});
|
|
2600
|
+
} finally {
|
|
2601
|
+
// Unconditional: a session lock that never releases blocks every later
|
|
2602
|
+
// message on this session, so nothing above may prevent it.
|
|
2603
|
+
releaseSessionLock();
|
|
2543
2604
|
}
|
|
2544
|
-
await waitForDynamicAckDispose({
|
|
2545
|
-
dispose: () => dynamicAckReactionController.dispose(MIN_THINKING_REACTION_VISIBLE_MS),
|
|
2546
|
-
log,
|
|
2547
|
-
sessionKey: route.sessionKey,
|
|
2548
|
-
});
|
|
2549
|
-
releaseSessionLock();
|
|
2550
2605
|
}
|
|
2551
2606
|
} finally {
|
|
2552
2607
|
if (cardFlightKey) {
|
|
@@ -21,13 +21,20 @@
|
|
|
21
21
|
// orchestrator, adapted to soimy's blocking gateway contract (we await each
|
|
22
22
|
// task so the gateway's per-message dedup stays correct).
|
|
23
23
|
|
|
24
|
-
import { attachNativeAckReaction } from "../ack-reaction-service";
|
|
24
|
+
import { attachNativeAckReaction } from "../ack-reaction/ack-reaction-service";
|
|
25
25
|
import {
|
|
26
26
|
createAICard,
|
|
27
27
|
isCardInTerminalState,
|
|
28
28
|
recallAICardMessage,
|
|
29
29
|
streamAICard,
|
|
30
|
-
} from "../card-service";
|
|
30
|
+
} from "../card/card-service";
|
|
31
|
+
import { sendMessage } from "../messaging/send-service";
|
|
32
|
+
import type {
|
|
33
|
+
AICardInstance,
|
|
34
|
+
DingTalkConfig,
|
|
35
|
+
DingTalkInboundMessage,
|
|
36
|
+
Logger,
|
|
37
|
+
} from "../platform/types";
|
|
31
38
|
import {
|
|
32
39
|
chainInboundSessionTask,
|
|
33
40
|
getInboundSessionQueueDepth,
|
|
@@ -37,8 +44,6 @@ import {
|
|
|
37
44
|
MAX_INBOUND_SESSION_QUEUE_WAIT_MS,
|
|
38
45
|
pickQueueBusyAckPhrase,
|
|
39
46
|
} from "./inbound-session-queue";
|
|
40
|
-
import { sendMessage } from "../send-service";
|
|
41
|
-
import type { AICardInstance, DingTalkConfig, DingTalkInboundMessage, Logger } from "../types";
|
|
42
47
|
|
|
43
48
|
export interface InboundQueueDispatchInput {
|
|
44
49
|
accountId: string;
|
|
@@ -138,13 +143,14 @@ export async function dispatchInboundViaSessionQueue<T>(
|
|
|
138
143
|
// slot below. Otherwise a burst of inbound messages can all observe the
|
|
139
144
|
// same pre-await depth and each pass the cap check.
|
|
140
145
|
let queuedAckState: "queued" | "timed-out" = "queued";
|
|
141
|
-
const preCreatedCardPromise =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
const preCreatedCardPromise =
|
|
147
|
+
wasBusy && shouldPrepareQueueAckCard(input)
|
|
148
|
+
? tryPrepareQueueAckCard(input, () =>
|
|
149
|
+
queuedAckState === "timed-out"
|
|
150
|
+
? { content: QUEUE_WAIT_TIMEOUT_ACK, finished: true }
|
|
151
|
+
: { content: pickQueueBusyAckPhrase(), finished: false },
|
|
152
|
+
)
|
|
153
|
+
: undefined;
|
|
148
154
|
// Chain onto the prior task for this conversation and AWAIT. Awaiting (rather
|
|
149
155
|
// than fire-and-forget) preserves the gateway's per-message dedup:
|
|
150
156
|
// `markMessageProcessed` runs only after this message truly completes, so a
|
|
@@ -153,9 +159,7 @@ export async function dispatchInboundViaSessionQueue<T>(
|
|
|
153
159
|
return await chainInboundSessionTask(
|
|
154
160
|
queueKey,
|
|
155
161
|
async () => {
|
|
156
|
-
const preCreatedCard = preCreatedCardPromise
|
|
157
|
-
? await preCreatedCardPromise
|
|
158
|
-
: undefined;
|
|
162
|
+
const preCreatedCard = preCreatedCardPromise ? await preCreatedCardPromise : undefined;
|
|
159
163
|
if (!preCreatedCard) {
|
|
160
164
|
return handler(undefined);
|
|
161
165
|
}
|
|
@@ -13,22 +13,22 @@ export const SESSION_LOCK_NAMESPACE_POLICY = "memory-only" as const;
|
|
|
13
13
|
* Different session keys run in parallel; same key runs serially.
|
|
14
14
|
*/
|
|
15
15
|
export async function acquireSessionLock(sessionKey: string): Promise<() => void> {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
let release!: () => void;
|
|
17
|
+
const gate = new Promise<void>((r) => {
|
|
18
|
+
release = r;
|
|
19
|
+
});
|
|
20
|
+
const prev = locks.get(sessionKey) ?? Promise.resolve();
|
|
21
|
+
locks.set(sessionKey, gate);
|
|
22
|
+
await prev;
|
|
23
|
+
return () => {
|
|
24
|
+
if (locks.get(sessionKey) === gate) {
|
|
25
|
+
locks.delete(sessionKey);
|
|
26
|
+
}
|
|
27
|
+
release();
|
|
28
|
+
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Visible for testing only. */
|
|
32
32
|
export function _getLocksMapForTest(): Map<string, Promise<void>> {
|
|
33
|
-
|
|
33
|
+
return locks;
|
|
34
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { AttachmentTextSource } from "../types";
|
|
3
|
+
import type { AttachmentTextSource } from "../platform/types";
|
|
4
4
|
|
|
5
5
|
const MAX_EXTRACTED_TEXT_CHARS = 6000;
|
|
6
6
|
const MAX_ATTACHMENT_EXTRACT_BYTES = 2 * 1024 * 1024;
|
|
@@ -37,7 +37,8 @@ function isTextLikeMimeType(mimeType: string | undefined): boolean {
|
|
|
37
37
|
function normalizeWhitespace(text: string): string {
|
|
38
38
|
return text
|
|
39
39
|
.replace(/\r\n/g, "\n")
|
|
40
|
-
.split("\u0000")
|
|
40
|
+
.split("\u0000")
|
|
41
|
+
.join("")
|
|
41
42
|
.replace(/[ \t]{2,}/g, " ")
|
|
42
43
|
.replace(/[ \t]+\n/g, "\n")
|
|
43
44
|
.replace(/\n{3,}/g, "\n\n")
|
|
@@ -50,7 +51,9 @@ function limitExtractedText(text: string): AttachmentTextExtractionResult | null
|
|
|
50
51
|
return null;
|
|
51
52
|
}
|
|
52
53
|
const truncated = normalized.length > MAX_EXTRACTED_TEXT_CHARS;
|
|
53
|
-
const limited = truncated
|
|
54
|
+
const limited = truncated
|
|
55
|
+
? `${normalized.slice(0, MAX_EXTRACTED_TEXT_CHARS)}\n\n[内容已截断]`
|
|
56
|
+
: normalized;
|
|
54
57
|
return {
|
|
55
58
|
text: limited,
|
|
56
59
|
truncated,
|
|
@@ -68,10 +71,12 @@ function stripHtml(html: string): string {
|
|
|
68
71
|
.replace(/>/gi, ">")
|
|
69
72
|
.replace(/&/gi, "&")
|
|
70
73
|
.replace(/'/gi, "'")
|
|
71
|
-
.replace(/"/gi, "
|
|
74
|
+
.replace(/"/gi, '"');
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
async function extractTextLikeFile(
|
|
77
|
+
async function extractTextLikeFile(
|
|
78
|
+
filePath: string,
|
|
79
|
+
): Promise<AttachmentTextExtractionResult | null> {
|
|
75
80
|
const raw = await fs.readFile(filePath, "utf8");
|
|
76
81
|
return limitExtractedText(raw);
|
|
77
82
|
}
|
|
@@ -108,7 +113,11 @@ export async function extractAttachmentText(
|
|
|
108
113
|
const mimeType = input.mimeType?.toLowerCase();
|
|
109
114
|
const fileName = (input.fileName || path.basename(input.path)).toLowerCase();
|
|
110
115
|
|
|
111
|
-
if (
|
|
116
|
+
if (
|
|
117
|
+
mimeType?.startsWith("image/") ||
|
|
118
|
+
mimeType?.startsWith("audio/") ||
|
|
119
|
+
mimeType?.startsWith("video/")
|
|
120
|
+
) {
|
|
112
121
|
return null;
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -127,7 +136,12 @@ export async function extractAttachmentText(
|
|
|
127
136
|
return extractPdf(input.path);
|
|
128
137
|
}
|
|
129
138
|
|
|
130
|
-
if (
|
|
139
|
+
if (
|
|
140
|
+
mimeType === "text/html" ||
|
|
141
|
+
mimeType === "application/xhtml+xml" ||
|
|
142
|
+
fileName.endsWith(".html") ||
|
|
143
|
+
fileName.endsWith(".htm")
|
|
144
|
+
) {
|
|
131
145
|
return extractHtml(input.path);
|
|
132
146
|
}
|
|
133
147
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { DingTalkConfig, Logger } from "../platform/types";
|
|
2
|
+
import { sendMessage } from "./send-service";
|
|
3
3
|
|
|
4
4
|
const MAX_QUESTION_LENGTH = 80;
|
|
5
5
|
const LEADING_MENTIONS_RE = /^(?:@\S+\s+)*/u;
|
|
@@ -3,12 +3,12 @@ import type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-co
|
|
|
3
3
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
4
4
|
import { readStringParam } from "openclaw/plugin-sdk/param-readers";
|
|
5
5
|
import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
|
|
6
|
-
import { getConfig, stripTargetPrefix } from "../config";
|
|
7
|
-
import { getLogger } from "../logger-context";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
6
|
+
import { getConfig, stripTargetPrefix } from "../platform/config";
|
|
7
|
+
import { getLogger } from "../platform/logger-context";
|
|
8
|
+
import { hasConfiguredSecretInput } from "../platform/secret-input";
|
|
9
|
+
import { parseBooleanLike } from "../shared/utils";
|
|
10
|
+
import { resolveOriginalPeerId } from "../targeting/peer-id-registry";
|
|
11
|
+
import { sendMedia, sendMessage } from "./send-service";
|
|
12
12
|
|
|
13
13
|
function readBooleanLikeParam(params: Record<string, unknown>, key: string): boolean | undefined {
|
|
14
14
|
return parseBooleanLike(params[key]);
|
|
@@ -67,7 +67,15 @@ export function createDingTalkMessageActions(): ChannelMessageActionAdapter {
|
|
|
67
67
|
describeMessageTool: ({ cfg }) => describeDingTalkMessageTool(cfg),
|
|
68
68
|
supportsAction: ({ action }) => action === "send",
|
|
69
69
|
extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
|
|
70
|
-
handleAction: async ({
|
|
70
|
+
handleAction: async ({
|
|
71
|
+
action,
|
|
72
|
+
params,
|
|
73
|
+
cfg,
|
|
74
|
+
accountId,
|
|
75
|
+
dryRun,
|
|
76
|
+
mediaLocalRoots,
|
|
77
|
+
sessionKey,
|
|
78
|
+
}) => {
|
|
71
79
|
if (action !== "send") {
|
|
72
80
|
throw new Error(`Action ${action} is not supported for provider dingtalk.`);
|
|
73
81
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { getConfig } from "../config";
|
|
3
|
-
import { getLogger } from "../logger-context";
|
|
4
|
-
import { getDingTalkRuntime } from "../runtime";
|
|
5
|
-
import {
|
|
2
|
+
import { getConfig } from "../platform/config";
|
|
3
|
+
import { getLogger } from "../platform/logger-context";
|
|
4
|
+
import { getDingTalkRuntime } from "../platform/runtime";
|
|
5
|
+
import type { DingTalkChannelPlugin } from "../platform/types";
|
|
6
|
+
import { formatDingTalkErrorPayloadLog, parseBooleanLike } from "../shared/utils";
|
|
6
7
|
import { normalizeResolvedDingTalkTarget } from "../targeting/target-directory-adapter";
|
|
7
|
-
import
|
|
8
|
-
import { formatDingTalkErrorPayloadLog, parseBooleanLike } from "../utils";
|
|
8
|
+
import { sendMedia, sendMessage } from "./send-service";
|
|
9
9
|
|
|
10
10
|
function readBooleanLikeParam(params: Record<string, unknown>, key: string): boolean | undefined {
|
|
11
11
|
return parseBooleanLike(params[key]);
|
|
@@ -66,7 +66,9 @@ export function createDingTalkOutbound(): NonNullable<DingTalkChannelPlugin["out
|
|
|
66
66
|
};
|
|
67
67
|
} catch (err: any) {
|
|
68
68
|
if (err?.response?.data !== undefined) {
|
|
69
|
-
effectiveLog?.error?.(
|
|
69
|
+
effectiveLog?.error?.(
|
|
70
|
+
formatDingTalkErrorPayloadLog("outbound.sendText", err.response.data),
|
|
71
|
+
);
|
|
70
72
|
}
|
|
71
73
|
throw new Error(
|
|
72
74
|
typeof err?.response?.data === "string"
|
|
@@ -112,9 +114,10 @@ export function createDingTalkOutbound(): NonNullable<DingTalkChannelPlugin["out
|
|
|
112
114
|
);
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
const requestedMediaType =
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
const requestedMediaType =
|
|
118
|
+
typeof providedMediaType === "string"
|
|
119
|
+
? (providedMediaType as "image" | "voice" | "video" | "file")
|
|
120
|
+
: undefined;
|
|
118
121
|
|
|
119
122
|
try {
|
|
120
123
|
const result = await sendMedia(config, to, rawMediaPath, {
|
|
@@ -144,7 +147,9 @@ export function createDingTalkOutbound(): NonNullable<DingTalkChannelPlugin["out
|
|
|
144
147
|
};
|
|
145
148
|
} catch (err: any) {
|
|
146
149
|
if (err?.response?.data !== undefined) {
|
|
147
|
-
effectiveLog?.error?.(
|
|
150
|
+
effectiveLog?.error?.(
|
|
151
|
+
formatDingTalkErrorPayloadLog("outbound.sendMedia", err.response.data),
|
|
152
|
+
);
|
|
148
153
|
}
|
|
149
154
|
throw new Error(
|
|
150
155
|
typeof err?.response?.data === "string"
|