mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-26 16:06:16 +00:00
Slack: expand advanced modal controls payloads and confirms
This commit is contained in:
@@ -504,6 +504,9 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
|
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
|
||||||
actionType: string;
|
actionType: string;
|
||||||
selectedValues?: string[];
|
selectedValues?: string[];
|
||||||
|
selectedUsers?: string[];
|
||||||
|
selectedChannels?: string[];
|
||||||
|
selectedConversations?: string[];
|
||||||
selectedLabels?: string[];
|
selectedLabels?: string[];
|
||||||
selectedDate?: string;
|
selectedDate?: string;
|
||||||
selectedTime?: string;
|
selectedTime?: string;
|
||||||
@@ -520,6 +523,9 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
"G777",
|
"G777",
|
||||||
"G888",
|
"G888",
|
||||||
]);
|
]);
|
||||||
|
expect(payload.selectedUsers).toEqual(["U777", "U888"]);
|
||||||
|
expect(payload.selectedChannels).toEqual(["C777", "C888"]);
|
||||||
|
expect(payload.selectedConversations).toEqual(["G777", "G888"]);
|
||||||
expect(payload.selectedLabels).toEqual(["Alpha", "Beta"]);
|
expect(payload.selectedLabels).toEqual(["Alpha", "Beta"]);
|
||||||
expect(payload.selectedDate).toBe("2026-02-16");
|
expect(payload.selectedDate).toBe("2026-02-16");
|
||||||
expect(payload.selectedTime).toBe("14:30");
|
expect(payload.selectedTime).toBe("14:30");
|
||||||
@@ -719,6 +725,9 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
actionId: string;
|
actionId: string;
|
||||||
inputKind?: string;
|
inputKind?: string;
|
||||||
selectedValues?: string[];
|
selectedValues?: string[];
|
||||||
|
selectedUsers?: string[];
|
||||||
|
selectedChannels?: string[];
|
||||||
|
selectedConversations?: string[];
|
||||||
selectedLabels?: string[];
|
selectedLabels?: string[];
|
||||||
selectedDate?: string;
|
selectedDate?: string;
|
||||||
selectedTime?: string;
|
selectedTime?: string;
|
||||||
@@ -736,9 +745,21 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
selectedValues: ["prod"],
|
selectedValues: ["prod"],
|
||||||
selectedLabels: ["Production"],
|
selectedLabels: ["Production"],
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({ actionId: "assignee_select", selectedValues: ["U900"] }),
|
expect.objectContaining({
|
||||||
expect.objectContaining({ actionId: "channel_select", selectedValues: ["C900"] }),
|
actionId: "assignee_select",
|
||||||
expect.objectContaining({ actionId: "convo_select", selectedValues: ["G900"] }),
|
selectedValues: ["U900"],
|
||||||
|
selectedUsers: ["U900"],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
actionId: "channel_select",
|
||||||
|
selectedValues: ["C900"],
|
||||||
|
selectedChannels: ["C900"],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
actionId: "convo_select",
|
||||||
|
selectedValues: ["G900"],
|
||||||
|
selectedConversations: ["G900"],
|
||||||
|
}),
|
||||||
expect.objectContaining({ actionId: "date_select", selectedDate: "2026-02-16" }),
|
expect.objectContaining({ actionId: "date_select", selectedDate: "2026-02-16" }),
|
||||||
expect.objectContaining({ actionId: "time_select", selectedTime: "12:45" }),
|
expect.objectContaining({ actionId: "time_select", selectedTime: "12:45" }),
|
||||||
expect.objectContaining({ actionId: "datetime_select", selectedDateTime: 1_771_632_300 }),
|
expect.objectContaining({ actionId: "datetime_select", selectedDateTime: 1_771_632_300 }),
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ type InteractionSummary = {
|
|||||||
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
|
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
|
||||||
value?: string;
|
value?: string;
|
||||||
selectedValues?: string[];
|
selectedValues?: string[];
|
||||||
|
selectedUsers?: string[];
|
||||||
|
selectedChannels?: string[];
|
||||||
|
selectedConversations?: string[];
|
||||||
selectedLabels?: string[];
|
selectedLabels?: string[];
|
||||||
selectedDate?: string;
|
selectedDate?: string;
|
||||||
selectedTime?: string;
|
selectedTime?: string;
|
||||||
@@ -51,6 +54,9 @@ type ModalInputSummary = {
|
|||||||
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
|
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
|
||||||
value?: string;
|
value?: string;
|
||||||
selectedValues?: string[];
|
selectedValues?: string[];
|
||||||
|
selectedUsers?: string[];
|
||||||
|
selectedChannels?: string[];
|
||||||
|
selectedConversations?: string[];
|
||||||
selectedLabels?: string[];
|
selectedLabels?: string[];
|
||||||
selectedDate?: string;
|
selectedDate?: string;
|
||||||
selectedTime?: string;
|
selectedTime?: string;
|
||||||
@@ -121,15 +127,24 @@ function summarizeAction(
|
|||||||
rich_text_value?: unknown;
|
rich_text_value?: unknown;
|
||||||
};
|
};
|
||||||
const actionType = typed.type;
|
const actionType = typed.type;
|
||||||
|
const selectedUsers = uniqueNonEmptyStrings([
|
||||||
|
...(typed.selected_user ? [typed.selected_user] : []),
|
||||||
|
...(Array.isArray(typed.selected_users) ? typed.selected_users : []),
|
||||||
|
]);
|
||||||
|
const selectedChannels = uniqueNonEmptyStrings([
|
||||||
|
...(typed.selected_channel ? [typed.selected_channel] : []),
|
||||||
|
...(Array.isArray(typed.selected_channels) ? typed.selected_channels : []),
|
||||||
|
]);
|
||||||
|
const selectedConversations = uniqueNonEmptyStrings([
|
||||||
|
...(typed.selected_conversation ? [typed.selected_conversation] : []),
|
||||||
|
...(Array.isArray(typed.selected_conversations) ? typed.selected_conversations : []),
|
||||||
|
]);
|
||||||
const selectedValues = uniqueNonEmptyStrings([
|
const selectedValues = uniqueNonEmptyStrings([
|
||||||
...(typed.selected_option?.value ? [typed.selected_option.value] : []),
|
...(typed.selected_option?.value ? [typed.selected_option.value] : []),
|
||||||
...(readOptionValues(typed.selected_options) ?? []),
|
...(readOptionValues(typed.selected_options) ?? []),
|
||||||
...(typed.selected_user ? [typed.selected_user] : []),
|
...selectedUsers,
|
||||||
...(Array.isArray(typed.selected_users) ? typed.selected_users : []),
|
...selectedChannels,
|
||||||
...(typed.selected_channel ? [typed.selected_channel] : []),
|
...selectedConversations,
|
||||||
...(Array.isArray(typed.selected_channels) ? typed.selected_channels : []),
|
|
||||||
...(typed.selected_conversation ? [typed.selected_conversation] : []),
|
|
||||||
...(Array.isArray(typed.selected_conversations) ? typed.selected_conversations : []),
|
|
||||||
]);
|
]);
|
||||||
const selectedLabels = uniqueNonEmptyStrings([
|
const selectedLabels = uniqueNonEmptyStrings([
|
||||||
...(typed.selected_option?.text?.text ? [typed.selected_option.text.text] : []),
|
...(typed.selected_option?.text?.text ? [typed.selected_option.text.text] : []),
|
||||||
@@ -169,6 +184,9 @@ function summarizeAction(
|
|||||||
inputKind,
|
inputKind,
|
||||||
value: typed.value,
|
value: typed.value,
|
||||||
selectedValues: selectedValues.length > 0 ? selectedValues : undefined,
|
selectedValues: selectedValues.length > 0 ? selectedValues : undefined,
|
||||||
|
selectedUsers: selectedUsers.length > 0 ? selectedUsers : undefined,
|
||||||
|
selectedChannels: selectedChannels.length > 0 ? selectedChannels : undefined,
|
||||||
|
selectedConversations: selectedConversations.length > 0 ? selectedConversations : undefined,
|
||||||
selectedLabels: selectedLabels.length > 0 ? selectedLabels : undefined,
|
selectedLabels: selectedLabels.length > 0 ? selectedLabels : undefined,
|
||||||
selectedDate: typed.selected_date,
|
selectedDate: typed.selected_date,
|
||||||
selectedTime: typed.selected_time,
|
selectedTime: typed.selected_time,
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ function encodeValue(parts: { command: string; arg: string; value: string; userI
|
|||||||
|
|
||||||
function findFirstActionsBlock(payload: { blocks?: Array<{ type: string }> }) {
|
function findFirstActionsBlock(payload: { blocks?: Array<{ type: string }> }) {
|
||||||
return payload.blocks?.find((block) => block.type === "actions") as
|
return payload.blocks?.find((block) => block.type === "actions") as
|
||||||
| { type: string; elements?: Array<{ type?: string; action_id?: string }> }
|
| { type: string; elements?: Array<{ type?: string; action_id?: string; confirm?: unknown }> }
|
||||||
| undefined;
|
| undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,6 +293,7 @@ describe("Slack native command argument menus", () => {
|
|||||||
const actions = findFirstActionsBlock(payload);
|
const actions = findFirstActionsBlock(payload);
|
||||||
const elementType = actions?.elements?.[0]?.type;
|
const elementType = actions?.elements?.[0]?.type;
|
||||||
expect(elementType).toBe("button");
|
expect(elementType).toBe("button");
|
||||||
|
expect(actions?.elements?.[0]?.confirm).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows a static_select menu when choices exceed button row size", async () => {
|
it("shows a static_select menu when choices exceed button row size", async () => {
|
||||||
@@ -321,6 +322,7 @@ describe("Slack native command argument menus", () => {
|
|||||||
const element = actions?.elements?.[0];
|
const element = actions?.elements?.[0];
|
||||||
expect(element?.type).toBe("static_select");
|
expect(element?.type).toBe("static_select");
|
||||||
expect(element?.action_id).toBe("openclaw_cmdarg");
|
expect(element?.action_id).toBe("openclaw_cmdarg");
|
||||||
|
expect(element?.confirm).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to buttons when static_select value limit would be exceeded", async () => {
|
it("falls back to buttons when static_select value limit would be exceeded", async () => {
|
||||||
@@ -345,6 +347,7 @@ describe("Slack native command argument menus", () => {
|
|||||||
const actions = findFirstActionsBlock(payload);
|
const actions = findFirstActionsBlock(payload);
|
||||||
const firstElement = actions?.elements?.[0];
|
const firstElement = actions?.elements?.[0];
|
||||||
expect(firstElement?.type).toBe("button");
|
expect(firstElement?.type).toBe("button");
|
||||||
|
expect(firstElement?.confirm).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows an overflow menu when choices fit compact range", async () => {
|
it("shows an overflow menu when choices fit compact range", async () => {
|
||||||
@@ -370,6 +373,7 @@ describe("Slack native command argument menus", () => {
|
|||||||
const element = actions?.elements?.[0];
|
const element = actions?.elements?.[0];
|
||||||
expect(element?.type).toBe("overflow");
|
expect(element?.type).toBe("overflow");
|
||||||
expect(element?.action_id).toBe("openclaw_cmdarg");
|
expect(element?.action_id).toBe("openclaw_cmdarg");
|
||||||
|
expect(element?.confirm).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("dispatches the command when a menu button is clicked", async () => {
|
it("dispatches the command when a menu button is clicked", async () => {
|
||||||
|
|||||||
@@ -47,6 +47,18 @@ function truncatePlainText(value: string, max: number): string {
|
|||||||
return `${trimmed.slice(0, max - 1)}…`;
|
return `${trimmed.slice(0, max - 1)}…`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSlackArgMenuConfirm(params: { command: string; arg: string }) {
|
||||||
|
return {
|
||||||
|
title: { type: "plain_text", text: "Confirm selection" },
|
||||||
|
text: {
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: `Run */${params.command}* with *${params.arg}* set to this value?`,
|
||||||
|
},
|
||||||
|
confirm: { type: "plain_text", text: "Run command" },
|
||||||
|
deny: { type: "plain_text", text: "Cancel" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
type CommandsRegistry = typeof import("../../auto-reply/commands-registry.js");
|
type CommandsRegistry = typeof import("../../auto-reply/commands-registry.js");
|
||||||
let commandsRegistry: CommandsRegistry | undefined;
|
let commandsRegistry: CommandsRegistry | undefined;
|
||||||
async function getCommandsRegistry(): Promise<CommandsRegistry> {
|
async function getCommandsRegistry(): Promise<CommandsRegistry> {
|
||||||
@@ -141,6 +153,7 @@ function buildSlackCommandArgMenuBlocks(params: {
|
|||||||
{
|
{
|
||||||
type: "overflow",
|
type: "overflow",
|
||||||
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
||||||
|
confirm: buildSlackArgMenuConfirm({ command: params.command, arg: params.arg }),
|
||||||
options: encodedChoices.map((choice) => ({
|
options: encodedChoices.map((choice) => ({
|
||||||
text: { type: "plain_text", text: choice.label.slice(0, 75) },
|
text: { type: "plain_text", text: choice.label.slice(0, 75) },
|
||||||
value: choice.value,
|
value: choice.value,
|
||||||
@@ -157,6 +170,7 @@ function buildSlackCommandArgMenuBlocks(params: {
|
|||||||
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
||||||
text: { type: "plain_text", text: choice.label },
|
text: { type: "plain_text", text: choice.label },
|
||||||
value: choice.value,
|
value: choice.value,
|
||||||
|
confirm: buildSlackArgMenuConfirm({ command: params.command, arg: params.arg }),
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
: chunkItems(encodedChoices, SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX).map((choices, index) => ({
|
: chunkItems(encodedChoices, SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX).map((choices, index) => ({
|
||||||
@@ -165,6 +179,7 @@ function buildSlackCommandArgMenuBlocks(params: {
|
|||||||
{
|
{
|
||||||
type: "static_select",
|
type: "static_select",
|
||||||
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
||||||
|
confirm: buildSlackArgMenuConfirm({ command: params.command, arg: params.arg }),
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: "plain_text",
|
type: "plain_text",
|
||||||
text: index === 0 ? `Choose ${params.arg}` : `Choose ${params.arg} (${index + 1})`,
|
text: index === 0 ? `Choose ${params.arg}` : `Choose ${params.arg} (${index + 1})`,
|
||||||
|
|||||||
Reference in New Issue
Block a user