fix: avoid Discord /tts conflict
This commit is contained in:
@@ -68,6 +68,7 @@ Text + native (when enabled):
|
||||
- `/debug show|set|unset|reset` (runtime overrides, owner-only; requires `commands.debug: true`)
|
||||
- `/usage off|tokens|full|cost` (per-response usage footer or local cost summary)
|
||||
- `/tts on|off|status|provider|limit|summary|audio` (control TTS; see [/tts](/tts))
|
||||
- Discord: native command is `/voice` (Discord reserves `/tts`); text `/tts` still works.
|
||||
- `/stop`
|
||||
- `/restart`
|
||||
- `/dock-telegram` (alias: `/dock_telegram`) (switch replies to Telegram)
|
||||
|
||||
@@ -260,6 +260,9 @@ Reply -> TTS enabled?
|
||||
There is a single command: `/tts`.
|
||||
See [Slash commands](/tools/slash-commands) for enablement details.
|
||||
|
||||
Discord note: `/tts` is a built-in Discord command, so Clawdbot registers
|
||||
`/voice` as the native command there. Text `/tts ...` still works.
|
||||
|
||||
```
|
||||
/tts on
|
||||
/tts off
|
||||
|
||||
@@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildCommandText,
|
||||
buildCommandTextFromArgs,
|
||||
findCommandByNativeName,
|
||||
getCommandDetection,
|
||||
listChatCommands,
|
||||
listChatCommandsForConfig,
|
||||
@@ -85,6 +86,16 @@ describe("commands registry", () => {
|
||||
expect(native.find((spec) => spec.name === "demo_skill")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("applies provider-specific native names", () => {
|
||||
const native = listNativeCommandSpecsForConfig(
|
||||
{ commands: { native: true } },
|
||||
{ provider: "discord" },
|
||||
);
|
||||
expect(native.find((spec) => spec.name === "voice")).toBeTruthy();
|
||||
expect(findCommandByNativeName("voice", "discord")?.key).toBe("tts");
|
||||
expect(findCommandByNativeName("tts", "discord")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("detects known text commands", () => {
|
||||
const detection = getCommandDetection();
|
||||
expect(detection.exact.has("/commands")).toBe(true);
|
||||
|
||||
@@ -105,13 +105,29 @@ export function listChatCommandsForConfig(
|
||||
return [...base, ...buildSkillCommandDefinitions(params.skillCommands)];
|
||||
}
|
||||
|
||||
const NATIVE_NAME_OVERRIDES: Record<string, Record<string, string>> = {
|
||||
discord: {
|
||||
tts: "voice",
|
||||
},
|
||||
};
|
||||
|
||||
function resolveNativeName(command: ChatCommandDefinition, provider?: string): string | undefined {
|
||||
if (!command.nativeName) return undefined;
|
||||
if (provider) {
|
||||
const override = NATIVE_NAME_OVERRIDES[provider]?.[command.key];
|
||||
if (override) return override;
|
||||
}
|
||||
return command.nativeName;
|
||||
}
|
||||
|
||||
export function listNativeCommandSpecs(params?: {
|
||||
skillCommands?: SkillCommandSpec[];
|
||||
provider?: string;
|
||||
}): NativeCommandSpec[] {
|
||||
return listChatCommands({ skillCommands: params?.skillCommands })
|
||||
.filter((command) => command.scope !== "text" && command.nativeName)
|
||||
.map((command) => ({
|
||||
name: command.nativeName ?? command.key,
|
||||
name: resolveNativeName(command, params?.provider) ?? command.key,
|
||||
description: command.description,
|
||||
acceptsArgs: Boolean(command.acceptsArgs),
|
||||
args: command.args,
|
||||
@@ -120,22 +136,27 @@ export function listNativeCommandSpecs(params?: {
|
||||
|
||||
export function listNativeCommandSpecsForConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
params?: { skillCommands?: SkillCommandSpec[] },
|
||||
params?: { skillCommands?: SkillCommandSpec[]; provider?: string },
|
||||
): NativeCommandSpec[] {
|
||||
return listChatCommandsForConfig(cfg, params)
|
||||
.filter((command) => command.scope !== "text" && command.nativeName)
|
||||
.map((command) => ({
|
||||
name: command.nativeName ?? command.key,
|
||||
name: resolveNativeName(command, params?.provider) ?? command.key,
|
||||
description: command.description,
|
||||
acceptsArgs: Boolean(command.acceptsArgs),
|
||||
args: command.args,
|
||||
}));
|
||||
}
|
||||
|
||||
export function findCommandByNativeName(name: string): ChatCommandDefinition | undefined {
|
||||
export function findCommandByNativeName(
|
||||
name: string,
|
||||
provider?: string,
|
||||
): ChatCommandDefinition | undefined {
|
||||
const normalized = name.trim().toLowerCase();
|
||||
return getChatCommands().find(
|
||||
(command) => command.scope !== "text" && command.nativeName?.toLowerCase() === normalized,
|
||||
(command) =>
|
||||
command.scope !== "text" &&
|
||||
resolveNativeName(command, provider)?.toLowerCase() === normalized,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ async function handleDiscordCommandArgInteraction(
|
||||
return;
|
||||
}
|
||||
const commandDefinition =
|
||||
findCommandByNativeName(parsed.command) ??
|
||||
findCommandByNativeName(parsed.command, "discord") ??
|
||||
listChatCommands().find((entry) => entry.key === parsed.command);
|
||||
if (!commandDefinition) {
|
||||
await safeDiscordInteractionCall("command arg update", () =>
|
||||
@@ -395,7 +395,7 @@ export function createDiscordNativeCommand(params: {
|
||||
}) {
|
||||
const { command, cfg, discordConfig, accountId, sessionPrefix, ephemeralDefault } = params;
|
||||
const commandDefinition =
|
||||
findCommandByNativeName(command.name) ??
|
||||
findCommandByNativeName(command.name, "discord") ??
|
||||
({
|
||||
key: command.name,
|
||||
nativeName: command.name,
|
||||
|
||||
@@ -346,11 +346,13 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
const maxDiscordCommands = 100;
|
||||
let skillCommands =
|
||||
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||
let commandSpecs = nativeEnabled ? listNativeCommandSpecsForConfig(cfg, { skillCommands }) : [];
|
||||
let commandSpecs = nativeEnabled
|
||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "discord" })
|
||||
: [];
|
||||
const initialCommandCount = commandSpecs.length;
|
||||
if (nativeEnabled && nativeSkillsEnabled && commandSpecs.length > maxDiscordCommands) {
|
||||
skillCommands = [];
|
||||
commandSpecs = listNativeCommandSpecsForConfig(cfg, { skillCommands: [] });
|
||||
commandSpecs = listNativeCommandSpecsForConfig(cfg, { skillCommands: [], provider: "discord" });
|
||||
runtime.log?.(
|
||||
warn(
|
||||
`discord: ${initialCommandCount} commands exceeds limit; removing per-skill commands and keeping /skill.`,
|
||||
|
||||
@@ -476,14 +476,14 @@ export function registerSlackMonitorSlashCommands(params: {
|
||||
const skillCommands =
|
||||
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||
const nativeCommands = nativeEnabled
|
||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "slack" })
|
||||
: [];
|
||||
if (nativeCommands.length > 0) {
|
||||
for (const command of nativeCommands) {
|
||||
ctx.app.command(
|
||||
`/${command.name}`,
|
||||
async ({ command: cmd, ack, respond }: SlackCommandMiddlewareArgs) => {
|
||||
const commandDefinition = findCommandByNativeName(command.name);
|
||||
const commandDefinition = findCommandByNativeName(command.name, "slack");
|
||||
const rawText = cmd.text?.trim() ?? "";
|
||||
const commandArgs = commandDefinition
|
||||
? parseCommandArgs(commandDefinition, rawText)
|
||||
@@ -557,7 +557,7 @@ export function registerSlackMonitorSlashCommands(params: {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const commandDefinition = findCommandByNativeName(parsed.command);
|
||||
const commandDefinition = findCommandByNativeName(parsed.command, "slack");
|
||||
const commandArgs: CommandArgs = {
|
||||
values: { [parsed.arg]: parsed.value },
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@ export const registerTelegramNativeCommands = ({
|
||||
const skillCommands =
|
||||
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||
const nativeCommands = nativeEnabled
|
||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "telegram" })
|
||||
: [];
|
||||
const reservedCommands = new Set(
|
||||
listNativeCommandSpecs().map((command) => command.name.toLowerCase()),
|
||||
@@ -216,7 +216,7 @@ export const registerTelegramNativeCommands = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const commandDefinition = findCommandByNativeName(command.name);
|
||||
const commandDefinition = findCommandByNativeName(command.name, "telegram");
|
||||
const rawText = ctx.match?.trim() ?? "";
|
||||
const commandArgs = commandDefinition
|
||||
? parseCommandArgs(commandDefinition, rawText)
|
||||
|
||||
Reference in New Issue
Block a user