From e824cb897a190d67b958f70b04f63334885bea46 Mon Sep 17 00:00:00 2001 From: Jack Wotherspoon Date: Mon, 9 Mar 2026 23:53:31 +0100 Subject: [PATCH] chore: address PR comments --- packages/cli/src/gemini.tsx | 30 ++++-- packages/cli/src/nonInteractiveCli.ts | 96 ++++++++++--------- packages/cli/src/ui/auth/AuthDialog.tsx | 2 +- .../src/ui/components/FolderTrustDialog.tsx | 6 +- packages/core/src/config/config.ts | 6 +- 5 files changed, 81 insertions(+), 59 deletions(-) diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 60813ed3ba..c7135059da 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -738,6 +738,14 @@ export async function main() { } } + // When this is an auto-restart (not explicit --resume), clear the + // original --prompt so it doesn't get submitted again — the resumed + // session already contains it. + if (isAutoRestart && resumedSessionData) { + config.clearQuestion(); + input = undefined; + } + cliStartupHandle?.end(); // Render UI, passing necessary config values. Check that there is no command line question. if (config.isInteractive()) { @@ -793,7 +801,7 @@ export async function main() { await config.getHookSystem()?.fireSessionEndEvent(SessionEndReason.Exit); }); - if (!input) { + if (!input && !resumedSessionData) { debugLogger.error( `No input provided via stdin. Input can be provided by piping data into gemini or using the --prompt option.`, ); @@ -802,15 +810,17 @@ export async function main() { } const prompt_id = Math.random().toString(16).slice(2); - logUserPrompt( - config, - new UserPromptEvent( - input.length, - prompt_id, - config.getContentGeneratorConfig()?.authType, - input, - ), - ); + if (input) { + logUserPrompt( + config, + new UserPromptEvent( + input.length, + prompt_id, + config.getContentGeneratorConfig()?.authType, + input, + ), + ); + } const authType = await validateNonInteractiveAuth( settings.merged.security.auth.selectedType, diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index c2cab72353..07d7e52691 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -50,7 +50,7 @@ import { TextOutput } from './ui/utils/textOutput.js'; interface RunNonInteractiveParams { config: Config; settings: LoadedSettings; - input: string; + input?: string; prompt_id: string; resumedSessionData?: ResumedSessionData; } @@ -237,56 +237,62 @@ export async function runNonInteractive({ }); } - let query: Part[] | undefined; + let currentMessages: Content[] = []; - if (isSlashCommand(input)) { - const slashCommandResult = await handleSlashCommand( - input, - abortController, - config, - settings, - ); - // If a slash command is found and returns a prompt, use it. - // Otherwise, slashCommandResult falls through to the default prompt - // handling. - if (slashCommandResult) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - query = slashCommandResult as Part[]; - } - } + // When resuming with no new input (e.g. auto-restart), skip sending + // a new user message — the resumed session already contains it. + if (input) { + let query: Part[] | undefined; - if (!query) { - const { processedQuery, error } = await handleAtCommand({ - query: input, - config, - addItem: (_item, _timestamp) => 0, - onDebugMessage: () => {}, - messageId: Date.now(), - signal: abortController.signal, - }); - - if (error || !processedQuery) { - // An error occurred during @include processing (e.g., file not found). - // The error message is already logged by handleAtCommand. - throw new FatalInputError( - error || 'Exiting due to an error processing the @ command.', + if (isSlashCommand(input)) { + const slashCommandResult = await handleSlashCommand( + input, + abortController, + config, + settings, ); + // If a slash command is found and returns a prompt, use it. + // Otherwise, slashCommandResult falls through to the default prompt + // handling. + if (slashCommandResult) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + query = slashCommandResult as Part[]; + } } - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - query = processedQuery as Part[]; - } - // Emit user message event for streaming JSON - if (streamFormatter) { - streamFormatter.emitEvent({ - type: JsonStreamEventType.MESSAGE, - timestamp: new Date().toISOString(), - role: 'user', - content: input, - }); - } + if (!query) { + const { processedQuery, error } = await handleAtCommand({ + query: input, + config, + addItem: (_item, _timestamp) => 0, + onDebugMessage: () => {}, + messageId: Date.now(), + signal: abortController.signal, + }); - let currentMessages: Content[] = [{ role: 'user', parts: query }]; + if (error || !processedQuery) { + // An error occurred during @include processing (e.g., file not found). + // The error message is already logged by handleAtCommand. + throw new FatalInputError( + error || 'Exiting due to an error processing the @ command.', + ); + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + query = processedQuery as Part[]; + } + + // Emit user message event for streaming JSON + if (streamFormatter) { + streamFormatter.emitEvent({ + type: JsonStreamEventType.MESSAGE, + timestamp: new Date().toISOString(), + role: 'user', + content: input, + }); + } + + currentMessages = [{ role: 'user', parts: query }]; + } let turnCount = 0; while (true) { diff --git a/packages/cli/src/ui/auth/AuthDialog.tsx b/packages/cli/src/ui/auth/AuthDialog.tsx index 58956e5f86..bb5cf04379 100644 --- a/packages/cli/src/ui/auth/AuthDialog.tsx +++ b/packages/cli/src/ui/auth/AuthDialog.tsx @@ -132,7 +132,7 @@ export function AuthDialog({ config.isBrowserLaunchSuppressed() ) { setExiting(true); - setTimeout(relaunchApp, 100); + setTimeout(() => relaunchApp(config.getSessionId()), 100); return; } diff --git a/packages/cli/src/ui/components/FolderTrustDialog.tsx b/packages/cli/src/ui/components/FolderTrustDialog.tsx index 5bb748b28f..066d195983 100644 --- a/packages/cli/src/ui/components/FolderTrustDialog.tsx +++ b/packages/cli/src/ui/components/FolderTrustDialog.tsx @@ -18,6 +18,7 @@ import * as process from 'node:process'; import * as path from 'node:path'; import { relaunchApp } from '../../utils/processUtils.js'; import { runExitCleanup } from '../../utils/cleanup.js'; +import { useConfig } from '../contexts/ConfigContext.js'; import { ExitCodes, type FolderDiscoveryResults, @@ -45,6 +46,7 @@ export const FolderTrustDialog: React.FC = ({ isRestarting, discoveryResults, }) => { + const config = useConfig(); const [exiting, setExiting] = useState(false); const { terminalHeight, terminalWidth, constrainHeight } = useUIState(); const isAlternateBuffer = useAlternateBuffer(); @@ -54,12 +56,12 @@ export const FolderTrustDialog: React.FC = ({ useEffect(() => { let timer: ReturnType; if (isRestarting) { - timer = setTimeout(relaunchApp, 250); + timer = setTimeout(() => relaunchApp(config.getSessionId()), 250); } return () => { if (timer) clearTimeout(timer); }; - }, [isRestarting]); + }, [isRestarting, config]); const handleExit = useCallback(() => { setExiting(true); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index f615564533..bed14b281f 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -625,7 +625,7 @@ export class Config implements McpContext, AgentLoopContext { private readonly targetDir: string; private workspaceContext: WorkspaceContext; private readonly debugMode: boolean; - private readonly question: string | undefined; + private question: string | undefined; readonly enableConseca: boolean; private readonly coreTools: string[] | undefined; @@ -1655,6 +1655,10 @@ export class Config implements McpContext, AgentLoopContext { return this.question; } + clearQuestion(): void { + this.question = undefined; + } + getHasAccessToPreviewModel(): boolean { return this.hasAccessToPreviewModel !== false; }