Discord: render shared interactive payloads outbound

This commit is contained in:
Vincent Koc
2026-03-15 18:44:31 -07:00
parent 3218efcfd8
commit f56aa4dee7

View File

@@ -1,11 +1,23 @@
import { sendTextMediaPayload } from "../../../src/channels/plugins/outbound/direct-text-media.js";
import {
resolvePayloadMediaUrls,
sendPayloadMediaSequence,
sendTextMediaPayload,
} from "../../../src/channels/plugins/outbound/direct-text-media.js";
import type { ChannelOutboundAdapter } from "../../../src/channels/plugins/types.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import type { OutboundIdentity } from "../../../src/infra/outbound/identity.js";
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
import { resolveInteractiveTextFallback } from "../../../src/interactive/payload.js";
import type { DiscordComponentMessageSpec } from "./components.js";
import { getThreadBindingManager, type ThreadBindingRecord } from "./monitor/thread-bindings.js";
import { normalizeDiscordOutboundTarget } from "./normalize.js";
import { sendMessageDiscord, sendPollDiscord, sendWebhookMessageDiscord } from "./send.js";
import {
sendDiscordComponentMessage,
sendMessageDiscord,
sendPollDiscord,
sendWebhookMessageDiscord,
} from "./send.js";
import { buildDiscordInteractiveComponents } from "./shared-interactive.js";
function resolveDiscordOutboundTarget(params: {
to: string;
@@ -78,8 +90,80 @@ export const discordOutbound: ChannelOutboundAdapter = {
textChunkLimit: 2000,
pollMaxOptions: 10,
resolveTarget: ({ to }) => normalizeDiscordOutboundTarget(to),
sendPayload: async (ctx) =>
await sendTextMediaPayload({ channel: "discord", ctx, adapter: discordOutbound }),
sendPayload: async (ctx) => {
const payload = {
...ctx.payload,
text:
resolveInteractiveTextFallback({
text: ctx.payload.text,
interactive: ctx.payload.interactive,
}) ?? "",
};
const discordData = payload.channelData?.discord as
| { components?: DiscordComponentMessageSpec }
| undefined;
const rawComponentSpec =
discordData?.components ?? buildDiscordInteractiveComponents(payload.interactive);
const componentSpec = rawComponentSpec
? rawComponentSpec.text
? rawComponentSpec
: {
...rawComponentSpec,
text: payload.text?.trim() ? payload.text : undefined,
}
: undefined;
if (!componentSpec) {
return await sendTextMediaPayload({
channel: "discord",
ctx: {
...ctx,
payload,
},
adapter: discordOutbound,
});
}
const send =
resolveOutboundSendDep<typeof sendMessageDiscord>(ctx.deps, "discord") ?? sendMessageDiscord;
const target = resolveDiscordOutboundTarget({ to: ctx.to, threadId: ctx.threadId });
const mediaUrls = resolvePayloadMediaUrls(payload);
if (mediaUrls.length === 0) {
const result = await sendDiscordComponentMessage(target, componentSpec, {
replyTo: ctx.replyToId ?? undefined,
accountId: ctx.accountId ?? undefined,
silent: ctx.silent ?? undefined,
cfg: ctx.cfg,
});
return { channel: "discord", ...result };
}
const lastResult = await sendPayloadMediaSequence({
text: payload.text ?? "",
mediaUrls,
send: async ({ text, mediaUrl, isFirst }) => {
if (isFirst) {
return await sendDiscordComponentMessage(target, componentSpec, {
mediaUrl,
mediaLocalRoots: ctx.mediaLocalRoots,
replyTo: ctx.replyToId ?? undefined,
accountId: ctx.accountId ?? undefined,
silent: ctx.silent ?? undefined,
cfg: ctx.cfg,
});
}
return await send(target, text, {
verbose: false,
mediaUrl,
mediaLocalRoots: ctx.mediaLocalRoots,
replyTo: ctx.replyToId ?? undefined,
accountId: ctx.accountId ?? undefined,
silent: ctx.silent ?? undefined,
cfg: ctx.cfg,
});
},
});
return lastResult
? { channel: "discord", ...lastResult }
: { channel: "discord", messageId: "" };
},
sendText: async ({ cfg, to, text, accountId, deps, replyToId, threadId, identity, silent }) => {
if (!silent) {
const webhookResult = await maybeSendDiscordWebhookText({