fix: allow plugin commands on Slack when channel supports native commands

The `getPluginCommandSpecs` gate only checked `nativeCommandsAutoEnabled`,
which Slack sets to `false`. This caused plugin-registered slash commands
(e.g. /lcm, /lossless from lossless-claw) to be silently excluded from
Slack, even though Slack declares `capabilities.nativeCommands: true` and
the user explicitly enables native commands via config.

The fix widens the gate to also pass when the channel plugin declares
`capabilities.nativeCommands === true`, and adds the
`getPluginCommandSpecs` integration to the Slack slash command setup so
plugin commands are merged into the native command list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
rafaelreis-r
2026-04-10 22:32:18 -03:00
committed by Josh Lehman
parent ce1fffa97e
commit 052ff9464d
4 changed files with 21 additions and 6 deletions

View File

@@ -9,6 +9,7 @@ import {
resolveNativeCommandsEnabled,
resolveNativeSkillsEnabled,
} from "openclaw/plugin-sdk/config-runtime";
import { getPluginCommandSpecs } from "openclaw/plugin-sdk/command-auth";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { chunkItems, normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
@@ -670,6 +671,15 @@ export async function registerSlackMonitorSlashCommands(params: {
skillCommands,
provider: "slack",
});
const existingNativeNames = new Set(
nativeCommands.map((c) => normalizeLowercaseStringOrEmpty(c.name)).filter(Boolean),
);
for (const pluginCommand of getPluginCommandSpecs("slack")) {
const normalizedName = normalizeLowercaseStringOrEmpty(pluginCommand.name);
if (!normalizedName || existingNativeNames.has(normalizedName)) continue;
existingNativeNames.add(normalizedName);
nativeCommands.push(pluginCommand);
}
}
if (nativeCommands.length > 0) {

View File

@@ -76,6 +76,7 @@ export {
listSkillCommandsForWorkspace,
resolveSkillCommandInvocation,
} from "../auto-reply/skill-commands.js";
export { getPluginCommandSpecs } from "../plugins/command-registration.js";
export type { SkillCommandSpec } from "../agents/skills.js";
export {
buildModelsProviderData,

View File

@@ -73,11 +73,15 @@ export function getPluginCommandSpecs(provider?: string): Array<{
acceptsArgs: boolean;
}> {
const providerName = normalizeOptionalLowercaseString(provider);
if (
providerName &&
getChannelPlugin(providerName)?.commands?.nativeCommandsAutoEnabled !== true
) {
return [];
if (providerName) {
const channelPlugin = getChannelPlugin(providerName);
if (
channelPlugin &&
!channelPlugin.capabilities?.nativeCommands &&
!channelPlugin.commands?.nativeCommandsAutoEnabled
) {
return [];
}
}
return Array.from(pluginCommands.values()).map((cmd) => ({
name: resolvePluginNativeName(cmd, provider),

View File

@@ -296,7 +296,7 @@ describe("registerPluginCommand", () => {
{ provider: undefined, expectedNames: ["talkvoice"] },
{ provider: "discord", expectedNames: ["discordvoice"] },
{ provider: "telegram", expectedNames: ["talkvoice"] },
{ provider: "slack", expectedNames: [] },
{ provider: "slack", expectedNames: ["talkvoice"] },
]);
});