mirror of https://github.com/openclaw/openclaw.git
fix(discord): avoid native plugin command collisions
This commit is contained in:
parent
4dcd930923
commit
be9ea991de
|
|
@ -283,6 +283,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Discord inbound listener non-blocking dispatch: make `MESSAGE_CREATE` listener handoff asynchronous (no per-listener queue blocking), so long runs no longer stall unrelated incoming events. (#39154) Thanks @yaseenkadlemakki.
|
||||
- Daemon/Windows PATH freeze fix: stop persisting install-time `PATH` snapshots into Scheduled Task scripts so runtime tool lookup follows current host PATH updates; also refresh local TUI history on silent local finals. (#39139) Thanks @Narcooo.
|
||||
- Gateway/systemd service restart hardening: clear stale gateway listeners by explicit run-port before service bind, add restart stale-pid port-override support, tune systemd start/stop/exit handling, and disable detached child mode only in service-managed runtime so cgroup stop semantics clean up descendants reliably. (#38463) Thanks @spirittechie.
|
||||
- Discord/plugin native command aliases: let plugins declare provider-specific slash names so native Discord registration can avoid built-in command collisions; the bundled Talk voice plugin now uses `/talkvoice` natively on Discord while keeping text `/voice`.
|
||||
|
||||
## 2026.3.2
|
||||
|
||||
|
|
|
|||
|
|
@ -863,6 +863,7 @@ Command handler context:
|
|||
Command options:
|
||||
|
||||
- `name`: Command name (without the leading `/`)
|
||||
- `nativeNames`: Optional native-command aliases for slash/menu surfaces. Use `default` for all native providers, or provider-specific keys like `discord`
|
||||
- `description`: Help text shown in command lists
|
||||
- `acceptsArgs`: Whether the command accepts arguments (default: false). If false and arguments are provided, the command won't match and the message falls through to other handlers
|
||||
- `requireAuth`: Whether to require authorized sender (default: true)
|
||||
|
|
|
|||
|
|
@ -77,12 +77,20 @@ function asTrimmedString(value: unknown): string {
|
|||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function resolveCommandLabel(channel: string): string {
|
||||
return channel === "discord" ? "/talkvoice" : "/voice";
|
||||
}
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
api.registerCommand({
|
||||
name: "voice",
|
||||
nativeNames: {
|
||||
discord: "talkvoice",
|
||||
},
|
||||
description: "List/set ElevenLabs Talk voice (affects iOS Talk playback).",
|
||||
acceptsArgs: true,
|
||||
handler: async (ctx) => {
|
||||
const commandLabel = resolveCommandLabel(ctx.channel);
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = (tokens[0] ?? "status").toLowerCase();
|
||||
|
|
@ -118,13 +126,13 @@ export default function register(api: OpenClawPluginApi) {
|
|||
if (action === "set") {
|
||||
const query = tokens.slice(1).join(" ").trim();
|
||||
if (!query) {
|
||||
return { text: "Usage: /voice set <voiceId|name>" };
|
||||
return { text: `Usage: ${commandLabel} set <voiceId|name>` };
|
||||
}
|
||||
const voices = await listVoices(apiKey);
|
||||
const chosen = findVoice(voices, query);
|
||||
if (!chosen) {
|
||||
const hint = isLikelyVoiceId(query) ? query : `"${query}"`;
|
||||
return { text: `No voice found for ${hint}. Try: /voice list` };
|
||||
return { text: `No voice found for ${hint}. Try: ${commandLabel} list` };
|
||||
}
|
||||
|
||||
const nextConfig = {
|
||||
|
|
@ -144,9 +152,9 @@ export default function register(api: OpenClawPluginApi) {
|
|||
text: [
|
||||
"Voice commands:",
|
||||
"",
|
||||
"/voice status",
|
||||
"/voice list [limit]",
|
||||
"/voice set <voiceId|name>",
|
||||
`${commandLabel} status`,
|
||||
`${commandLabel} list [limit]`,
|
||||
`${commandLabel} set <voiceId|name>`,
|
||||
].join("\n"),
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -720,6 +720,7 @@ describe("monitorDiscordProvider", () => {
|
|||
const commandNames = (createDiscordNativeCommandMock.mock.calls as Array<unknown[]>)
|
||||
.map((call) => (call[0] as { command?: { name?: string } } | undefined)?.command?.name)
|
||||
.filter((value): value is string => typeof value === "string");
|
||||
expect(getPluginCommandSpecsMock).toHaveBeenCalledWith("discord");
|
||||
expect(commandNames).toContain("cmd");
|
||||
expect(commandNames).toContain("cron_jobs");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ function appendPluginCommandSpecs(params: {
|
|||
const existingNames = new Set(
|
||||
merged.map((spec) => spec.name.trim().toLowerCase()).filter(Boolean),
|
||||
);
|
||||
for (const pluginCommand of getPluginCommandSpecs()) {
|
||||
for (const pluginCommand of getPluginCommandSpecs("discord")) {
|
||||
const normalizedName = pluginCommand.name.trim().toLowerCase();
|
||||
if (!normalizedName) {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -59,4 +59,39 @@ describe("registerPluginCommand", () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("supports provider-specific native command aliases", () => {
|
||||
const result = registerPluginCommand("demo-plugin", {
|
||||
name: "voice",
|
||||
nativeNames: {
|
||||
default: "talkvoice",
|
||||
discord: "discordvoice",
|
||||
},
|
||||
description: "Demo command",
|
||||
handler: async () => ({ text: "ok" }),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ ok: true });
|
||||
expect(getPluginCommandSpecs()).toEqual([
|
||||
{
|
||||
name: "talkvoice",
|
||||
description: "Demo command",
|
||||
acceptsArgs: false,
|
||||
},
|
||||
]);
|
||||
expect(getPluginCommandSpecs("discord")).toEqual([
|
||||
{
|
||||
name: "discordvoice",
|
||||
description: "Demo command",
|
||||
acceptsArgs: false,
|
||||
},
|
||||
]);
|
||||
expect(getPluginCommandSpecs("telegram")).toEqual([
|
||||
{
|
||||
name: "talkvoice",
|
||||
description: "Demo command",
|
||||
acceptsArgs: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -316,16 +316,32 @@ export function listPluginCommands(): Array<{
|
|||
}));
|
||||
}
|
||||
|
||||
function resolvePluginNativeName(
|
||||
command: OpenClawPluginCommandDefinition,
|
||||
provider?: string,
|
||||
): string {
|
||||
const providerName = provider?.trim().toLowerCase();
|
||||
const providerOverride = providerName ? command.nativeNames?.[providerName] : undefined;
|
||||
if (typeof providerOverride === "string" && providerOverride.trim()) {
|
||||
return providerOverride.trim();
|
||||
}
|
||||
const defaultOverride = command.nativeNames?.default;
|
||||
if (typeof defaultOverride === "string" && defaultOverride.trim()) {
|
||||
return defaultOverride.trim();
|
||||
}
|
||||
return command.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin command specs for native command registration (e.g., Telegram).
|
||||
*/
|
||||
export function getPluginCommandSpecs(): Array<{
|
||||
export function getPluginCommandSpecs(provider?: string): Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
acceptsArgs: boolean;
|
||||
}> {
|
||||
return Array.from(pluginCommands.values()).map((cmd) => ({
|
||||
name: cmd.name,
|
||||
name: resolvePluginNativeName(cmd, provider),
|
||||
description: cmd.description,
|
||||
acceptsArgs: cmd.acceptsArgs ?? false,
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -186,6 +186,12 @@ export type PluginCommandHandler = (
|
|||
export type OpenClawPluginCommandDefinition = {
|
||||
/** Command name without leading slash (e.g., "tts") */
|
||||
name: string;
|
||||
/**
|
||||
* Optional native-command aliases for slash/menu surfaces.
|
||||
* `default` applies to all native providers unless a provider-specific
|
||||
* override exists (for example `{ default: "talkvoice", discord: "voice2" }`).
|
||||
*/
|
||||
nativeNames?: Partial<Record<string, string>> & { default?: string };
|
||||
/** Description shown in /help and command menus */
|
||||
description: string;
|
||||
/** Whether this command accepts arguments */
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ export const registerTelegramNativeCommands = ({
|
|||
runtime.error?.(danger(issue.message));
|
||||
}
|
||||
const customCommands = customResolution.commands;
|
||||
const pluginCommandSpecs = getPluginCommandSpecs();
|
||||
const pluginCommandSpecs = getPluginCommandSpecs("telegram");
|
||||
const existingCommands = new Set(
|
||||
[
|
||||
...nativeCommands.map((command) => normalizeTelegramCommandName(command.name)),
|
||||
|
|
|
|||
Loading…
Reference in New Issue