From 8bc80fad477e2700cb5cac909ca74b22ee7ce0d0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 27 Feb 2026 19:08:59 +0000 Subject: [PATCH] fix(slack): land #29032 /agentstatus alias from @maloqab Land contributor PR #29032 by @maloqab with Slack native alias docs, integration tests, and changelog entry. Co-authored-by: maloqab --- CHANGELOG.md | 1 + docs/channels/slack.md | 3 ++- docs/tools/slash-commands.md | 1 + src/auto-reply/commands-registry.test.ts | 11 +++++++++++ src/auto-reply/commands-registry.ts | 5 +++++ src/slack/monitor/slash.test.ts | 17 +++++++++++++++++ 6 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a57a510e139..d675d9789f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai - CLI/Install: add an npm-link fallback to fix CLI startup `Permission denied` failures (`exit 127`) on affected installs. (#17151) Thanks @sskyu and @vincentkoc. - Agents/Ollama: demote empty-discovery logging from `warn` to `debug` to reduce noisy warnings in normal edge-case discovery flows. (#26379) Thanks @byungsker. - Install/npm: fix npm global install deprecation warnings. (#28318) Thanks @vincentkoc. +- Slack/Native commands: register Slack native status as `/agentstatus` (Slack-reserved `/status`) so manifest slash command registration stays valid while text `/status` still works. Landed from contributor PR #29032 by @maloqab. Thanks @maloqab. - Android/Nodes reliability: reject `facing=both` when `deviceId` is set to avoid mislabeled duplicate captures, allow notification `open`/`reply` on non-clearable entries while still gating dismiss, trigger listener rebind before notification actions, and scale invoke-result ack timeout to invoke budget for large clip payloads. (#28260) Thanks @obviyus. - Android/Camera clip: remove `camera.clip` HTTP-upload fallback to base64 so clip transport is deterministic and fail-loud, and reject non-positive `maxWidth` values so invalid inputs fall back to the safe resize default. (#28229) Thanks @obviyus. - Android/Gateway canvas capability refresh: send `node.canvas.capability.refresh` with object `params` (`{}`) from Android node runtime so gateway object-schema validation accepts refresh retries and A2UI host recovery works after scoped capability expiry. (#28413) Thanks @obviyus. diff --git a/docs/channels/slack.md b/docs/channels/slack.md index 22b7f816e37..4fa23cf1ee7 100644 --- a/docs/channels/slack.md +++ b/docs/channels/slack.md @@ -208,7 +208,8 @@ For actions/directory reads, user token can be preferred when configured. For wr - Native command auto-mode is **off** for Slack (`commands.native: "auto"` does not enable Slack native commands). - Enable native Slack command handlers with `channels.slack.commands.native: true` (or global `commands.native: true`). -- When native commands are enabled, register matching slash commands in Slack (`/` names). +- When native commands are enabled, register matching slash commands in Slack (`/` names), with one exception: + - register `/agentstatus` for the status command (Slack reserves `/status`) - If native commands are not enabled, you can run a single configured slash command via `channels.slack.slashCommand`. - Native arg menus now adapt their rendering strategy: - up to 5 options: button blocks diff --git a/docs/tools/slash-commands.md b/docs/tools/slash-commands.md index b6cd63ba6cf..dea4fb0d30f 100644 --- a/docs/tools/slash-commands.md +++ b/docs/tools/slash-commands.md @@ -219,3 +219,4 @@ Notes: - Telegram: `telegram:slash:` (targets the chat session via `CommandTargetSessionKey`) - **`/stop`** targets the active chat session so it can abort the current run. - **Slack:** `channels.slack.slashCommand` is still supported for a single `/openclaw`-style command. If you enable `commands.native`, you must create one Slack slash command per built-in command (same names as `/help`). Command argument menus for Slack are delivered as ephemeral Block Kit buttons. + - Slack native exception: register `/agentstatus` (not `/status`) because Slack reserves `/status`. Text `/status` still works in Slack messages. diff --git a/src/auto-reply/commands-registry.test.ts b/src/auto-reply/commands-registry.test.ts index 918310278c9..91cc92c7891 100644 --- a/src/auto-reply/commands-registry.test.ts +++ b/src/auto-reply/commands-registry.test.ts @@ -109,6 +109,17 @@ describe("commands registry", () => { expect(findCommandByNativeName("tts", "discord")).toBeUndefined(); }); + it("renames status to agentstatus for slack", () => { + const native = listNativeCommandSpecsForConfig( + { commands: { native: true } }, + { provider: "slack" }, + ); + expect(native.find((spec) => spec.name === "agentstatus")).toBeTruthy(); + expect(native.find((spec) => spec.name === "status")).toBeFalsy(); + expect(findCommandByNativeName("agentstatus", "slack")?.key).toBe("status"); + expect(findCommandByNativeName("status", "slack")).toBeUndefined(); + }); + it("keeps discord native command specs within slash-command limits", () => { const native = listNativeCommandSpecsForConfig( { commands: { native: true } }, diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index 34ca31492bc..93f8872e37b 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -123,6 +123,11 @@ const NATIVE_NAME_OVERRIDES: Record> = { discord: { tts: "voice", }, + slack: { + // Slack reserves /status — registering it returns "invalid name" + // and invalidates the entire slash_commands manifest array. + status: "agentstatus", + }, }; function resolveNativeName(command: ChatCommandDefinition, provider?: string): string | undefined { diff --git a/src/slack/monitor/slash.test.ts b/src/slack/monitor/slash.test.ts index da96fb6af48..24c533018a9 100644 --- a/src/slack/monitor/slash.test.ts +++ b/src/slack/monitor/slash.test.ts @@ -8,6 +8,7 @@ vi.mock("../../auto-reply/commands-registry.js", () => { const reportExternalCommand = { key: "reportexternal", nativeName: "reportexternal" }; const reportLongCommand = { key: "reportlong", nativeName: "reportlong" }; const unsafeConfirmCommand = { key: "unsafeconfirm", nativeName: "unsafeconfirm" }; + const statusAliasCommand = { key: "status", nativeName: "status" }; const periodArg = { name: "period", description: "period" }; const baseReportPeriodChoices = [ { value: "day", label: "day" }, @@ -73,6 +74,9 @@ vi.mock("../../auto-reply/commands-registry.js", () => { if (normalized === "unsafeconfirm") { return unsafeConfirmCommand; } + if (normalized === "agentstatus") { + return statusAliasCommand; + } return undefined; }, listNativeCommandSpecsForConfig: () => [ @@ -112,6 +116,12 @@ vi.mock("../../auto-reply/commands-registry.js", () => { acceptsArgs: true, args: [], }, + { + name: "agentstatus", + description: "Status", + acceptsArgs: false, + args: [], + }, ], parseCommandArgs: () => ({ values: {} }), resolveCommandArgMenu: (params: { @@ -394,6 +404,7 @@ describe("Slack native command argument menus", () => { let reportExternalHandler: (args: unknown) => Promise; let reportLongHandler: (args: unknown) => Promise; let unsafeConfirmHandler: (args: unknown) => Promise; + let agentStatusHandler: (args: unknown) => Promise; let argMenuHandler: (args: unknown) => Promise; let argMenuOptionsHandler: (args: unknown) => Promise; @@ -406,6 +417,7 @@ describe("Slack native command argument menus", () => { reportExternalHandler = requireHandler(harness.commands, "/reportexternal", "/reportexternal"); reportLongHandler = requireHandler(harness.commands, "/reportlong", "/reportlong"); unsafeConfirmHandler = requireHandler(harness.commands, "/unsafeconfirm", "/unsafeconfirm"); + agentStatusHandler = requireHandler(harness.commands, "/agentstatus", "/agentstatus"); argMenuHandler = requireHandler(harness.actions, "openclaw_cmdarg", "arg-menu action"); argMenuOptionsHandler = requireHandler(harness.options, "openclaw_cmdarg", "arg-menu options"); }); @@ -474,6 +486,11 @@ describe("Slack native command argument menus", () => { expect(call.ctx?.Body).toBe("/usage tokens"); }); + it("maps /agentstatus to /status when dispatching", async () => { + await runCommandHandler(agentStatusHandler); + expectSingleDispatchedSlashBody("/status"); + }); + it("dispatches the command when a static_select option is chosen", async () => { await runArgMenuAction(argMenuHandler, { action: {