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