fix(providers): guard invalid skill command namespaces

Claude plugin ids come from local settings and installed plugin metadata.

Invalid ids such as empty strings or @ should not become command namespaces.

Skip plugin folders when no safe plugin name can be derived.

This prevents malformed slash commands like /:command from reaching the UI.

Add regression coverage for empty and @ plugin ids.

Keyboard selection in the slash menu should match mouse selection.

Only skills are inserted into the composer because they are provider invocations.

Built-in and custom commands execute directly and close the menu on success or failure.
This commit is contained in:
Haileyesus
2026-05-11 18:58:55 +03:00
parent 053e43447a
commit aabf331e91
3 changed files with 85 additions and 26 deletions

View File

@@ -310,22 +310,36 @@ export function useSlashCommands({
[input, resetCommandMenuState, setInput, slashPosition, textareaRef],
);
const executeNonSkillCommand = useCallback(
(command: SlashCommand) => {
const executionResult = onExecuteCommand(command);
if (isPromiseLike(executionResult)) {
executionResult.then(
() => {
resetCommandMenuState();
},
() => {
resetCommandMenuState();
// Keep behavior silent; execution errors are handled by caller.
},
);
} else {
resetCommandMenuState();
}
},
[onExecuteCommand, resetCommandMenuState],
);
const selectCommandFromKeyboard = useCallback(
(command: SlashCommand) => {
insertCommandIntoInput(command);
if (isSkillCommand(command)) {
insertCommandIntoInput(command);
return;
}
const executionResult = onExecuteCommand(command);
if (isPromiseLike(executionResult)) {
executionResult.catch(() => {
// Keep behavior silent; execution errors are handled by caller.
});
}
executeNonSkillCommand(command);
},
[insertCommandIntoInput, onExecuteCommand],
[executeNonSkillCommand, insertCommandIntoInput],
);
const handleCommandSelect = useCallback(
@@ -345,20 +359,9 @@ export function useSlashCommands({
return;
}
const executionResult = onExecuteCommand(command);
if (isPromiseLike(executionResult)) {
executionResult.then(() => {
resetCommandMenuState();
});
executionResult.catch(() => {
// Keep behavior silent; execution errors are handled by caller.
});
} else {
resetCommandMenuState();
}
executeNonSkillCommand(command);
},
[selectedProject, trackCommandUsage, insertCommandIntoInput, onExecuteCommand, resetCommandMenuState],
[selectedProject, trackCommandUsage, insertCommandIntoInput, executeNonSkillCommand],
);
const handleToggleCommandMenu = useCallback(() => {