mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(slack): preserve threads on ephemeral menu replies (#128341)
This commit is contained in:
committed by
GitHub
parent
b3b33d2419
commit
bd7fb1d597
@@ -502,6 +502,8 @@ async function runArgMenuAction(
|
||||
userName?: string;
|
||||
channelId?: string;
|
||||
channelName?: string;
|
||||
message?: { ts: string; thread_ts?: string };
|
||||
container?: { message_ts: string; thread_ts?: string };
|
||||
respond?: ReturnType<typeof vi.fn>;
|
||||
includeRespond?: boolean;
|
||||
},
|
||||
@@ -515,6 +517,8 @@ async function runArgMenuAction(
|
||||
user: { id: params.userId ?? "U1", name: params.userName ?? "Ada" },
|
||||
channel: { id: params.channelId ?? "C1", name: params.channelName ?? "directmessage" },
|
||||
trigger_id: "t1",
|
||||
...(params.message ? { message: params.message } : {}),
|
||||
...(params.container ? { container: params.container } : {}),
|
||||
},
|
||||
};
|
||||
if (includeRespond) {
|
||||
@@ -1223,17 +1227,28 @@ describe("Slack native command argument menus", () => {
|
||||
expect(firstDispatchArg().ctx?.OriginatingTo).toBe("team:TGRID1:user:U1");
|
||||
});
|
||||
|
||||
it("does not apply the response_url call cap to Web API action replies", async () => {
|
||||
it.each([
|
||||
{ name: "top-level", message: undefined, threadTs: undefined },
|
||||
{
|
||||
name: "threaded",
|
||||
message: { ts: "171.222", thread_ts: "170.111" },
|
||||
threadTs: "170.111",
|
||||
},
|
||||
])("does not cap $name Web API action replies", async ({ message, threadTs }) => {
|
||||
mockSixDispatchedReplies();
|
||||
|
||||
await runArgMenuAction(argMenuHandler, {
|
||||
action: {
|
||||
value: encodeValue({ command: "usage", arg: "mode", value: "tokens", userId: "U1" }),
|
||||
},
|
||||
message,
|
||||
includeRespond: false,
|
||||
});
|
||||
|
||||
expect(harness.postEphemeral).toHaveBeenCalledTimes(6);
|
||||
for (const [payload] of harness.postEphemeral.mock.calls) {
|
||||
expect(payload.thread_ts).toBe(threadTs);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps table fallback tokens literal in Web API action replies", async () => {
|
||||
@@ -1280,6 +1295,7 @@ describe("Slack native command argument menus", () => {
|
||||
action: {
|
||||
value: encodeValue({ command: "usage", arg: "mode", value: "tokens", userId: "U1" }),
|
||||
},
|
||||
message: { ts: "171.222", thread_ts: "170.111" },
|
||||
});
|
||||
|
||||
expect(respond).toHaveBeenCalledTimes(5);
|
||||
@@ -1488,17 +1504,37 @@ describe("Slack native command argument menus", () => {
|
||||
expect(trackEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("falls back to postEphemeral with token when respond is unavailable", async () => {
|
||||
await runArgMenuAction(argMenuHandler, {
|
||||
action: { value: "garbage" },
|
||||
includeRespond: false,
|
||||
});
|
||||
it.each([
|
||||
{ name: "a top-level action", message: undefined, container: undefined, threadTs: undefined },
|
||||
{
|
||||
name: "the action container's parent thread",
|
||||
message: { ts: "171.222", thread_ts: "169.999" },
|
||||
container: { message_ts: "171.222", thread_ts: "170.111" },
|
||||
threadTs: "170.111",
|
||||
},
|
||||
{
|
||||
name: "the action message's parent thread",
|
||||
message: { ts: "171.222", thread_ts: "170.111" },
|
||||
container: { message_ts: "171.222" },
|
||||
threadTs: "170.111",
|
||||
},
|
||||
])(
|
||||
"keeps $name when respond falls back to postEphemeral",
|
||||
async ({ message, container, threadTs }) => {
|
||||
await runArgMenuAction(argMenuHandler, {
|
||||
action: { value: "garbage" },
|
||||
message,
|
||||
container,
|
||||
includeRespond: false,
|
||||
});
|
||||
|
||||
const payload = firstCallPayload(harness.postEphemeral, "postEphemeral");
|
||||
expect(payload.token).toBe("bot-token");
|
||||
expect(payload.channel).toBe("C1");
|
||||
expect(payload.user).toBe("U1");
|
||||
});
|
||||
const payload = firstCallPayload(harness.postEphemeral, "postEphemeral");
|
||||
expect(payload.token).toBe("bot-token");
|
||||
expect(payload.channel).toBe("C1");
|
||||
expect(payload.user).toBe("U1");
|
||||
expect(payload.thread_ts).toBe(threadTs);
|
||||
},
|
||||
);
|
||||
|
||||
it("treats malformed percent-encoding as an invalid button", async () => {
|
||||
await runArgMenuAction(argMenuHandler, {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Slack plugin module implements slash behavior.
|
||||
import type {
|
||||
AllMiddlewareArgs,
|
||||
BlockAction,
|
||||
SlackActionMiddlewareArgs,
|
||||
SlackCommandMiddlewareArgs,
|
||||
SlackOptionsMiddlewareArgs,
|
||||
@@ -91,7 +92,7 @@ const SLACK_COMMAND_ARG_ACTION_BLOCKS_MAX = SLACK_MAX_BLOCKS - SLACK_COMMAND_ARG
|
||||
|
||||
type SlackCommandHandlerArgs = SlackCommandMiddlewareArgs &
|
||||
Pick<AllMiddlewareArgs, "context" | "client">;
|
||||
type SlackArgActionHandlerArgs = SlackActionMiddlewareArgs &
|
||||
type SlackArgActionHandlerArgs = SlackActionMiddlewareArgs<BlockAction> &
|
||||
Pick<AllMiddlewareArgs, "context" | "client">;
|
||||
type SlackArgOptionsHandlerArgs = SlackOptionsMiddlewareArgs<"block_suggestion"> &
|
||||
Pick<AllMiddlewareArgs, "context" | "client">;
|
||||
@@ -1177,11 +1178,13 @@ export async function registerSlackMonitorSlashCommands(params: {
|
||||
blocks?: (Block | KnownBlock)[];
|
||||
mrkdwn?: boolean;
|
||||
});
|
||||
const threadTs = body.container?.thread_ts ?? body.message?.thread_ts;
|
||||
await args.client.chat.postEphemeral({
|
||||
token: ctx.botToken,
|
||||
channel: body.channel.id,
|
||||
user: body.user.id,
|
||||
text: payload.text ?? "",
|
||||
...(threadTs ? { thread_ts: threadTs } : {}),
|
||||
...(payload.blocks ? { blocks: payload.blocks } : {}),
|
||||
...(typeof payload.mrkdwn === "boolean" ? { mrkdwn: payload.mrkdwn } : {}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user