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`)
|
- `/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)
|
- `/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))
|
- `/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`
|
- `/stop`
|
||||||
- `/restart`
|
- `/restart`
|
||||||
- `/dock-telegram` (alias: `/dock_telegram`) (switch replies to Telegram)
|
- `/dock-telegram` (alias: `/dock_telegram`) (switch replies to Telegram)
|
||||||
|
|||||||
@@ -260,6 +260,9 @@ Reply -> TTS enabled?
|
|||||||
There is a single command: `/tts`.
|
There is a single command: `/tts`.
|
||||||
See [Slash commands](/tools/slash-commands) for enablement details.
|
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 on
|
||||||
/tts off
|
/tts off
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|||||||
import {
|
import {
|
||||||
buildCommandText,
|
buildCommandText,
|
||||||
buildCommandTextFromArgs,
|
buildCommandTextFromArgs,
|
||||||
|
findCommandByNativeName,
|
||||||
getCommandDetection,
|
getCommandDetection,
|
||||||
listChatCommands,
|
listChatCommands,
|
||||||
listChatCommandsForConfig,
|
listChatCommandsForConfig,
|
||||||
@@ -85,6 +86,16 @@ describe("commands registry", () => {
|
|||||||
expect(native.find((spec) => spec.name === "demo_skill")).toBeTruthy();
|
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", () => {
|
it("detects known text commands", () => {
|
||||||
const detection = getCommandDetection();
|
const detection = getCommandDetection();
|
||||||
expect(detection.exact.has("/commands")).toBe(true);
|
expect(detection.exact.has("/commands")).toBe(true);
|
||||||
|
|||||||
@@ -105,13 +105,29 @@ export function listChatCommandsForConfig(
|
|||||||
return [...base, ...buildSkillCommandDefinitions(params.skillCommands)];
|
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?: {
|
export function listNativeCommandSpecs(params?: {
|
||||||
skillCommands?: SkillCommandSpec[];
|
skillCommands?: SkillCommandSpec[];
|
||||||
|
provider?: string;
|
||||||
}): NativeCommandSpec[] {
|
}): NativeCommandSpec[] {
|
||||||
return listChatCommands({ skillCommands: params?.skillCommands })
|
return listChatCommands({ skillCommands: params?.skillCommands })
|
||||||
.filter((command) => command.scope !== "text" && command.nativeName)
|
.filter((command) => command.scope !== "text" && command.nativeName)
|
||||||
.map((command) => ({
|
.map((command) => ({
|
||||||
name: command.nativeName ?? command.key,
|
name: resolveNativeName(command, params?.provider) ?? command.key,
|
||||||
description: command.description,
|
description: command.description,
|
||||||
acceptsArgs: Boolean(command.acceptsArgs),
|
acceptsArgs: Boolean(command.acceptsArgs),
|
||||||
args: command.args,
|
args: command.args,
|
||||||
@@ -120,22 +136,27 @@ export function listNativeCommandSpecs(params?: {
|
|||||||
|
|
||||||
export function listNativeCommandSpecsForConfig(
|
export function listNativeCommandSpecsForConfig(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
params?: { skillCommands?: SkillCommandSpec[] },
|
params?: { skillCommands?: SkillCommandSpec[]; provider?: string },
|
||||||
): NativeCommandSpec[] {
|
): NativeCommandSpec[] {
|
||||||
return listChatCommandsForConfig(cfg, params)
|
return listChatCommandsForConfig(cfg, params)
|
||||||
.filter((command) => command.scope !== "text" && command.nativeName)
|
.filter((command) => command.scope !== "text" && command.nativeName)
|
||||||
.map((command) => ({
|
.map((command) => ({
|
||||||
name: command.nativeName ?? command.key,
|
name: resolveNativeName(command, params?.provider) ?? command.key,
|
||||||
description: command.description,
|
description: command.description,
|
||||||
acceptsArgs: Boolean(command.acceptsArgs),
|
acceptsArgs: Boolean(command.acceptsArgs),
|
||||||
args: command.args,
|
args: command.args,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findCommandByNativeName(name: string): ChatCommandDefinition | undefined {
|
export function findCommandByNativeName(
|
||||||
|
name: string,
|
||||||
|
provider?: string,
|
||||||
|
): ChatCommandDefinition | undefined {
|
||||||
const normalized = name.trim().toLowerCase();
|
const normalized = name.trim().toLowerCase();
|
||||||
return getChatCommands().find(
|
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;
|
return;
|
||||||
}
|
}
|
||||||
const commandDefinition =
|
const commandDefinition =
|
||||||
findCommandByNativeName(parsed.command) ??
|
findCommandByNativeName(parsed.command, "discord") ??
|
||||||
listChatCommands().find((entry) => entry.key === parsed.command);
|
listChatCommands().find((entry) => entry.key === parsed.command);
|
||||||
if (!commandDefinition) {
|
if (!commandDefinition) {
|
||||||
await safeDiscordInteractionCall("command arg update", () =>
|
await safeDiscordInteractionCall("command arg update", () =>
|
||||||
@@ -395,7 +395,7 @@ export function createDiscordNativeCommand(params: {
|
|||||||
}) {
|
}) {
|
||||||
const { command, cfg, discordConfig, accountId, sessionPrefix, ephemeralDefault } = params;
|
const { command, cfg, discordConfig, accountId, sessionPrefix, ephemeralDefault } = params;
|
||||||
const commandDefinition =
|
const commandDefinition =
|
||||||
findCommandByNativeName(command.name) ??
|
findCommandByNativeName(command.name, "discord") ??
|
||||||
({
|
({
|
||||||
key: command.name,
|
key: command.name,
|
||||||
nativeName: command.name,
|
nativeName: command.name,
|
||||||
|
|||||||
@@ -346,11 +346,13 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
const maxDiscordCommands = 100;
|
const maxDiscordCommands = 100;
|
||||||
let skillCommands =
|
let skillCommands =
|
||||||
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||||
let commandSpecs = nativeEnabled ? listNativeCommandSpecsForConfig(cfg, { skillCommands }) : [];
|
let commandSpecs = nativeEnabled
|
||||||
|
? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "discord" })
|
||||||
|
: [];
|
||||||
const initialCommandCount = commandSpecs.length;
|
const initialCommandCount = commandSpecs.length;
|
||||||
if (nativeEnabled && nativeSkillsEnabled && commandSpecs.length > maxDiscordCommands) {
|
if (nativeEnabled && nativeSkillsEnabled && commandSpecs.length > maxDiscordCommands) {
|
||||||
skillCommands = [];
|
skillCommands = [];
|
||||||
commandSpecs = listNativeCommandSpecsForConfig(cfg, { skillCommands: [] });
|
commandSpecs = listNativeCommandSpecsForConfig(cfg, { skillCommands: [], provider: "discord" });
|
||||||
runtime.log?.(
|
runtime.log?.(
|
||||||
warn(
|
warn(
|
||||||
`discord: ${initialCommandCount} commands exceeds limit; removing per-skill commands and keeping /skill.`,
|
`discord: ${initialCommandCount} commands exceeds limit; removing per-skill commands and keeping /skill.`,
|
||||||
|
|||||||
@@ -476,14 +476,14 @@ export function registerSlackMonitorSlashCommands(params: {
|
|||||||
const skillCommands =
|
const skillCommands =
|
||||||
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||||
const nativeCommands = nativeEnabled
|
const nativeCommands = nativeEnabled
|
||||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "slack" })
|
||||||
: [];
|
: [];
|
||||||
if (nativeCommands.length > 0) {
|
if (nativeCommands.length > 0) {
|
||||||
for (const command of nativeCommands) {
|
for (const command of nativeCommands) {
|
||||||
ctx.app.command(
|
ctx.app.command(
|
||||||
`/${command.name}`,
|
`/${command.name}`,
|
||||||
async ({ command: cmd, ack, respond }: SlackCommandMiddlewareArgs) => {
|
async ({ command: cmd, ack, respond }: SlackCommandMiddlewareArgs) => {
|
||||||
const commandDefinition = findCommandByNativeName(command.name);
|
const commandDefinition = findCommandByNativeName(command.name, "slack");
|
||||||
const rawText = cmd.text?.trim() ?? "";
|
const rawText = cmd.text?.trim() ?? "";
|
||||||
const commandArgs = commandDefinition
|
const commandArgs = commandDefinition
|
||||||
? parseCommandArgs(commandDefinition, rawText)
|
? parseCommandArgs(commandDefinition, rawText)
|
||||||
@@ -557,7 +557,7 @@ export function registerSlackMonitorSlashCommands(params: {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const commandDefinition = findCommandByNativeName(parsed.command);
|
const commandDefinition = findCommandByNativeName(parsed.command, "slack");
|
||||||
const commandArgs: CommandArgs = {
|
const commandArgs: CommandArgs = {
|
||||||
values: { [parsed.arg]: parsed.value },
|
values: { [parsed.arg]: parsed.value },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const registerTelegramNativeCommands = ({
|
|||||||
const skillCommands =
|
const skillCommands =
|
||||||
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : [];
|
||||||
const nativeCommands = nativeEnabled
|
const nativeCommands = nativeEnabled
|
||||||
? listNativeCommandSpecsForConfig(cfg, { skillCommands })
|
? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "telegram" })
|
||||||
: [];
|
: [];
|
||||||
const reservedCommands = new Set(
|
const reservedCommands = new Set(
|
||||||
listNativeCommandSpecs().map((command) => command.name.toLowerCase()),
|
listNativeCommandSpecs().map((command) => command.name.toLowerCase()),
|
||||||
@@ -216,7 +216,7 @@ export const registerTelegramNativeCommands = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandDefinition = findCommandByNativeName(command.name);
|
const commandDefinition = findCommandByNativeName(command.name, "telegram");
|
||||||
const rawText = ctx.match?.trim() ?? "";
|
const rawText = ctx.match?.trim() ?? "";
|
||||||
const commandArgs = commandDefinition
|
const commandArgs = commandDefinition
|
||||||
? parseCommandArgs(commandDefinition, rawText)
|
? parseCommandArgs(commandDefinition, rawText)
|
||||||
|
|||||||
Reference in New Issue
Block a user