mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
fix: harden slack command menus
This commit is contained in:
@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- CLI/status: resolve read-only channel setup runtime fallback from the packaged OpenClaw dist root, so `status --all`, `status --deep`, channel, and doctor paths do not crash when an external channel plugin needs setup metadata. Fixes #74693. Thanks @giangthb.
|
||||
- Google Meet: block managed Chrome intro/test speech until browser health proves the participant is in-call, and expose `speechReady` diagnostics so login, admission, permission, and audio-bridge blockers no longer look like successful speech. Refs #72478. Thanks @DougButdorf.
|
||||
- Slack/commands: keep native command argument menus on select controls for encoded choice values up to Slack's option limit and truncate fallback button labels to Slack's button-text limit, so long valid choices no longer render invalid Slack blocks. Thanks @slackapi.
|
||||
- CLI/update: scope packaged Node compile caches by OpenClaw version and install metadata, so global installs no longer reuse stale compiled chunks after package updates. Thanks @pashpashpash.
|
||||
- Channels/Voice call: keep pre-auth webhook in-flight limiting active when socket remote address metadata is missing, so slow-body requests from stripped-IP proxy paths still share the fallback bucket. (#74453) Thanks @davidangularme.
|
||||
- Plugin SDK/testing: lazy-load TypeScript from the plugin test-contract runtime and add release checks for critical SDK contract entrypoint imports and bundle size, so published packages fail preflight before shipping ESM-incompatible or oversized contract helpers. Thanks @vincentkoc.
|
||||
|
||||
@@ -7,6 +7,7 @@ vi.mock("./slash-commands.runtime.js", () => {
|
||||
const reportCompactCommand = { key: "reportcompact", nativeName: "reportcompact" };
|
||||
const reportExternalCommand = { key: "reportexternal", nativeName: "reportexternal" };
|
||||
const reportLongCommand = { key: "reportlong", nativeName: "reportlong" };
|
||||
const reportLongButtonCommand = { key: "reportlongbutton", nativeName: "reportlongbutton" };
|
||||
const unsafeConfirmCommand = { key: "unsafeconfirm", nativeName: "unsafeconfirm" };
|
||||
const statusAliasCommand = { key: "status", nativeName: "status" };
|
||||
const periodArg = { name: "period", description: "period" };
|
||||
@@ -71,6 +72,9 @@ vi.mock("./slash-commands.runtime.js", () => {
|
||||
if (normalized === "reportlong") {
|
||||
return reportLongCommand;
|
||||
}
|
||||
if (normalized === "reportlongbutton") {
|
||||
return reportLongButtonCommand;
|
||||
}
|
||||
if (normalized === "unsafeconfirm") {
|
||||
return unsafeConfirmCommand;
|
||||
}
|
||||
@@ -110,6 +114,12 @@ vi.mock("./slash-commands.runtime.js", () => {
|
||||
acceptsArgs: true,
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: "reportlongbutton",
|
||||
description: "ReportLongButton",
|
||||
acceptsArgs: true,
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: "unsafeconfirm",
|
||||
description: "UnsafeConfirm",
|
||||
@@ -140,6 +150,14 @@ vi.mock("./slash-commands.runtime.js", () => {
|
||||
{ value: "x".repeat(90), label: "long" },
|
||||
]);
|
||||
}
|
||||
if (params.command?.key === "reportlongbutton") {
|
||||
return resolvePeriodMenu(params, [
|
||||
{
|
||||
value: "x".repeat(170),
|
||||
label: "Long button label ".repeat(8),
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (params.command?.key === "reportcompact") {
|
||||
return resolvePeriodMenu(params, baseReportPeriodChoices);
|
||||
}
|
||||
@@ -408,6 +426,7 @@ describe("Slack native command argument menus", () => {
|
||||
let reportCompactHandler: (args: unknown) => Promise<void>;
|
||||
let reportExternalHandler: (args: unknown) => Promise<void>;
|
||||
let reportLongHandler: (args: unknown) => Promise<void>;
|
||||
let reportLongButtonHandler: (args: unknown) => Promise<void>;
|
||||
let unsafeConfirmHandler: (args: unknown) => Promise<void>;
|
||||
let agentStatusHandler: (args: unknown) => Promise<void>;
|
||||
let argMenuHandler: (args: unknown) => Promise<void>;
|
||||
@@ -421,6 +440,11 @@ describe("Slack native command argument menus", () => {
|
||||
reportCompactHandler = requireHandler(harness.commands, "/reportcompact", "/reportcompact");
|
||||
reportExternalHandler = requireHandler(harness.commands, "/reportexternal", "/reportexternal");
|
||||
reportLongHandler = requireHandler(harness.commands, "/reportlong", "/reportlong");
|
||||
reportLongButtonHandler = requireHandler(
|
||||
harness.commands,
|
||||
"/reportlongbutton",
|
||||
"/reportlongbutton",
|
||||
);
|
||||
unsafeConfirmHandler = requireHandler(harness.commands, "/unsafeconfirm", "/unsafeconfirm");
|
||||
agentStatusHandler = requireHandler(harness.commands, "/agentstatus", "/agentstatus");
|
||||
argMenuHandler = requireHandler(harness.actions, /^openclaw_cmdarg/, "arg-menu action");
|
||||
@@ -539,9 +563,20 @@ describe("Slack native command argument menus", () => {
|
||||
expect(element?.confirm).toBeTruthy();
|
||||
});
|
||||
|
||||
it("falls back to buttons when static_select value limit would be exceeded", async () => {
|
||||
it("uses static_select when encoded values fit Slack option limits", async () => {
|
||||
const firstElement = await getFirstActionElementFromCommand(reportLongHandler);
|
||||
expect(firstElement?.type).toBe("static_select");
|
||||
expect(firstElement?.confirm).toBeTruthy();
|
||||
});
|
||||
|
||||
it("truncates button labels when static_select value limit would be exceeded", async () => {
|
||||
const firstElement = (await getFirstActionElementFromCommand(reportLongButtonHandler)) as
|
||||
| { type?: string; text?: { text?: string }; value?: string; confirm?: unknown }
|
||||
| undefined;
|
||||
expect(firstElement?.type).toBe("button");
|
||||
expect(firstElement?.text?.text).toHaveLength(75);
|
||||
expect(firstElement?.text?.text?.endsWith("…")).toBe(true);
|
||||
expect(firstElement?.value?.length).toBeGreaterThan(150);
|
||||
expect(firstElement?.confirm).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ const SLACK_COMMAND_ARG_BUTTON_ROW_SIZE = 5;
|
||||
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_COMMAND_ARG_SELECT_OPTION_TEXT_MAX = 75;
|
||||
const SLACK_COMMAND_ARG_SELECT_OPTION_VALUE_MAX = 150;
|
||||
const SLACK_COMMAND_ARG_BUTTON_TEXT_MAX = 75;
|
||||
const SLACK_HEADER_TEXT_MAX = 150;
|
||||
let slashCommandsRuntimePromise: Promise<typeof import("./slash-commands.runtime.js")> | null =
|
||||
null;
|
||||
@@ -217,7 +219,10 @@ function parseSlackCommandArgValue(raw?: string | null): {
|
||||
|
||||
function buildSlackArgMenuOptions(choices: EncodedMenuChoice[]) {
|
||||
return choices.map((choice) => ({
|
||||
text: { type: "plain_text", text: choice.label.slice(0, 75) },
|
||||
text: {
|
||||
type: "plain_text",
|
||||
text: truncateSlackText(choice.label, SLACK_COMMAND_ARG_SELECT_OPTION_TEXT_MAX),
|
||||
},
|
||||
value: choice.value,
|
||||
}));
|
||||
}
|
||||
@@ -293,7 +298,10 @@ function buildSlackCommandArgMenuBlocks(params: {
|
||||
elements: choices.map((choice, colIndex) => ({
|
||||
type: "button",
|
||||
action_id: `${SLACK_COMMAND_ARG_ACTION_ID}_${rowIndex}_${colIndex}`,
|
||||
text: { type: "plain_text", text: choice.label },
|
||||
text: {
|
||||
type: "plain_text",
|
||||
text: truncateSlackText(choice.label, SLACK_COMMAND_ARG_BUTTON_TEXT_MAX),
|
||||
},
|
||||
value: choice.value,
|
||||
confirm: buildSlackArgMenuConfirm({ command: params.command, arg: params.arg }),
|
||||
})),
|
||||
|
||||
Reference in New Issue
Block a user