Slack: add header and context blocks to arg menus

This commit is contained in:
Colin
2026-02-16 13:26:59 -05:00
committed by Peter Steinberger
parent 7c5529a153
commit 5f9a04604e
2 changed files with 50 additions and 18 deletions

View File

@@ -167,6 +167,12 @@ function encodeValue(parts: { command: string; arg: string; value: string; userI
].join("|");
}
function findFirstActionsBlock(payload: { blocks?: Array<{ type: string }> }) {
return payload.blocks?.find((block) => block.type === "actions") as
| { type: string; elements?: Array<{ type?: string; action_id?: string }> }
| undefined;
}
function createArgMenusHarness() {
const commands = new Map<string, (args: unknown) => Promise<void>>();
const actions = new Map<string, (args: unknown) => Promise<void>>();
@@ -281,10 +287,11 @@ describe("Slack native command argument menus", () => {
expect(respond).toHaveBeenCalledTimes(1);
const payload = respond.mock.calls[0]?.[0] as { blocks?: Array<{ type: string }> };
expect(payload.blocks?.[0]?.type).toBe("section");
expect(payload.blocks?.[1]?.type).toBe("actions");
const elementType = (payload.blocks?.[1] as { elements?: Array<{ type?: string }> } | undefined)
?.elements?.[0]?.type;
expect(payload.blocks?.[0]?.type).toBe("header");
expect(payload.blocks?.[1]?.type).toBe("section");
expect(payload.blocks?.[2]?.type).toBe("context");
const actions = findFirstActionsBlock(payload);
const elementType = actions?.elements?.[0]?.type;
expect(elementType).toBe("button");
});
@@ -307,11 +314,11 @@ describe("Slack native command argument menus", () => {
expect(respond).toHaveBeenCalledTimes(1);
const payload = respond.mock.calls[0]?.[0] as { blocks?: Array<{ type: string }> };
expect(payload.blocks?.[0]?.type).toBe("section");
expect(payload.blocks?.[1]?.type).toBe("actions");
const element = (
payload.blocks?.[1] as { elements?: Array<{ type?: string; action_id?: string }> } | undefined
)?.elements?.[0];
expect(payload.blocks?.[0]?.type).toBe("header");
expect(payload.blocks?.[1]?.type).toBe("section");
expect(payload.blocks?.[2]?.type).toBe("context");
const actions = findFirstActionsBlock(payload);
const element = actions?.elements?.[0];
expect(element?.type).toBe("static_select");
expect(element?.action_id).toBe("openclaw_cmdarg");
});
@@ -335,10 +342,8 @@ describe("Slack native command argument menus", () => {
expect(respond).toHaveBeenCalledTimes(1);
const payload = respond.mock.calls[0]?.[0] as { blocks?: Array<{ type: string }> };
expect(payload.blocks?.[1]?.type).toBe("actions");
const firstElement = (
payload.blocks?.[1] as { elements?: Array<{ type?: string }> } | undefined
)?.elements?.[0];
const actions = findFirstActionsBlock(payload);
const firstElement = actions?.elements?.[0];
expect(firstElement?.type).toBe("button");
});
@@ -361,10 +366,8 @@ describe("Slack native command argument menus", () => {
expect(respond).toHaveBeenCalledTimes(1);
const payload = respond.mock.calls[0]?.[0] as { blocks?: Array<{ type: string }> };
expect(payload.blocks?.[1]?.type).toBe("actions");
const element = (
payload.blocks?.[1] as { elements?: Array<{ type?: string; action_id?: string }> } | undefined
)?.elements?.[0];
const actions = findFirstActionsBlock(payload);
const element = actions?.elements?.[0];
expect(element?.type).toBe("overflow");
expect(element?.action_id).toBe("openclaw_cmdarg");
});

View File

@@ -34,6 +34,18 @@ const SLACK_COMMAND_ARG_OVERFLOW_MIN = 3;
const SLACK_COMMAND_ARG_OVERFLOW_MAX = 5;
const SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX = 100;
const SLACK_COMMAND_ARG_SELECT_OPTION_VALUE_MAX = 75;
const SLACK_HEADER_TEXT_MAX = 150;
function truncatePlainText(value: string, max: number): string {
const trimmed = value.trim();
if (trimmed.length <= max) {
return trimmed;
}
if (max <= 1) {
return trimmed.slice(0, max);
}
return `${trimmed.slice(0, max - 1)}`;
}
type CommandsRegistry = typeof import("../../auto-reply/commands-registry.js");
let commandsRegistry: CommandsRegistry | undefined;
@@ -164,10 +176,27 @@ function buildSlackCommandArgMenuBlocks(params: {
},
],
}));
const headerText = truncatePlainText(
`/${params.command}: choose ${params.arg}`,
SLACK_HEADER_TEXT_MAX,
);
const sectionText = truncatePlainText(params.title, 3000);
const contextText = truncatePlainText(
`Select one option to continue /${params.command} (${params.arg})`,
3000,
);
return [
{
type: "header",
text: { type: "plain_text", text: headerText },
},
{
type: "section",
text: { type: "mrkdwn", text: params.title },
text: { type: "mrkdwn", text: sectionText },
},
{
type: "context",
elements: [{ type: "mrkdwn", text: contextText }],
},
...rows,
];