feat: add plugin command API for LLM-free auto-reply commands

This adds a new `api.registerCommand()` method to the plugin API, allowing
plugins to register slash commands that execute without invoking the AI agent.

Features:
- Plugin commands are processed before built-in commands and the agent
- Commands can optionally require authorization
- Commands can accept arguments
- Async handlers are supported

Use case: plugins can implement toggle commands (like /tts_on, /tts_off)
that respond immediately without consuming LLM API calls.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Glucksberg
2026-01-23 03:17:10 +00:00
committed by Peter Steinberger
parent 66eec295b8
commit 4ee808dbcb
11 changed files with 355 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ import {
handleStopCommand,
handleUsageCommand,
} from "./commands-session.js";
import { handlePluginCommand } from "./commands-plugin.js";
import type {
CommandHandler,
CommandHandlerResult,
@@ -31,6 +32,8 @@ import type {
} from "./commands-types.js";
const HANDLERS: CommandHandler[] = [
// Plugin commands are processed first, before built-in commands
handlePluginCommand,
handleBashCommand,
handleActivationCommand,
handleSendPolicyCommand,

View File

@@ -0,0 +1,46 @@
/**
* Plugin Command Handler
*
* Handles commands registered by plugins, bypassing the LLM agent.
* This handler is called before built-in command handlers.
*/
import { matchPluginCommand, executePluginCommand } from "../../plugins/commands.js";
import type { CommandHandler, CommandHandlerResult } from "./commands-types.js";
/**
* Handle plugin-registered commands.
* Returns a result if a plugin command was matched and executed,
* or null to continue to the next handler.
*/
export const handlePluginCommand: CommandHandler = async (
params,
_allowTextCommands,
): Promise<CommandHandlerResult | null> => {
const { command, cfg } = params;
// Try to match a plugin command
const match = matchPluginCommand(command.commandBodyNormalized);
if (!match) return null;
// Execute the plugin command
const result = await executePluginCommand({
command: match.command,
args: match.args,
senderId: command.senderId,
channel: command.channel,
isAuthorizedSender: command.isAuthorizedSender,
commandBody: command.commandBodyNormalized,
config: cfg,
});
if (result) {
return {
shouldContinue: false,
reply: { text: result.text },
};
}
// Command was blocked (e.g., unauthorized) - don't continue to agent
return { shouldContinue: false };
};