From 178388d9314f506c96855a3d2e5fa0fc19c36e07 Mon Sep 17 00:00:00 2001 From: skyvanguard Date: Wed, 18 Feb 2026 18:52:51 -0300 Subject: [PATCH] fix(cli): treat unknown slash commands as regular input instead of showing error (#17393) Co-authored-by: Tommaso Sciortino --- .../ui/hooks/slashCommandProcessor.test.tsx | 61 +++++++++++++------ .../cli/src/ui/hooks/slashCommandProcessor.ts | 48 +++++++++------ 2 files changed, 74 insertions(+), 35 deletions(-) diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx b/packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx index 7fc947ba48..6190d163f7 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx @@ -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 + >; 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); + + const result = await setupProcessorHook(); + await waitFor(() => expect(result.current.slashCommands).toBeDefined()); + + let handled: Awaited< + ReturnType + >; + 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 + >; 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 () => { diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index 4f9bb8def0..be8e313abe 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -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;