Skip to content

Commit 68e0e45

Browse files
fix: allow plugin commands on Slack when channel supports native commands (openclaw#64578)
Merged via squash. Prepared head SHA: 2ec97bf Co-authored-by: rafaelreis-r <57492577+rafaelreis-r@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman
1 parent ce1fffa commit 68e0e45

8 files changed

Lines changed: 68 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ Docs: https://docs.openclaw.ai
271271
- Daemon/launchd: keep `openclaw gateway stop` persistent without uninstalling the macOS LaunchAgent, re-enable it on explicit restart or repair, and harden launchd label handling. (#64447) Thanks @ngutman.
272272
- Plugins/context engines: preserve `plugins.slots.contextEngine` through normalization and keep explicitly selected workspace context-engine plugins enabled, so loader diagnostics and plugin activation stop dropping that slot selection. (#64192) Thanks @hclsys.
273273
- Heartbeat: stop top-level `interval:` and `prompt:` fields outside the `tasks:` block from bleeding into the last parsed heartbeat task. (#64488) Thanks @Rahulkumar070.
274+
- Slack/plugin commands: include plugin-registered slash commands in Slack native command registration when Slack native commands are enabled. (#64578) Thanks @rafaelreis-r.
274275
- Agents/OpenAI replay: preserve malformed function-call arguments in stored assistant history, avoid double-encoding preserved raw strings on replay, and coerce replayed string args back to objects at Anthropic and Google provider boundaries. (#61956) Thanks @100yenadmin.
275276
- Heartbeat/config: accept and honor `agents.defaults.heartbeat.timeoutSeconds` and per-agent heartbeat timeout overrides for heartbeat agent turns. (#64491) Thanks @cedillarack.
276277
- CLI/devices: make implicit `openclaw devices approve` selection preview-only and require approving the exact request ID, preventing latest-request races during device pairing. (#64160) Thanks @coygeek.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
600f05b14825fa01eb9d63ab6cab5f33c74ff44a48cab5c65457ab08e5b0e91a plugin-sdk-api-baseline.json
2-
99d649a86a30756b18b91686f3683e6e829c5e316e1370266ec4fee344bc55cb plugin-sdk-api-baseline.jsonl
1+
42a93d8368fd40f6bbe3045ba89b84a28e1131c700d4e57580febd3e773b23a4 plugin-sdk-api-baseline.json
2+
515333c277b725abaccf4fd5ab8c5e58b2de39b26e1fe4738f31852fcf789c96 plugin-sdk-api-baseline.jsonl

extensions/slack/src/monitor/slash.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pi
33
import {
44
resolveCommandAuthorizedFromAuthorizers,
55
resolveNativeCommandSessionTargets,
6+
listProviderPluginCommandSpecs,
67
} from "openclaw/plugin-sdk/command-auth";
78
import { type ChatCommandDefinition, type CommandArgs } from "openclaw/plugin-sdk/command-auth";
89
import {
@@ -670,6 +671,17 @@ export async function registerSlackMonitorSlashCommands(params: {
670671
skillCommands,
671672
provider: "slack",
672673
});
674+
const existingNativeNames = new Set(
675+
nativeCommands.map((c) => normalizeLowercaseStringOrEmpty(c.name)).filter(Boolean),
676+
);
677+
for (const pluginCommand of listProviderPluginCommandSpecs("slack")) {
678+
const normalizedName = normalizeLowercaseStringOrEmpty(pluginCommand.name);
679+
if (!normalizedName || existingNativeNames.has(normalizedName)) {
680+
continue;
681+
}
682+
existingNativeNames.add(normalizedName);
683+
nativeCommands.push(pluginCommand);
684+
}
673685
}
674686

675687
if (nativeCommands.length > 0) {

src/plugin-sdk/command-auth.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export {
7676
listSkillCommandsForWorkspace,
7777
resolveSkillCommandInvocation,
7878
} from "../auto-reply/skill-commands.js";
79+
export {
80+
getPluginCommandSpecs,
81+
listProviderPluginCommandSpecs,
82+
} from "../plugins/command-registration.js";
7983
export type { SkillCommandSpec } from "../agents/skills.js";
8084
export {
8185
buildModelsProviderData,

src/plugins/command-registration.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
clearPluginCommandsForPlugin,
99
getPluginCommandSpecs,
1010
isPluginCommandRegistryLocked,
11+
listProviderPluginCommandSpecs,
1112
pluginCommands,
1213
type RegisteredPluginCommand,
1314
} from "./command-registry-state.js";
@@ -196,5 +197,10 @@ export function registerPluginCommand(
196197
return { ok: true };
197198
}
198199

199-
export { clearPluginCommands, clearPluginCommandsForPlugin, getPluginCommandSpecs };
200+
export {
201+
clearPluginCommands,
202+
clearPluginCommandsForPlugin,
203+
getPluginCommandSpecs,
204+
listProviderPluginCommandSpecs,
205+
};
200206
export type { RegisteredPluginCommand };

src/plugins/command-registry-state.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ export function getPluginCommandSpecs(provider?: string): Array<{
7979
) {
8080
return [];
8181
}
82+
return listProviderPluginCommandSpecs(provider);
83+
}
84+
85+
/** Resolve plugin command specs for a provider's native naming surface without support gating. */
86+
export function listProviderPluginCommandSpecs(provider?: string): Array<{
87+
name: string;
88+
description: string;
89+
acceptsArgs: boolean;
90+
}> {
8291
return Array.from(pluginCommands.values()).map((cmd) => ({
8392
name: resolvePluginNativeName(cmd, provider),
8493
description: cmd.description,

src/plugins/commands.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
clearPluginCommands,
66
executePluginCommand,
77
getPluginCommandSpecs,
8+
listProviderPluginCommandSpecs,
89
listPluginCommands,
910
matchPluginCommand,
1011
registerPluginCommand,
@@ -202,6 +203,17 @@ beforeEach(() => {
202203
},
203204
},
204205
},
206+
{
207+
pluginId: "slack",
208+
source: "test",
209+
plugin: {
210+
...createChannelTestPluginBase({
211+
id: "slack",
212+
label: "Slack",
213+
capabilities: { nativeCommands: true, chatTypes: ["direct", "group"] },
214+
}),
215+
},
216+
},
205217
]),
206218
);
207219
});
@@ -300,6 +312,25 @@ describe("registerPluginCommand", () => {
300312
]);
301313
});
302314

315+
it("allows Slack to resolve provider-native plugin specs without changing shared native gating", () => {
316+
const result = registerVoiceCommandForTest({
317+
nativeNames: {
318+
default: "talkvoice",
319+
discord: "discordvoice",
320+
},
321+
description: "Demo command",
322+
});
323+
324+
expect(result).toEqual({ ok: true });
325+
expect(listProviderPluginCommandSpecs("slack")).toEqual([
326+
{
327+
name: "talkvoice",
328+
description: "Demo command",
329+
acceptsArgs: false,
330+
},
331+
]);
332+
});
333+
303334
it("accepts native progress metadata on plugin commands", () => {
304335
const result = registerVoiceCommandForTest({
305336
nativeProgressMessages: { telegram: "Running voice command..." },

src/plugins/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
clearPluginCommandsForPlugin,
1515
getPluginCommandSpecs,
1616
listPluginInvocationKeys,
17+
listProviderPluginCommandSpecs,
1718
registerPluginCommand,
1819
validateCommandName,
1920
validatePluginCommandDefinition,
@@ -42,6 +43,7 @@ export {
4243
clearPluginCommands,
4344
clearPluginCommandsForPlugin,
4445
getPluginCommandSpecs,
46+
listProviderPluginCommandSpecs,
4547
registerPluginCommand,
4648
validateCommandName,
4749
validatePluginCommandDefinition,

0 commit comments

Comments
 (0)