refactor(auto-reply): centralize chat command aliases

This commit is contained in:
Peter Steinberger
2026-01-09 17:14:33 +01:00
parent d372fac9c6
commit 1838582546
3 changed files with 175 additions and 125 deletions

View File

@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { hasControlCommand } from "./command-detection.js"; import { hasControlCommand } from "./command-detection.js";
import { listChatCommands } from "./commands-registry.js";
import { parseActivationCommand } from "./group-activation.js"; import { parseActivationCommand } from "./group-activation.js";
import { parseSendPolicyCommand } from "./send-policy.js"; import { parseSendPolicyCommand } from "./send-policy.js";
@@ -37,16 +38,17 @@ describe("control command parsing", () => {
}); });
it("treats bare commands as non-control", () => { it("treats bare commands as non-control", () => {
expect(hasControlCommand("/send")).toBe(true);
expect(hasControlCommand("send")).toBe(false); expect(hasControlCommand("send")).toBe(false);
expect(hasControlCommand("/help")).toBe(true);
expect(hasControlCommand("/help:")).toBe(true);
expect(hasControlCommand("help")).toBe(false); expect(hasControlCommand("help")).toBe(false);
expect(hasControlCommand("/status")).toBe(true);
expect(hasControlCommand("/status:")).toBe(true);
expect(hasControlCommand("/usage")).toBe(true);
expect(hasControlCommand("/usage:")).toBe(true);
expect(hasControlCommand("status")).toBe(false); expect(hasControlCommand("status")).toBe(false);
expect(hasControlCommand("usage")).toBe(false);
for (const command of listChatCommands()) {
for (const alias of command.textAliases) {
expect(hasControlCommand(alias)).toBe(true);
expect(hasControlCommand(`${alias}:`)).toBe(true);
}
}
}); });
it("requires commands to be the full message", () => { it("requires commands to be the full message", () => {

View File

@@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
import { import {
buildCommandText, buildCommandText,
getCommandDetection, getCommandDetection,
listChatCommands,
listNativeCommandSpecs, listNativeCommandSpecs,
shouldHandleTextCommands, shouldHandleTextCommands,
} from "./commands-registry.js"; } from "./commands-registry.js";
@@ -21,16 +22,21 @@ describe("commands registry", () => {
it("detects known text commands", () => { it("detects known text commands", () => {
const detection = getCommandDetection(); const detection = getCommandDetection();
expect(detection.exact.has("/help")).toBe(true); for (const command of listChatCommands()) {
expect(detection.regex.test("/status")).toBe(true); for (const alias of command.textAliases) {
expect(detection.regex.test("/status:")).toBe(true); expect(detection.exact.has(alias.toLowerCase())).toBe(true);
expect(detection.regex.test("/usage")).toBe(true); expect(detection.regex.test(alias)).toBe(true);
expect(detection.regex.test("/usage:")).toBe(true); expect(detection.regex.test(`${alias}:`)).toBe(true);
expect(detection.regex.test("/stop")).toBe(true);
expect(detection.regex.test("/send:")).toBe(true); if (command.acceptsArgs) {
expect(detection.regex.test("/debug set foo=bar")).toBe(true); expect(detection.regex.test(`${alias} list`)).toBe(true);
expect(detection.regex.test("/models")).toBe(true); expect(detection.regex.test(`${alias}: list`)).toBe(true);
expect(detection.regex.test("/models list")).toBe(true); } else {
expect(detection.regex.test(`${alias} list`)).toBe(false);
expect(detection.regex.test(`${alias}: list`)).toBe(false);
}
}
}
expect(detection.regex.test("try /status")).toBe(false); expect(detection.regex.test("try /status")).toBe(false);
}); });

View File

@@ -14,114 +14,156 @@ export type NativeCommandSpec = {
acceptsArgs: boolean; acceptsArgs: boolean;
}; };
const CHAT_COMMANDS: ChatCommandDefinition[] = [ function defineChatCommand(
{ command: Omit<ChatCommandDefinition, "textAliases"> & { textAlias: string },
key: "help", ): ChatCommandDefinition {
nativeName: "help", return {
description: "Show available commands.", key: command.key,
textAliases: ["/help"], nativeName: command.nativeName,
}, description: command.description,
{ acceptsArgs: command.acceptsArgs,
key: "status", textAliases: [command.textAlias],
nativeName: "status", };
description: "Show current status.", }
textAliases: ["/status", "/usage"],
}, function registerAlias(
{ commands: ChatCommandDefinition[],
key: "debug", key: string,
nativeName: "debug", ...aliases: string[]
description: "Set runtime debug overrides.", ): void {
textAliases: ["/debug"], const command = commands.find((entry) => entry.key === key);
acceptsArgs: true, if (!command) {
}, throw new Error(`registerAlias: unknown command key: ${key}`);
{ }
key: "cost", const existing = new Set(command.textAliases.map((alias) => alias.trim()));
nativeName: "cost", for (const alias of aliases) {
description: "Toggle per-response usage line.", const trimmed = alias.trim();
textAliases: ["/cost"], if (!trimmed) continue;
acceptsArgs: true, if (existing.has(trimmed)) continue;
}, existing.add(trimmed);
{ command.textAliases.push(trimmed);
key: "stop", }
nativeName: "stop", }
description: "Stop the current run.",
textAliases: ["/stop"], export const CHAT_COMMANDS: ChatCommandDefinition[] = (() => {
}, const commands: ChatCommandDefinition[] = [
{ defineChatCommand({
key: "restart", key: "help",
nativeName: "restart", nativeName: "help",
description: "Restart Clawdbot.", description: "Show available commands.",
textAliases: ["/restart"], textAlias: "/help",
}, }),
{ defineChatCommand({
key: "activation", key: "status",
nativeName: "activation", nativeName: "status",
description: "Set group activation mode.", description: "Show current status.",
textAliases: ["/activation"], textAlias: "/status",
acceptsArgs: true, }),
}, defineChatCommand({
{ key: "debug",
key: "send", nativeName: "debug",
nativeName: "send", description: "Set runtime debug overrides.",
description: "Set send policy.", textAlias: "/debug",
textAliases: ["/send"], acceptsArgs: true,
acceptsArgs: true, }),
}, defineChatCommand({
{ key: "cost",
key: "reset", nativeName: "cost",
nativeName: "reset", description: "Toggle per-response usage line.",
description: "Reset the current session.", textAlias: "/cost",
textAliases: ["/reset"], acceptsArgs: true,
}, }),
{ defineChatCommand({
key: "new", key: "stop",
nativeName: "new", nativeName: "stop",
description: "Start a new session.", description: "Stop the current run.",
textAliases: ["/new"], textAlias: "/stop",
}, }),
{ defineChatCommand({
key: "think", key: "restart",
nativeName: "think", nativeName: "restart",
description: "Set thinking level.", description: "Restart Clawdbot.",
textAliases: ["/thinking", "/think", "/t"], textAlias: "/restart",
acceptsArgs: true, }),
}, defineChatCommand({
{ key: "activation",
key: "verbose", nativeName: "activation",
nativeName: "verbose", description: "Set group activation mode.",
description: "Toggle verbose mode.", textAlias: "/activation",
textAliases: ["/verbose", "/v"], acceptsArgs: true,
acceptsArgs: true, }),
}, defineChatCommand({
{ key: "send",
key: "reasoning", nativeName: "send",
nativeName: "reasoning", description: "Set send policy.",
description: "Toggle reasoning visibility.", textAlias: "/send",
textAliases: ["/reasoning", "/reason"], acceptsArgs: true,
acceptsArgs: true, }),
}, defineChatCommand({
{ key: "reset",
key: "elevated", nativeName: "reset",
nativeName: "elevated", description: "Reset the current session.",
description: "Toggle elevated mode.", textAlias: "/reset",
textAliases: ["/elevated", "/elev"], }),
acceptsArgs: true, defineChatCommand({
}, key: "new",
{ nativeName: "new",
key: "model", description: "Start a new session.",
nativeName: "model", textAlias: "/new",
description: "Show or set the model.", }),
textAliases: ["/model", "/models"], defineChatCommand({
acceptsArgs: true, key: "think",
}, nativeName: "think",
{ description: "Set thinking level.",
key: "queue", textAlias: "/think",
nativeName: "queue", acceptsArgs: true,
description: "Adjust queue settings.", }),
textAliases: ["/queue"], defineChatCommand({
acceptsArgs: true, key: "verbose",
}, nativeName: "verbose",
]; description: "Toggle verbose mode.",
textAlias: "/verbose",
acceptsArgs: true,
}),
defineChatCommand({
key: "reasoning",
nativeName: "reasoning",
description: "Toggle reasoning visibility.",
textAlias: "/reasoning",
acceptsArgs: true,
}),
defineChatCommand({
key: "elevated",
nativeName: "elevated",
description: "Toggle elevated mode.",
textAlias: "/elevated",
acceptsArgs: true,
}),
defineChatCommand({
key: "model",
nativeName: "model",
description: "Show or set the model.",
textAlias: "/model",
acceptsArgs: true,
}),
defineChatCommand({
key: "queue",
nativeName: "queue",
description: "Adjust queue settings.",
textAlias: "/queue",
acceptsArgs: true,
}),
];
registerAlias(commands, "status", "/usage");
registerAlias(commands, "think", "/thinking", "/t");
registerAlias(commands, "verbose", "/v");
registerAlias(commands, "reasoning", "/reason");
registerAlias(commands, "elevated", "/elev");
registerAlias(commands, "model", "/models");
return commands;
})();
const NATIVE_COMMAND_SURFACES = new Set(["discord", "slack", "telegram"]); const NATIVE_COMMAND_SURFACES = new Set(["discord", "slack", "telegram"]);