From 323b1298f91c7fe86f5108f8dd369b71ad81a146 Mon Sep 17 00:00:00 2001 From: Taylor Mullen Date: Mon, 19 May 2025 16:11:45 -0700 Subject: [PATCH] fix: Ensure user written `!` is treated opaquely if not in shell mode\n\n- Addresses an issue where commands prefixed with `!` (e.g., `!ls`) were incorrectly handled by the shell command processor if the `!` was added after initially typing the command.\n- Ensures that such commands are correctly forwarded to the Gemini model.\n- Updates `useGeminiStream` to be aware of shell mode to properly manage streaming state.\n\nFixes https://buganizer.corp.google.com/issues/418761305 --- packages/cli/src/ui/App.tsx | 11 +++-------- .../cli/src/ui/hooks/shellCommandProcessor.ts | 5 ----- packages/cli/src/ui/hooks/useGeminiStream.ts | 4 +++- packages/cli/src/ui/utils/commandUtils.ts | 17 ----------------- 4 files changed, 6 insertions(+), 31 deletions(-) diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 30944046..befefec1 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -145,6 +145,7 @@ export const App = ({ config, setDebugMessage, handleSlashCommand, + shellModeActive, ); const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator(streamingState); @@ -154,16 +155,10 @@ export const App = ({ (submittedValue: string) => { const trimmedValue = submittedValue.trim(); if (trimmedValue.length > 0) { - if (shellModeActive && !trimmedValue.startsWith('!')) { - // TODO: Don't prefix (hack) and properly submit pass throughs to a dedicated hook: - // https://b.corp.google.com/issues/418509745 - submitQuery(`!${trimmedValue}`); - } else { - submitQuery(trimmedValue); - } + submitQuery(trimmedValue); } }, - [submitQuery, shellModeActive], + [submitQuery], ); const userMessages = useMemo( diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.ts index f1d9af32..5e43712b 100644 --- a/packages/cli/src/ui/hooks/shellCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/shellCommandProcessor.ts @@ -8,7 +8,6 @@ import { exec as _exec } from 'child_process'; import { useCallback } from 'react'; import { Config } from '@gemini-code/server'; import { type PartListUnion } from '@google/genai'; -import { getCommandFromQuery } from '../utils/commandUtils.js'; import { UseHistoryManagerReturn } from './useHistoryManager.js'; import crypto from 'crypto'; import path from 'path'; @@ -34,10 +33,6 @@ export const useShellCommandProcessor = ( return false; } - const [symbol] = getCommandFromQuery(rawQuery); - if (symbol !== '!' && symbol !== '$') { - return false; - } let commandToExecute = rawQuery.trim().slice(1).trimStart(); // wrap command to write pwd to temporary file diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index 29ce313d..19cb244d 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -60,6 +60,7 @@ export const useGeminiStream = ( config: Config, onDebugMessage: (message: string) => void, handleSlashCommand: (cmd: PartListUnion) => boolean, + shellModeActive: boolean, ) => { const toolRegistry = config.getToolRegistry(); const [initError, setInitError] = useState(null); @@ -120,7 +121,7 @@ export const useGeminiStream = ( if (handleSlashCommand(trimmedQuery)) { return { queryToSend: null, shouldProceed: false }; } - if (handleShellCommand(trimmedQuery)) { + if (shellModeActive && handleShellCommand(trimmedQuery)) { return { queryToSend: null, shouldProceed: false }; } @@ -608,6 +609,7 @@ export const useGeminiStream = ( isResponding, setShowHelp, handleSlashCommand, + shellModeActive, handleShellCommand, config, addItem, diff --git a/packages/cli/src/ui/utils/commandUtils.ts b/packages/cli/src/ui/utils/commandUtils.ts index b17b264d..aadd035e 100644 --- a/packages/cli/src/ui/utils/commandUtils.ts +++ b/packages/cli/src/ui/utils/commandUtils.ts @@ -24,20 +24,3 @@ export const isAtCommand = (query: string): boolean => * @returns True if the query looks like an '/' command, false otherwise. */ export const isSlashCommand = (query: string): boolean => query.startsWith('/'); - -const control_symbols: string[] = ['/', '@', '!', '?', '$']; -/** - * Returns the first word of query with optional leading slash, ampersand, bang. - * - * @param query The input query string. - * @returns optional leading symbol and first word of query - */ -export const getCommandFromQuery = ( - query: string, -): [string | undefined, string] => { - const word = query.trim().split(/\s/, 1)[0]; - if (word.length > 0 && control_symbols.includes(word[0])) { - return [word[0], word.slice(1)]; - } - return [undefined, word]; -};