@soimy/dingtalk 3.6.10 → 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 +11655 -10324
- 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} +5 -5
- 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/messaging/inline-directives.d.ts +42 -0
- 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} +75 -2
- 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} +144 -85
- 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/messaging/inline-directives.ts +342 -0
- 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} +77 -2
- 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
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type MessageRecord,
|
|
4
|
-
} from "../message-context-store";
|
|
5
|
-
import type { DingTalkInboundMessage, Logger, MessageContent, QuotedRef } from "../types";
|
|
1
|
+
import type { DingTalkInboundMessage, Logger, MessageContent, QuotedRef } from "../platform/types";
|
|
2
|
+
import { resolveByQuotedRef, type MessageRecord } from "./message-context-store";
|
|
6
3
|
|
|
7
4
|
function firstTrimmedString(...candidates: Array<string | undefined>): string | undefined {
|
|
8
5
|
for (const candidate of candidates) {
|
|
@@ -27,7 +24,11 @@ export function buildInboundQuotedRef(
|
|
|
27
24
|
content: MessageContent,
|
|
28
25
|
): QuotedRef | undefined {
|
|
29
26
|
const repliedMsg = data.text?.repliedMsg;
|
|
30
|
-
const repliedMsgId = firstTrimmedString(
|
|
27
|
+
const repliedMsgId = firstTrimmedString(
|
|
28
|
+
repliedMsg?.msgId,
|
|
29
|
+
data.originalMsgId,
|
|
30
|
+
content.quoted?.msgId,
|
|
31
|
+
);
|
|
31
32
|
const fallbackCreatedAt = firstFiniteNumber(
|
|
32
33
|
repliedMsg?.createdAt,
|
|
33
34
|
content.quoted?.cardCreatedAt,
|
|
@@ -38,7 +39,10 @@ export function buildInboundQuotedRef(
|
|
|
38
39
|
repliedMsg?.senderId === data.chatbotUserId ||
|
|
39
40
|
content.quoted?.isQuotedCard === true;
|
|
40
41
|
if (isOutboundQuoted) {
|
|
41
|
-
const processQueryKey = firstTrimmedString(
|
|
42
|
+
const processQueryKey = firstTrimmedString(
|
|
43
|
+
data.originalProcessQueryKey,
|
|
44
|
+
content.quoted?.processQueryKey,
|
|
45
|
+
);
|
|
42
46
|
if (processQueryKey) {
|
|
43
47
|
return {
|
|
44
48
|
targetDirection: "outbound",
|
|
@@ -6,34 +6,43 @@
|
|
|
6
6
|
* inbound-handler only coordinates — it no longer owns card state.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { createCardDraftController } from "../card/card-draft-controller";
|
|
10
|
+
import {
|
|
11
|
+
buildImagePlaceholderText,
|
|
12
|
+
extractMarkdownImageCandidates,
|
|
13
|
+
} from "../card/card-markdown-image-reroute";
|
|
14
|
+
import { attachCardRunController } from "../card/card-run-registry";
|
|
9
15
|
import {
|
|
10
16
|
commitAICardBlocks,
|
|
11
17
|
isCardInTerminalState,
|
|
12
18
|
updateAICardStatusLine,
|
|
13
|
-
} from "
|
|
14
|
-
import { splitCardReasoningAnswerText } from "./card/reasoning-answer-split";
|
|
15
|
-
import { createReasoningBlockAssembler } from "./card/reasoning-block-assembler";
|
|
19
|
+
} from "../card/card-service";
|
|
16
20
|
import {
|
|
17
21
|
resolveCardStreamingMode,
|
|
18
22
|
shouldWarnDeprecatedCardRealTimeStreamOnce,
|
|
19
|
-
} from "
|
|
23
|
+
} from "../card/card-streaming-mode";
|
|
20
24
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from "
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
25
|
+
createCardTaskProgressController,
|
|
26
|
+
resolveCardTaskProgressEnabled,
|
|
27
|
+
} from "../card/card-task-progress";
|
|
28
|
+
import { splitCardReasoningAnswerText } from "../card/reasoning-answer-split";
|
|
29
|
+
import { createReasoningBlockAssembler } from "../card/reasoning-block-assembler";
|
|
30
|
+
import { recordRunStart, getAggregatedUsage, clearRuns } from "../card/run-usage-store";
|
|
31
|
+
import { renderStatusLine } from "../card/statusline-renderer";
|
|
32
|
+
import type { StatusLineData } from "../card/statusline-renderer";
|
|
33
|
+
import { resolveRelativePath } from "../platform/config";
|
|
34
|
+
import { getTaskTimeSeconds, updateSessionState } from "../platform/session-state";
|
|
35
|
+
import type { AICardInstance } from "../platform/types";
|
|
36
|
+
import { AICardStatus } from "../platform/types";
|
|
37
|
+
import { formatDingTalkErrorPayloadLog } from "../shared/utils";
|
|
28
38
|
import { prepareMediaInput, resolveOutboundMediaType } from "./media-utils";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
import type {
|
|
40
|
+
DeliverPayload,
|
|
41
|
+
ReplyOptions,
|
|
42
|
+
ReplyStrategy,
|
|
43
|
+
ReplyStrategyContext,
|
|
44
|
+
} from "./reply-strategy-types";
|
|
33
45
|
import { sendBySession, sendMessage, sendProactiveMedia, uploadMedia } from "./send-service";
|
|
34
|
-
import type { AICardInstance } from "./types";
|
|
35
|
-
import { AICardStatus } from "./types";
|
|
36
|
-
import { formatDingTalkErrorPayloadLog } from "./utils";
|
|
37
46
|
|
|
38
47
|
const EMPTY_FINAL_REPLY = "✅ Done";
|
|
39
48
|
const DEFAULT_CARD_FAILED_MESSAGE = "回复生成失败,请重试";
|
|
@@ -73,33 +82,38 @@ export function createCardReplyStrategy(
|
|
|
73
82
|
? getTaskTimeSeconds(sessionTaskStateScope)
|
|
74
83
|
: undefined;
|
|
75
84
|
const cardElapsedMs = Math.max(0, Date.now() - card.createdAt);
|
|
76
|
-
const sessionElapsedMs =
|
|
77
|
-
? sessionTaskTimeSeconds * 1000
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
ctx.taskMeta.elapsedMs = Math.max(
|
|
83
|
-
cardElapsedMs,
|
|
84
|
-
sessionElapsedMs ?? 0,
|
|
85
|
-
metaElapsedMs ?? 0,
|
|
86
|
-
);
|
|
85
|
+
const sessionElapsedMs =
|
|
86
|
+
typeof sessionTaskTimeSeconds === "number" ? sessionTaskTimeSeconds * 1000 : undefined;
|
|
87
|
+
const metaElapsedMs =
|
|
88
|
+
typeof ctx.taskMeta.elapsedMs === "number" && ctx.taskMeta.elapsedMs > 0
|
|
89
|
+
? ctx.taskMeta.elapsedMs
|
|
90
|
+
: undefined;
|
|
91
|
+
ctx.taskMeta.elapsedMs = Math.max(cardElapsedMs, sessionElapsedMs ?? 0, metaElapsedMs ?? 0);
|
|
87
92
|
|
|
88
93
|
let inputTokens: number | undefined;
|
|
89
94
|
let outputTokens: number | undefined;
|
|
90
95
|
let cacheRead: number | undefined;
|
|
91
96
|
if (ctx.taskMeta.runIds && ctx.taskMeta.runIds.size > 0) {
|
|
92
97
|
const tokenUsage = getAggregatedUsage(ctx.taskMeta.runIds);
|
|
93
|
-
if (typeof tokenUsage.input === "number") {
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
if (typeof tokenUsage.input === "number") {
|
|
99
|
+
inputTokens = tokenUsage.input;
|
|
100
|
+
}
|
|
101
|
+
if (typeof tokenUsage.output === "number") {
|
|
102
|
+
outputTokens = tokenUsage.output;
|
|
103
|
+
}
|
|
104
|
+
if (typeof tokenUsage.cacheRead === "number") {
|
|
105
|
+
cacheRead = tokenUsage.cacheRead;
|
|
106
|
+
}
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
const statusLineData: StatusLineData = {
|
|
99
110
|
model: ctx.taskMeta.model,
|
|
100
111
|
effort: ctx.taskMeta.effort,
|
|
101
112
|
agent: ctx.taskMeta.agent,
|
|
102
|
-
taskTime:
|
|
113
|
+
taskTime:
|
|
114
|
+
typeof ctx.taskMeta.elapsedMs === "number"
|
|
115
|
+
? Math.round(ctx.taskMeta.elapsedMs / 1000)
|
|
116
|
+
: undefined,
|
|
103
117
|
inputTokens,
|
|
104
118
|
outputTokens,
|
|
105
119
|
cacheRead,
|
|
@@ -131,6 +145,15 @@ export function createCardReplyStrategy(
|
|
|
131
145
|
throttleMs: config.cardStreamInterval ?? 1000,
|
|
132
146
|
getStatusLine: buildStatusLine,
|
|
133
147
|
});
|
|
148
|
+
const taskProgressController = createCardTaskProgressController({
|
|
149
|
+
sessionKey: ctx.sessionKey ?? "",
|
|
150
|
+
enabled: resolveCardTaskProgressEnabled(config),
|
|
151
|
+
refresh: config.cardTaskProgressRefresh,
|
|
152
|
+
runtimeEvents: ctx.runtimeEvents,
|
|
153
|
+
updateProgress: controller.updateProgress,
|
|
154
|
+
clearProgress: controller.clearProgress,
|
|
155
|
+
log,
|
|
156
|
+
});
|
|
134
157
|
const reasoningAssembler = createReasoningBlockAssembler();
|
|
135
158
|
if (card.outTrackId) {
|
|
136
159
|
attachCardRunController(card.outTrackId, controller);
|
|
@@ -145,7 +168,8 @@ export function createCardReplyStrategy(
|
|
|
145
168
|
const processedMediaUrls = new Set<string>();
|
|
146
169
|
|
|
147
170
|
const getRenderedTimeline = (options: { preferFinalAnswer?: boolean } = {}): string => {
|
|
148
|
-
const fallbackAnswer =
|
|
171
|
+
const fallbackAnswer =
|
|
172
|
+
finalTextForFallback || (sawFinalDelivery ? EMPTY_FINAL_REPLY : undefined);
|
|
149
173
|
return controller.getRenderedContent({
|
|
150
174
|
fallbackAnswer,
|
|
151
175
|
overrideAnswer: options.preferFinalAnswer ? finalTextForFallback : undefined,
|
|
@@ -183,10 +207,7 @@ export function createCardReplyStrategy(
|
|
|
183
207
|
}
|
|
184
208
|
const blocks = reasoningAssembler.ingestSnapshot(text);
|
|
185
209
|
const trimmed = text.trimStart();
|
|
186
|
-
if (
|
|
187
|
-
blocks.length === 0
|
|
188
|
-
&& !trimmed.startsWith("Reasoning:")
|
|
189
|
-
) {
|
|
210
|
+
if (blocks.length === 0 && !trimmed.startsWith("Reasoning:")) {
|
|
190
211
|
if (trimmed.startsWith("Reason:")) {
|
|
191
212
|
latestReasoningSnapshot = "";
|
|
192
213
|
return;
|
|
@@ -308,8 +329,9 @@ export function createCardReplyStrategy(
|
|
|
308
329
|
await applyModeAwareReasoningSnapshot(normalizedSnapshot.reasoningText);
|
|
309
330
|
}
|
|
310
331
|
|
|
311
|
-
const answerSnapshot =
|
|
312
|
-
|
|
332
|
+
const answerSnapshot =
|
|
333
|
+
normalizedSnapshot.answerText ??
|
|
334
|
+
(!normalizedSnapshot.reasoningText ? rewrittenSnapshot : undefined);
|
|
313
335
|
if (!answerSnapshot) {
|
|
314
336
|
return;
|
|
315
337
|
}
|
|
@@ -364,9 +386,7 @@ export function createCardReplyStrategy(
|
|
|
364
386
|
let prepared: Awaited<ReturnType<typeof prepareMediaInput>> | undefined;
|
|
365
387
|
try {
|
|
366
388
|
prepared = await prepareMediaInput(candidate.url, log, config.mediaUrlAllowlist);
|
|
367
|
-
const mediaPath = prepared.cleanup
|
|
368
|
-
? prepared.path
|
|
369
|
-
: resolveRelativePath(prepared.path);
|
|
389
|
+
const mediaPath = prepared.cleanup ? prepared.path : resolveRelativePath(prepared.path);
|
|
370
390
|
const mediaType = resolveOutboundMediaType({ mediaPath, asVoice: false });
|
|
371
391
|
if (mediaType !== "image") {
|
|
372
392
|
continue;
|
|
@@ -379,7 +399,8 @@ export function createCardReplyStrategy(
|
|
|
379
399
|
|
|
380
400
|
processedMediaUrls.add(candidate.url.trim());
|
|
381
401
|
const placeholder = buildImagePlaceholderText({ alt: candidate.alt, url: candidate.url });
|
|
382
|
-
const blockText =
|
|
402
|
+
const blockText =
|
|
403
|
+
candidate.alt.trim() || placeholder.replace(/^见下图/, "").trim() || "图片";
|
|
383
404
|
successfulReroutes.push({
|
|
384
405
|
start: candidate.start,
|
|
385
406
|
end: candidate.end,
|
|
@@ -427,7 +448,9 @@ export function createCardReplyStrategy(
|
|
|
427
448
|
}
|
|
428
449
|
recordRunStart(runId);
|
|
429
450
|
if (ctx.taskMeta) {
|
|
430
|
-
if (!ctx.taskMeta.runIds) {
|
|
451
|
+
if (!ctx.taskMeta.runIds) {
|
|
452
|
+
ctx.taskMeta.runIds = new Set();
|
|
453
|
+
}
|
|
431
454
|
ctx.taskMeta.runIds.add(runId);
|
|
432
455
|
}
|
|
433
456
|
},
|
|
@@ -472,7 +495,10 @@ export function createCardReplyStrategy(
|
|
|
472
495
|
const textToSend = payload.text;
|
|
473
496
|
|
|
474
497
|
// Empty-payload guard — card final is an exception (e.g. file-only response).
|
|
475
|
-
if (
|
|
498
|
+
if (
|
|
499
|
+
(typeof textToSend !== "string" || textToSend.length === 0) &&
|
|
500
|
+
payload.mediaUrls.length === 0
|
|
501
|
+
) {
|
|
476
502
|
if (payload.kind !== "final") {
|
|
477
503
|
return;
|
|
478
504
|
}
|
|
@@ -482,14 +508,15 @@ export function createCardReplyStrategy(
|
|
|
482
508
|
if (payload.kind === "final") {
|
|
483
509
|
// OpenClaw can append a non-terminal tool warning after a valid final.
|
|
484
510
|
// Do not let that plain-text warning replace the answer already captured by the card.
|
|
485
|
-
const hasPreservableReply =
|
|
486
|
-
finalTextForFallback?.trim() || controller.getFinalAnswerContent().trim()
|
|
487
|
-
|
|
511
|
+
const hasPreservableReply =
|
|
512
|
+
Boolean(finalTextForFallback?.trim() || controller.getFinalAnswerContent().trim()) ||
|
|
513
|
+
processedMediaUrls.size > 0 ||
|
|
514
|
+
pendingNonImageMedia.length > 0;
|
|
488
515
|
if (
|
|
489
|
-
payload.isError === true
|
|
490
|
-
&&
|
|
491
|
-
|
|
492
|
-
|
|
516
|
+
payload.isError === true &&
|
|
517
|
+
sawFinalDelivery &&
|
|
518
|
+
payload.mediaUrls.length === 0 &&
|
|
519
|
+
hasPreservableReply
|
|
493
520
|
) {
|
|
494
521
|
log?.debug?.("[DingTalk][Finalize] Ignoring late error final after answer delivery");
|
|
495
522
|
return;
|
|
@@ -502,10 +529,10 @@ export function createCardReplyStrategy(
|
|
|
502
529
|
}
|
|
503
530
|
log?.info?.(
|
|
504
531
|
`[DingTalk][Finalize] deliver(final) received — cardState=${card.state} ` +
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
532
|
+
`textLen=${typeof textToSend === "string" ? textToSend.length : "null"} ` +
|
|
533
|
+
`mediaUrls=${payload.mediaUrls.length} ` +
|
|
534
|
+
`lastAnswer="${(controller.getLastAnswerContent() ?? "").slice(0, 80)}" ` +
|
|
535
|
+
`lastContent="${(controller.getLastContent() ?? "").slice(0, 80)}"`,
|
|
509
536
|
);
|
|
510
537
|
// Inline media upload → image blocks in card; defer non-image attachments
|
|
511
538
|
if (payload.mediaUrls.length > 0) {
|
|
@@ -516,9 +543,14 @@ export function createCardReplyStrategy(
|
|
|
516
543
|
}
|
|
517
544
|
try {
|
|
518
545
|
const prepared = await prepareMediaInput(url, log, config.mediaUrlAllowlist);
|
|
519
|
-
const mediaType = resolveOutboundMediaType({
|
|
546
|
+
const mediaType = resolveOutboundMediaType({
|
|
547
|
+
mediaPath: prepared.path,
|
|
548
|
+
asVoice: false,
|
|
549
|
+
});
|
|
520
550
|
if (mediaType !== "image") {
|
|
521
|
-
log?.debug?.(
|
|
551
|
+
log?.debug?.(
|
|
552
|
+
`[DingTalk][Card] Deferring non-image media (${mediaType}) for out-of-card delivery: ${url}`,
|
|
553
|
+
);
|
|
522
554
|
// Collect non-image attachments for later delivery
|
|
523
555
|
if (mediaType === "voice" || mediaType === "video" || mediaType === "file") {
|
|
524
556
|
pendingNonImageMedia.push({ url, type: mediaType });
|
|
@@ -548,7 +580,11 @@ export function createCardReplyStrategy(
|
|
|
548
580
|
const normalizedFinal = await applySplitTextToTimeline(rewrittenFinalText, {
|
|
549
581
|
answerHandling: "capture",
|
|
550
582
|
});
|
|
551
|
-
if (
|
|
583
|
+
if (
|
|
584
|
+
isFirstFinalDelivery &&
|
|
585
|
+
!normalizedFinal.answerText &&
|
|
586
|
+
!normalizedFinal.reasoningText
|
|
587
|
+
) {
|
|
552
588
|
finalTextForFallback = rewrittenFinalText;
|
|
553
589
|
}
|
|
554
590
|
await flushPendingReasoning();
|
|
@@ -560,7 +596,9 @@ export function createCardReplyStrategy(
|
|
|
560
596
|
// ---- tool: append to card ----
|
|
561
597
|
if (payload.kind === "tool") {
|
|
562
598
|
if (controller.isFailed() || isCardInTerminalState(card.state)) {
|
|
563
|
-
log?.debug?.(
|
|
599
|
+
log?.debug?.(
|
|
600
|
+
"[DingTalk] Card failed, skipping tool result (will send full reply on final)",
|
|
601
|
+
);
|
|
564
602
|
return;
|
|
565
603
|
}
|
|
566
604
|
await flushPendingReasoning();
|
|
@@ -599,9 +637,14 @@ export function createCardReplyStrategy(
|
|
|
599
637
|
}
|
|
600
638
|
try {
|
|
601
639
|
const prepared = await prepareMediaInput(url, log, config.mediaUrlAllowlist);
|
|
602
|
-
const mediaType = resolveOutboundMediaType({
|
|
640
|
+
const mediaType = resolveOutboundMediaType({
|
|
641
|
+
mediaPath: prepared.path,
|
|
642
|
+
asVoice: false,
|
|
643
|
+
});
|
|
603
644
|
if (mediaType !== "image") {
|
|
604
|
-
log?.debug?.(
|
|
645
|
+
log?.debug?.(
|
|
646
|
+
`[DingTalk][Card] Deferring non-image media (${mediaType}) for out-of-card delivery: ${url}`,
|
|
647
|
+
);
|
|
605
648
|
pendingNonImageMedia.push({ url, type: mediaType });
|
|
606
649
|
await prepared.cleanup?.();
|
|
607
650
|
continue;
|
|
@@ -621,13 +664,14 @@ export function createCardReplyStrategy(
|
|
|
621
664
|
},
|
|
622
665
|
|
|
623
666
|
async finalize(): Promise<void> {
|
|
667
|
+
await taskProgressController.dispose();
|
|
624
668
|
log?.info?.(
|
|
625
669
|
`[DingTalk][Finalize] Step 5 entry — ` +
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
670
|
+
`cardState=${card.state ?? "N/A"} ` +
|
|
671
|
+
`controllerFailed=${controller.isFailed()} ` +
|
|
672
|
+
`finalTextForFallback="${(finalTextForFallback ?? "").slice(0, 80)}" ` +
|
|
673
|
+
`lastAnswer="${(controller.getLastAnswerContent() ?? "").slice(0, 80)}" ` +
|
|
674
|
+
`lastContent="${(controller.getLastContent() ?? "").slice(0, 80)}"`,
|
|
631
675
|
);
|
|
632
676
|
|
|
633
677
|
if (isStopRequested?.()) {
|
|
@@ -645,14 +689,15 @@ export function createCardReplyStrategy(
|
|
|
645
689
|
// deliver it as a markdown fallback so the user sees the final result.
|
|
646
690
|
// The user may see partial overlap with the frozen card's content, but
|
|
647
691
|
// delivering the full answer is preferred over silence.
|
|
648
|
-
const recoveryText =
|
|
649
|
-
||
|
|
650
|
-
||
|
|
651
|
-
|
|
692
|
+
const recoveryText =
|
|
693
|
+
getRenderedTimeline({ preferFinalAnswer: true }) ||
|
|
694
|
+
finalTextForFallback ||
|
|
695
|
+
controller.getLastAnswerContent() ||
|
|
696
|
+
controller.getLastContent();
|
|
652
697
|
if (recoveryText) {
|
|
653
698
|
log?.info?.(
|
|
654
699
|
`[DingTalk][Finalize] Card already FINISHED — sending markdown fallback for session-recovery content ` +
|
|
655
|
-
|
|
700
|
+
`len=${recoveryText.length} preview="${recoveryText.slice(0, 80)}"`,
|
|
656
701
|
);
|
|
657
702
|
const sendResult = await sendMessage(ctx.config, ctx.to, recoveryText, {
|
|
658
703
|
sessionWebhook: ctx.sessionWebhook,
|
|
@@ -690,9 +735,10 @@ export function createCardReplyStrategy(
|
|
|
690
735
|
|
|
691
736
|
// Card failed -> markdown fallback (bypass sendMessage to avoid duplicate card).
|
|
692
737
|
if (card.state === AICardStatus.FAILED || controller.isFailed()) {
|
|
693
|
-
const fallbackText =
|
|
694
|
-
|
|
695
|
-
||
|
|
738
|
+
const fallbackText =
|
|
739
|
+
getRenderedTimeline({ preferFinalAnswer: true }) ||
|
|
740
|
+
controller.getLastAnswerContent() ||
|
|
741
|
+
DEFAULT_CARD_FAILED_MESSAGE;
|
|
696
742
|
if (fallbackText) {
|
|
697
743
|
log?.debug?.("[DingTalk] Card failed during streaming, sending markdown fallback");
|
|
698
744
|
const sendResult = await sendMessage(ctx.config, ctx.to, fallbackText, {
|
|
@@ -727,38 +773,48 @@ export function createCardReplyStrategy(
|
|
|
727
773
|
await controller.waitForInFlight();
|
|
728
774
|
|
|
729
775
|
// Prepare finalize options for single instances API call
|
|
730
|
-
const fallbackAnswer =
|
|
776
|
+
const fallbackAnswer =
|
|
777
|
+
finalTextForFallback || (sawFinalDelivery ? EMPTY_FINAL_REPLY : undefined);
|
|
731
778
|
const blockListJson = controller.getRenderedBlocks({
|
|
732
779
|
fallbackAnswer,
|
|
733
780
|
overrideAnswer: finalTextForFallback,
|
|
734
781
|
});
|
|
735
|
-
const content =
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
782
|
+
const content =
|
|
783
|
+
controller.getRenderedContent({
|
|
784
|
+
fallbackAnswer,
|
|
785
|
+
overrideAnswer: finalTextForFallback,
|
|
786
|
+
}) ||
|
|
787
|
+
fallbackAnswer ||
|
|
788
|
+
EMPTY_FINAL_REPLY;
|
|
739
789
|
|
|
740
790
|
controller.stop();
|
|
741
791
|
log?.info?.(
|
|
742
792
|
`[DingTalk][Finalize] Calling commitAICardBlocks — ` +
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
793
|
+
`blockListLen=${blockListJson.length} contentLen=${content.length} ` +
|
|
794
|
+
`source=${finalTextForFallback ? "final.payload" : controller.getFinalAnswerContent() ? "timeline.answer" : sawFinalDelivery ? "timeline.fileOnly" : "fallbackDone"} ` +
|
|
795
|
+
`preview="${content.slice(0, 120)}"`,
|
|
746
796
|
);
|
|
747
797
|
|
|
748
798
|
// Build statusLine for card template
|
|
749
799
|
const statusLine = buildStatusLine();
|
|
750
800
|
|
|
751
|
-
await commitAICardBlocks(
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
801
|
+
await commitAICardBlocks(
|
|
802
|
+
card,
|
|
803
|
+
{
|
|
804
|
+
blockListJson,
|
|
805
|
+
content,
|
|
806
|
+
statusLine,
|
|
807
|
+
quotedRef: ctx.replyQuotedRef,
|
|
808
|
+
},
|
|
809
|
+
log,
|
|
810
|
+
);
|
|
757
811
|
|
|
758
812
|
// Send deferred non-image attachments after card finalize
|
|
759
813
|
// Use sessionWebhook for reply-session semantics; fallback to proactive if unavailable.
|
|
760
814
|
if (pendingNonImageMedia.length > 0) {
|
|
761
|
-
log?.debug?.(
|
|
815
|
+
log?.debug?.(
|
|
816
|
+
`[DingTalk][Card] Sending ${pendingNonImageMedia.length} deferred non-image attachments`,
|
|
817
|
+
);
|
|
762
818
|
for (const { url, type } of pendingNonImageMedia) {
|
|
763
819
|
try {
|
|
764
820
|
const prepared = await prepareMediaInput(url, log, config.mediaUrlAllowlist);
|
|
@@ -775,7 +831,9 @@ export function createCardReplyStrategy(
|
|
|
775
831
|
storePath: ctx.storePath,
|
|
776
832
|
});
|
|
777
833
|
if (!sendResult.ok) {
|
|
778
|
-
log?.warn?.(
|
|
834
|
+
log?.warn?.(
|
|
835
|
+
`[DingTalk][Card] Deferred media session send failed: ${sendResult.error || "unknown"}`,
|
|
836
|
+
);
|
|
779
837
|
}
|
|
780
838
|
} else {
|
|
781
839
|
// Fallback: proactive send when no reply session available
|
|
@@ -784,7 +842,9 @@ export function createCardReplyStrategy(
|
|
|
784
842
|
accountId: ctx.accountId,
|
|
785
843
|
});
|
|
786
844
|
if (!result.ok) {
|
|
787
|
-
log?.warn?.(
|
|
845
|
+
log?.warn?.(
|
|
846
|
+
`[DingTalk][Card] Deferred media proactive send failed: ${result.error || "unknown"}`,
|
|
847
|
+
);
|
|
788
848
|
}
|
|
789
849
|
}
|
|
790
850
|
|
|
@@ -832,6 +892,7 @@ export function createCardReplyStrategy(
|
|
|
832
892
|
},
|
|
833
893
|
|
|
834
894
|
async abort(_error: Error): Promise<void> {
|
|
895
|
+
await taskProgressController.dispose();
|
|
835
896
|
lifecycleState = "sealed";
|
|
836
897
|
if (card.accountId && card.conversationId) {
|
|
837
898
|
clearRuns(ctx.taskMeta?.runIds);
|
|
@@ -842,22 +903,38 @@ export function createCardReplyStrategy(
|
|
|
842
903
|
try {
|
|
843
904
|
// For V2 template, finalize via instances API
|
|
844
905
|
const errorBlockListJson = JSON.stringify([{ type: 0, markdown: "❌ 处理失败" }]);
|
|
845
|
-
await commitAICardBlocks(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
906
|
+
await commitAICardBlocks(
|
|
907
|
+
card,
|
|
908
|
+
{
|
|
909
|
+
blockListJson: errorBlockListJson,
|
|
910
|
+
content: "❌ 处理失败",
|
|
911
|
+
},
|
|
912
|
+
log,
|
|
913
|
+
);
|
|
849
914
|
} catch (cardCloseErr: unknown) {
|
|
850
|
-
log?.debug?.(
|
|
915
|
+
log?.debug?.(
|
|
916
|
+
`[DingTalk] Failed to finalize card after dispatch error: ${(cardCloseErr as Error).message}`,
|
|
917
|
+
);
|
|
851
918
|
card.state = AICardStatus.FAILED;
|
|
852
919
|
card.lastUpdated = Date.now();
|
|
853
920
|
}
|
|
854
921
|
}
|
|
855
922
|
},
|
|
856
923
|
|
|
924
|
+
async dispose(): Promise<void> {
|
|
925
|
+
// Release the progress heartbeat/subscription even when this strategy is
|
|
926
|
+
// dropped without finalize (for example ask-user question-card takeover).
|
|
927
|
+
// This path never commits the card, so a progress-only card must also
|
|
928
|
+
// clear its remote block list instead of leaving a live-looking card.
|
|
929
|
+
await taskProgressController.dispose({ clearRemoteWhenEmpty: true });
|
|
930
|
+
},
|
|
931
|
+
|
|
857
932
|
getFinalText(): string | undefined {
|
|
858
|
-
return
|
|
859
|
-
||
|
|
860
|
-
|
|
933
|
+
return (
|
|
934
|
+
finalTextForFallback ||
|
|
935
|
+
controller.getFinalAnswerContent() ||
|
|
936
|
+
(sawFinalDelivery ? EMPTY_FINAL_REPLY : undefined)
|
|
937
|
+
);
|
|
861
938
|
},
|
|
862
939
|
};
|
|
863
940
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import path from "node:path";
|
|
10
|
-
import { resolveRelativePath } from "
|
|
10
|
+
import { resolveRelativePath } from "../platform/config";
|
|
11
11
|
import { prepareMediaInput, resolveOutboundMediaType } from "./media-utils";
|
|
12
12
|
import type {
|
|
13
13
|
DeliverPayload,
|
|
@@ -90,7 +90,9 @@ export function createMarkdownReplyStrategy(ctx: ReplyStrategyContext): ReplyStr
|
|
|
90
90
|
sentVisibleContent = true;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
const prepareAnswerSuffix = (
|
|
93
|
+
const prepareAnswerSuffix = (
|
|
94
|
+
text: string | undefined,
|
|
95
|
+
): {
|
|
94
96
|
text: string;
|
|
95
97
|
markSent: () => void;
|
|
96
98
|
} | null => {
|
|
@@ -272,6 +274,10 @@ export function createMarkdownReplyStrategy(ctx: ReplyStrategyContext): ReplyStr
|
|
|
272
274
|
// Nothing to clean up.
|
|
273
275
|
},
|
|
274
276
|
|
|
277
|
+
async dispose(): Promise<void> {
|
|
278
|
+
// Markdown delivery owns no timers or subscriptions.
|
|
279
|
+
},
|
|
280
|
+
|
|
275
281
|
getFinalText(): string | undefined {
|
|
276
282
|
return finalText || activeAnswerText || undefined;
|
|
277
283
|
},
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { GetReplyOptions } from "openclaw/plugin-sdk/reply-runtime";
|
|
10
|
-
import type {
|
|
10
|
+
import type { RuntimeEventsSurface } from "../platform/runtime-events";
|
|
11
|
+
import type { DingTalkConfig, Logger, QuotedRef } from "../platform/types";
|
|
11
12
|
|
|
12
13
|
// ---- Internal helper type ----
|
|
13
14
|
|
|
@@ -57,6 +58,14 @@ export interface ReplyStrategy {
|
|
|
57
58
|
/** Called when dispatch throws an error. */
|
|
58
59
|
abort(error: Error): Promise<void>;
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Release strategy-owned resources (timers, event subscriptions) without
|
|
63
|
+
* touching the delivery surface. Idempotent, and safe to call after
|
|
64
|
+
* `finalize()`/`abort()`. Callers must invoke this even on early-return
|
|
65
|
+
* paths so a strategy that never finalized cannot leak.
|
|
66
|
+
*/
|
|
67
|
+
dispose(): Promise<void>;
|
|
68
|
+
|
|
60
69
|
/** Last known final text (for external consumers such as logging). */
|
|
61
70
|
getFinalText(): string | undefined;
|
|
62
71
|
}
|
|
@@ -94,4 +103,5 @@ export interface ReplyStrategyContext {
|
|
|
94
103
|
isStopRequested?: () => boolean;
|
|
95
104
|
inboundText?: string;
|
|
96
105
|
taskMeta?: TaskMeta;
|
|
106
|
+
runtimeEvents?: RuntimeEventsSurface;
|
|
97
107
|
}
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
* concurrently. The `sessionFilter` parameter is required for this reason.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
import type { DingTalkConfig, Logger } from "../platform/types";
|
|
27
28
|
import type { DeliverPayload, ReplyOptions, ReplyStrategy } from "./reply-strategy-types";
|
|
28
|
-
import type { DingTalkConfig, Logger } from "./types";
|
|
29
29
|
|
|
30
30
|
const TOOL_REACTION_SILENCE_MS = 55_000;
|
|
31
31
|
const TOOL_HEARTBEAT_INTERVAL_MS = 60_000;
|
|
@@ -183,6 +183,11 @@ export function withDynamicReaction(
|
|
|
183
183
|
await inner.abort(_error);
|
|
184
184
|
},
|
|
185
185
|
|
|
186
|
+
async dispose(): Promise<void> {
|
|
187
|
+
await dispose();
|
|
188
|
+
await inner.dispose();
|
|
189
|
+
},
|
|
190
|
+
|
|
186
191
|
getFinalText(): string | undefined {
|
|
187
192
|
return inner.getFinalText();
|
|
188
193
|
},
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* strategy implementations. Type definitions live in reply-strategy-types.ts.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { AICardInstance } from "
|
|
9
|
-
import type { ReplyStrategy, ReplyStrategyContext } from "./reply-strategy-types";
|
|
8
|
+
import type { AICardInstance } from "../platform/types";
|
|
10
9
|
import { createCardReplyStrategy } from "./reply-strategy-card";
|
|
11
10
|
import { createMarkdownReplyStrategy } from "./reply-strategy-markdown";
|
|
11
|
+
import type { ReplyStrategy, ReplyStrategyContext } from "./reply-strategy-types";
|
|
12
12
|
|
|
13
13
|
// Re-export all types so existing consumers that import from "./reply-strategy"
|
|
14
14
|
// continue to work without changes beyond the ones we explicitly migrate.
|