fix(discord): send initial message for non-forum thread creation (#18117)

Co-authored-by: Shadow <shadow@openclaw.ai>
This commit is contained in:
zerone0x 2026-02-17 03:48:46 +08:00 committed by GitHub
parent 7c240a2b58
commit 81d2a91a90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 67 additions and 1 deletions

View File

@ -53,6 +53,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Discord: send initial content when creating non-forum threads so `thread-create` content is delivered. (#18117) Thanks @zerone0x.
- Security: replace deprecated SHA-1 sandbox configuration hashing with SHA-256 for deterministic sandbox cache identity and recreation checks. Thanks @kexinoh.
- Security/Logging: redact Telegram bot tokens from error messages and uncaught stack traces to prevent accidental secret leakage into logs. Thanks @aether-ai-agent.
- Sandbox/Security: block dangerous sandbox Docker config (bind mounts, host networking, unconfined seccomp/apparmor) to prevent container escape via config injection. Thanks @aether-ai-agent.

View File

@ -107,6 +107,61 @@ describe("sendMessageDiscord", () => {
);
});
it("sends initial message for non-forum threads with content", async () => {
const { rest, getMock, postMock } = makeDiscordRest();
getMock.mockResolvedValue({ type: ChannelType.GuildText });
postMock.mockResolvedValue({ id: "t1" });
await createThreadDiscord(
"chan1",
{ name: "thread", content: "Hello thread!" },
{ rest, token: "t" },
);
expect(postMock).toHaveBeenCalledTimes(2);
// First call: create thread
expect(postMock).toHaveBeenNthCalledWith(
1,
Routes.threads("chan1"),
expect.objectContaining({
body: expect.objectContaining({ name: "thread", type: ChannelType.PublicThread }),
}),
);
// Second call: send message to thread
expect(postMock).toHaveBeenNthCalledWith(
2,
Routes.channelMessages("t1"),
expect.objectContaining({
body: { content: "Hello thread!" },
}),
);
});
it("sends initial message for message-attached threads with content", async () => {
const { rest, getMock, postMock } = makeDiscordRest();
postMock.mockResolvedValue({ id: "t1" });
await createThreadDiscord(
"chan1",
{ name: "thread", messageId: "m1", content: "Discussion here" },
{ rest, token: "t" },
);
// Should not detect channel type for message-attached threads
expect(getMock).not.toHaveBeenCalled();
expect(postMock).toHaveBeenCalledTimes(2);
// First call: create thread from message
expect(postMock).toHaveBeenNthCalledWith(
1,
Routes.threads("chan1", "m1"),
expect.objectContaining({ body: { name: "thread" } }),
);
// Second call: send message to thread
expect(postMock).toHaveBeenNthCalledWith(
2,
Routes.channelMessages("t1"),
expect.objectContaining({
body: { content: "Discussion here" },
}),
);
});
it("lists active threads by guild", async () => {
const { rest, getMock } = makeDiscordRest();
getMock.mockResolvedValue({ threads: [] });

View File

@ -134,7 +134,17 @@ export async function createThreadDiscord(
const route = payload.messageId
? Routes.threads(channelId, payload.messageId)
: Routes.threads(channelId);
return await rest.post(route, { body });
const thread = (await rest.post(route, { body })) as { id: string };
// For non-forum channels, send the initial message separately after thread creation.
// Forum channels handle this via the `message` field in the request body.
if (!isForumLike && payload.content?.trim()) {
await rest.post(Routes.channelMessages(thread.id), {
body: { content: payload.content },
});
}
return thread;
}
export async function listThreadsDiscord(payload: DiscordThreadList, opts: DiscordReactOpts = {}) {