refactor: deduplicate reply payload handling

This commit is contained in:
Peter Steinberger
2026-03-18 18:14:36 +00:00
parent 152d179302
commit 62edfdffbd
58 changed files with 704 additions and 450 deletions

View File

@@ -5,6 +5,7 @@ import { createReplyPrefixOptions } from "openclaw/plugin-sdk/channel-runtime";
import { createTypingCallbacks } from "openclaw/plugin-sdk/channel-runtime";
import { resolveStorePath, updateLastRoute } from "openclaw/plugin-sdk/config-runtime";
import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/infra-runtime";
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
import { dispatchInboundMessage } from "openclaw/plugin-sdk/reply-runtime";
import { clearHistoryEntriesIfEnabled } from "openclaw/plugin-sdk/reply-runtime";
import { createReplyDispatcherWithTyping } from "openclaw/plugin-sdk/reply-runtime";
@@ -33,7 +34,7 @@ import {
import type { PreparedSlackMessage } from "./types.js";
function hasMedia(payload: ReplyPayload): boolean {
return Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
return resolveSendableOutboundReplyParts(payload).hasMedia;
}
export function isSlackStreamingEnabled(params: {
@@ -250,17 +251,13 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
};
const deliverWithStreaming = async (payload: ReplyPayload): Promise<void> => {
if (
streamFailed ||
hasMedia(payload) ||
readSlackReplyBlocks(payload)?.length ||
!payload.text?.trim()
) {
const reply = resolveSendableOutboundReplyParts(payload);
if (streamFailed || reply.hasMedia || readSlackReplyBlocks(payload)?.length || !reply.hasText) {
await deliverNormally(payload, streamSession?.threadTs);
return;
}
const text = payload.text.trim();
const text = reply.trimmedText;
let plannedThreadTs: string | undefined;
try {
if (!streamSession) {
@@ -311,16 +308,16 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
return;
}
const mediaCount = payload.mediaUrls?.length ?? (payload.mediaUrl ? 1 : 0);
const reply = resolveSendableOutboundReplyParts(payload);
const slackBlocks = readSlackReplyBlocks(payload);
const draftMessageId = draftStream?.messageId();
const draftChannelId = draftStream?.channelId();
const finalText = payload.text ?? "";
const trimmedFinalText = finalText.trim();
const finalText = reply.text;
const trimmedFinalText = reply.trimmedText;
const canFinalizeViaPreviewEdit =
previewStreamingEnabled &&
streamMode !== "status_final" &&
mediaCount === 0 &&
!reply.hasMedia &&
!payload.isError &&
(trimmedFinalText.length > 0 || Boolean(slackBlocks?.length)) &&
typeof draftMessageId === "string" &&
@@ -361,7 +358,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
} catch (err) {
logVerbose(`slack: status_final completion update failed (${String(err)})`);
}
} else if (mediaCount > 0) {
} else if (reply.hasMedia) {
await draftStream?.clear();
hasStreamedMessage = false;
}

View File

@@ -1,5 +1,8 @@
import type { MarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
import { deliverTextOrMediaReply } from "openclaw/plugin-sdk/reply-payload";
import {
deliverTextOrMediaReply,
resolveSendableOutboundReplyParts,
} from "openclaw/plugin-sdk/reply-payload";
import type { ChunkMode } from "openclaw/plugin-sdk/reply-runtime";
import { chunkMarkdownTextWithMode } from "openclaw/plugin-sdk/reply-runtime";
import { createReplyReferencePlanner } from "openclaw/plugin-sdk/reply-runtime";
@@ -38,15 +41,14 @@ export async function deliverReplies(params: {
// must not force threading.
const inlineReplyToId = params.replyToMode === "off" ? undefined : payload.replyToId;
const threadTs = inlineReplyToId ?? params.replyThreadTs;
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
const text = payload.text ?? "";
const reply = resolveSendableOutboundReplyParts(payload);
const slackBlocks = readSlackReplyBlocks(payload);
if (!text && mediaList.length === 0 && !slackBlocks?.length) {
if (!reply.hasContent && !slackBlocks?.length) {
continue;
}
if (mediaList.length === 0 && slackBlocks?.length) {
const trimmed = text.trim();
if (!reply.hasMedia && slackBlocks?.length) {
const trimmed = reply.trimmedText;
if (!trimmed && !slackBlocks?.length) {
continue;
}
@@ -66,17 +68,16 @@ export async function deliverReplies(params: {
const delivered = await deliverTextOrMediaReply({
payload,
text,
chunkText:
mediaList.length === 0
? (value) => {
const trimmed = value.trim();
if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) {
return [];
}
return [trimmed];
text: reply.text,
chunkText: !reply.hasMedia
? (value) => {
const trimmed = value.trim();
if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) {
return [];
}
: undefined,
return [trimmed];
}
: undefined,
sendText: async (trimmed) => {
await sendMessageSlack(params.target, trimmed, {
token: params.token,
@@ -189,12 +190,12 @@ export async function deliverSlackSlashReplies(params: {
const messages: string[] = [];
const chunkLimit = Math.min(params.textLimit, 4000);
for (const payload of params.replies) {
const textRaw = payload.text?.trim() ?? "";
const text = textRaw && !isSilentReplyText(textRaw, SILENT_REPLY_TOKEN) ? textRaw : undefined;
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
const combined = [text ?? "", ...mediaList.map((url) => url.trim()).filter(Boolean)]
.filter(Boolean)
.join("\n");
const reply = resolveSendableOutboundReplyParts(payload);
const text =
reply.hasText && !isSilentReplyText(reply.trimmedText, SILENT_REPLY_TOKEN)
? reply.trimmedText
: undefined;
const combined = [text ?? "", ...reply.mediaUrls].filter(Boolean).join("\n");
if (!combined) {
continue;
}