mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
fix(cli): treat unknown slash commands as regular input instead of showing error (#17393)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
type GeminiClient,
|
||||
type UserFeedbackPayload,
|
||||
SlashCommandStatus,
|
||||
MCPDiscoveryState,
|
||||
makeFakeConfig,
|
||||
coreEvents,
|
||||
CoreEvent,
|
||||
@@ -389,21 +390,49 @@ describe('useSlashCommandProcessor', () => {
|
||||
});
|
||||
|
||||
describe('Command Execution Logic', () => {
|
||||
it('should display an error for an unknown command', async () => {
|
||||
it('should treat unknown commands as regular input', async () => {
|
||||
const result = await setupProcessorHook();
|
||||
await waitFor(() => expect(result.current.slashCommands).toBeDefined());
|
||||
|
||||
let handled: Awaited<
|
||||
ReturnType<typeof result.current.handleSlashCommand>
|
||||
>;
|
||||
await act(async () => {
|
||||
await result.current.handleSlashCommand('/nonexistent');
|
||||
handled = await result.current.handleSlashCommand('/nonexistent');
|
||||
});
|
||||
|
||||
// Expect 2 calls: one for the user's input, one for the error message.
|
||||
expect(mockAddItem).toHaveBeenCalledTimes(2);
|
||||
expect(mockAddItem).toHaveBeenLastCalledWith(
|
||||
{
|
||||
// Unknown commands should return false so the input is sent to the model
|
||||
expect(handled!).toBe(false);
|
||||
// Should not add anything to history (the regular flow will handle it)
|
||||
expect(mockAddItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show MCP loading warning for unknown commands when MCP is loading', async () => {
|
||||
vi.spyOn(mockConfig, 'getMcpClientManager').mockReturnValue({
|
||||
getDiscoveryState: () => MCPDiscoveryState.IN_PROGRESS,
|
||||
} as ReturnType<typeof mockConfig.getMcpClientManager>);
|
||||
|
||||
const result = await setupProcessorHook();
|
||||
await waitFor(() => expect(result.current.slashCommands).toBeDefined());
|
||||
|
||||
let handled: Awaited<
|
||||
ReturnType<typeof result.current.handleSlashCommand>
|
||||
>;
|
||||
await act(async () => {
|
||||
handled = await result.current.handleSlashCommand('/mcp-command');
|
||||
});
|
||||
|
||||
// When MCP is loading, should handle the command (show warning)
|
||||
expect(handled!).not.toBe(false);
|
||||
// Should add user input and error message to history
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{ type: MessageType.USER, text: '/mcp-command' },
|
||||
expect.any(Number),
|
||||
);
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.ERROR,
|
||||
text: 'Unknown command: /nonexistent',
|
||||
},
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
@@ -769,19 +798,17 @@ describe('useSlashCommandProcessor', () => {
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
let handled: Awaited<
|
||||
ReturnType<typeof result.current.handleSlashCommand>
|
||||
>;
|
||||
await act(async () => {
|
||||
// Use uppercase when command is lowercase
|
||||
await result.current.handleSlashCommand('/Test');
|
||||
handled = await result.current.handleSlashCommand('/Test');
|
||||
});
|
||||
|
||||
// It should fail and call addItem with an error
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: MessageType.ERROR,
|
||||
text: 'Unknown command: /Test',
|
||||
},
|
||||
expect.any(Number),
|
||||
);
|
||||
// Case mismatch means it's not a known command, so treat as regular input
|
||||
expect(handled!).toBe(false);
|
||||
expect(mockAddItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should correctly match an altName', async () => {
|
||||
|
||||
@@ -362,6 +362,36 @@ export const useSlashCommandProcessor = (
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
commandToExecute,
|
||||
args,
|
||||
canonicalPath: resolvedCommandPath,
|
||||
} = parseSlashCommand(trimmed, commands);
|
||||
|
||||
// If the input doesn't match any known command, check if MCP servers
|
||||
// are still loading (the command might come from an MCP server).
|
||||
// Otherwise, treat it as regular text input (e.g. file paths like
|
||||
// /home/user/file.txt) and let it be sent to the model.
|
||||
if (!commandToExecute) {
|
||||
const isMcpLoading =
|
||||
config?.getMcpClientManager()?.getDiscoveryState() ===
|
||||
MCPDiscoveryState.IN_PROGRESS;
|
||||
if (isMcpLoading) {
|
||||
setIsProcessing(true);
|
||||
if (addToHistory) {
|
||||
addItem({ type: MessageType.USER, text: trimmed }, Date.now());
|
||||
}
|
||||
addMessage({
|
||||
type: MessageType.ERROR,
|
||||
content: `Unknown command: ${trimmed}. Command might have been from an MCP server but MCP servers are not done loading.`,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
setIsProcessing(false);
|
||||
return { type: 'handled' };
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsProcessing(true);
|
||||
|
||||
if (addToHistory) {
|
||||
@@ -373,11 +403,6 @@ export const useSlashCommandProcessor = (
|
||||
}
|
||||
|
||||
let hasError = false;
|
||||
const {
|
||||
commandToExecute,
|
||||
args,
|
||||
canonicalPath: resolvedCommandPath,
|
||||
} = parseSlashCommand(trimmed, commands);
|
||||
|
||||
const subcommand =
|
||||
resolvedCommandPath.length > 1
|
||||
@@ -654,19 +679,6 @@ export const useSlashCommandProcessor = (
|
||||
}
|
||||
}
|
||||
|
||||
const isMcpLoading =
|
||||
config?.getMcpClientManager()?.getDiscoveryState() ===
|
||||
MCPDiscoveryState.IN_PROGRESS;
|
||||
const errorMessage = isMcpLoading
|
||||
? `Unknown command: ${trimmed}. Command might have been from an MCP server but MCP servers are not done loading.`
|
||||
: `Unknown command: ${trimmed}`;
|
||||
|
||||
addMessage({
|
||||
type: MessageType.ERROR,
|
||||
content: errorMessage,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
|
||||
return { type: 'handled' };
|
||||
} catch (e: unknown) {
|
||||
hasError = true;
|
||||
|
||||
Reference in New Issue
Block a user