mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:10:44 +00:00
fix(discord): send component-only native replies
This commit is contained in:
@@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Discord/voice: merge configured media-understanding providers such as Deepgram into partial active provider registries, so follow-up voice turns keep transcribing after another media plugin is already active. Fixes #65687. Thanks @OneMintJulep.
|
||||
- WhatsApp: stage `qrcode` through root mirrored runtime dependencies so packaged QR pairing can render from staged plugin-runtime-deps installs. Fixes #75394. Thanks @FelipeX2001.
|
||||
- Discord/voice: apply per-channel Discord `systemPrompt` overrides to voice transcript turns by forwarding the trusted channel prompt through the voice agent run. Fixes #47095. Thanks @qearlyao.
|
||||
- Discord/native commands: send component-only interaction replies from slash command and status handlers instead of treating renderable Discord components as an empty response. Thanks @vincentkoc.
|
||||
- Gateway/agent: reject strict `openclaw agent --deliver` requests with missing delivery targets before starting the agent run, so users do not wait for a completed turn that cannot send anywhere. Thanks @vincentkoc.
|
||||
- Setup/import: honor non-interactive `--import-from` onboarding flags by running the migration import path instead of silently completing normal setup without importing anything. Thanks @vincentkoc.
|
||||
- Discord/voice: run voice-channel turns under a voice-output policy that hides the agent `tts` tool and asks for spoken reply text, so `/vc join` sessions synthesize and play agent replies instead of ending with `NO_REPLY`. Fixes #61536. Thanks @aounakram.
|
||||
|
||||
68
extensions/discord/src/monitor/native-command-reply.test.ts
Normal file
68
extensions/discord/src/monitor/native-command-reply.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { Container, TextDisplay } from "../internal/discord.js";
|
||||
import {
|
||||
deliverDiscordInteractionReply,
|
||||
hasRenderableReplyPayload,
|
||||
} from "./native-command-reply.js";
|
||||
|
||||
function createInteraction() {
|
||||
return {
|
||||
reply: vi.fn().mockResolvedValue({ ok: true }),
|
||||
followUp: vi.fn().mockResolvedValue({ ok: true }),
|
||||
};
|
||||
}
|
||||
|
||||
describe("deliverDiscordInteractionReply", () => {
|
||||
it("sends component-only native command replies as follow-ups", async () => {
|
||||
const interaction = createInteraction();
|
||||
const components = [new Container([new TextDisplay("Pick a model")])];
|
||||
const payload = {
|
||||
channelData: {
|
||||
discord: {
|
||||
components,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(hasRenderableReplyPayload(payload)).toBe(true);
|
||||
|
||||
await deliverDiscordInteractionReply({
|
||||
interaction: interaction as never,
|
||||
payload,
|
||||
textLimit: 2000,
|
||||
preferFollowUp: true,
|
||||
responseEphemeral: true,
|
||||
chunkMode: "length",
|
||||
});
|
||||
|
||||
expect(interaction.followUp).toHaveBeenCalledWith({
|
||||
components,
|
||||
ephemeral: true,
|
||||
});
|
||||
expect(interaction.reply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends component-only native command replies through the initial reply when not deferred", async () => {
|
||||
const interaction = createInteraction();
|
||||
const components = [new Container([new TextDisplay("Choose an action")])];
|
||||
|
||||
await deliverDiscordInteractionReply({
|
||||
interaction: interaction as never,
|
||||
payload: {
|
||||
channelData: {
|
||||
discord: {
|
||||
components,
|
||||
},
|
||||
},
|
||||
},
|
||||
textLimit: 2000,
|
||||
preferFollowUp: false,
|
||||
chunkMode: "length",
|
||||
});
|
||||
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
components,
|
||||
});
|
||||
expect(interaction.followUp).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -89,10 +89,11 @@ export async function deliverDiscordInteractionReply(params: {
|
||||
files?: { name: string; data: Buffer }[],
|
||||
components?: TopLevelComponents[],
|
||||
) => {
|
||||
const contentPayload = content ? { content } : {};
|
||||
const payload =
|
||||
files && files.length > 0
|
||||
? {
|
||||
content,
|
||||
...contentPayload,
|
||||
...(components ? { components } : {}),
|
||||
...(params.responseEphemeral !== undefined
|
||||
? { ephemeral: params.responseEphemeral }
|
||||
@@ -106,7 +107,7 @@ export async function deliverDiscordInteractionReply(params: {
|
||||
}),
|
||||
}
|
||||
: {
|
||||
content,
|
||||
...contentPayload,
|
||||
...(components ? { components } : {}),
|
||||
...(params.responseEphemeral !== undefined
|
||||
? { ephemeral: params.responseEphemeral }
|
||||
@@ -159,7 +160,7 @@ export async function deliverDiscordInteractionReply(params: {
|
||||
if (!reply.hasText && !firstMessageComponents) {
|
||||
return;
|
||||
}
|
||||
const chunks =
|
||||
let chunks =
|
||||
reply.text || firstMessageComponents
|
||||
? resolveTextChunksWithFallback(
|
||||
reply.text,
|
||||
@@ -170,6 +171,9 @@ export async function deliverDiscordInteractionReply(params: {
|
||||
}),
|
||||
)
|
||||
: [];
|
||||
if (chunks.length === 0 && firstMessageComponents) {
|
||||
chunks = [""];
|
||||
}
|
||||
for (const chunk of chunks) {
|
||||
if (!chunk.trim() && !firstMessageComponents) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user