(feat): Initial Version of Custom Commands (#4572)

This commit is contained in:
Abhi
2025-07-22 00:34:55 -04:00
committed by GitHub
parent 5f813ef510
commit 9daead63dd
17 changed files with 1008 additions and 96 deletions

View File

@@ -22,6 +22,7 @@ import { LoadedSettings } from '../../config/settings.js';
import { type CommandContext, type SlashCommand } from '../commands/types.js';
import { CommandService } from '../../services/CommandService.js';
import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js';
import { FileCommandLoader } from '../../services/FileCommandLoader.js';
/**
* Hook to define and process slash commands (e.g., /help, /clear).
@@ -162,8 +163,10 @@ export const useSlashCommandProcessor = (
useEffect(() => {
const controller = new AbortController();
const load = async () => {
// TODO - Add other loaders for custom commands.
const loaders = [new BuiltinCommandLoader(config)];
const loaders = [
new BuiltinCommandLoader(config),
new FileCommandLoader(config),
];
const commandService = await CommandService.create(
loaders,
controller.signal,
@@ -192,12 +195,7 @@ export const useSlashCommandProcessor = (
}
const userMessageTimestamp = Date.now();
if (trimmed !== '/quit' && trimmed !== '/exit') {
addItem(
{ type: MessageType.USER, text: trimmed },
userMessageTimestamp,
);
}
addItem({ type: MessageType.USER, text: trimmed }, userMessageTimestamp);
const parts = trimmed.substring(1).trim().split(/\s+/);
const commandPath = parts.filter((p) => p); // The parts of the command, e.g., ['memory', 'add']
@@ -207,9 +205,21 @@ export const useSlashCommandProcessor = (
let pathIndex = 0;
for (const part of commandPath) {
const foundCommand = currentCommands.find(
(cmd) => cmd.name === part || cmd.altNames?.includes(part),
);
// TODO: For better performance and architectural clarity, this two-pass
// search could be replaced. A more optimal approach would be to
// pre-compute a single lookup map in `CommandService.ts` that resolves
// all name and alias conflicts during the initial loading phase. The
// processor would then perform a single, fast lookup on that map.
// First pass: check for an exact match on the primary command name.
let foundCommand = currentCommands.find((cmd) => cmd.name === part);
// Second pass: if no primary name matches, check for an alias.
if (!foundCommand) {
foundCommand = currentCommands.find((cmd) =>
cmd.altNames?.includes(part),
);
}
if (foundCommand) {
commandToExecute = foundCommand;
@@ -290,6 +300,12 @@ export const useSlashCommandProcessor = (
process.exit(0);
}, 100);
return { type: 'handled' };
case 'submit_prompt':
return {
type: 'submit_prompt',
content: result.content,
};
default: {
const unhandled: never = result;
throw new Error(`Unhandled slash command result: ${unhandled}`);