diff --git a/packages/cli/src/services/BuiltinCommandLoader.ts b/packages/cli/src/services/BuiltinCommandLoader.ts index 31673e921a0..2e164ce4f3f 100644 --- a/packages/cli/src/services/BuiltinCommandLoader.ts +++ b/packages/cli/src/services/BuiltinCommandLoader.ts @@ -34,6 +34,7 @@ import { extensionsCommand } from '../ui/commands/extensionsCommand.js'; import { helpCommand } from '../ui/commands/helpCommand.js'; import { shortcutsCommand } from '../ui/commands/shortcutsCommand.js'; import { rewindCommand } from '../ui/commands/rewindCommand.js'; +import { undoCommand } from '../ui/commands/undoCommand.js'; import { hooksCommand } from '../ui/commands/hooksCommand.js'; import { ideCommand } from '../ui/commands/ideCommand.js'; import { initCommand } from '../ui/commands/initCommand.js'; @@ -122,6 +123,7 @@ export class BuiltinCommandLoader implements ICommandLoader { shortcutsCommand, ...(this.config?.getEnableHooksUI() ? [hooksCommand] : []), rewindCommand, + undoCommand, await ideCommand(), initCommand, ...(isNightlyBuild ? [oncallCommand] : []), diff --git a/packages/cli/src/ui/commands/rewindCommand.tsx b/packages/cli/src/ui/commands/rewindCommand.tsx index c4af3e845d5..7776376e8ed 100644 --- a/packages/cli/src/ui/commands/rewindCommand.tsx +++ b/packages/cli/src/ui/commands/rewindCommand.tsx @@ -37,7 +37,7 @@ import { * @param messageId The ID of the message to rewind to. * @param newText The new text for the input field after rewinding. */ -async function rewindConversation( +export async function rewindConversation( context: CommandContext, client: GeminiClient, recordingService: ChatRecordingService, diff --git a/packages/cli/src/ui/commands/undoCommand.tsx b/packages/cli/src/ui/commands/undoCommand.tsx new file mode 100644 index 00000000000..e28e0dffdb5 --- /dev/null +++ b/packages/cli/src/ui/commands/undoCommand.tsx @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CommandKind, type SlashCommand } from './types.js'; +import { rewindConversation } from './rewindCommand.js'; +import { partToString } from '@google/gemini-cli-core'; + +export const undoCommand: SlashCommand = { + name: 'undo', + description: 'Revert the last conversation turn', + kind: CommandKind.BUILT_IN, + action: async (context) => { + const client = context.services.config?.getGeminiClient(); + const recordingService = client?.getChatRecordingService(); + const conversation = recordingService?.getConversation(); + + if (!client || !recordingService || !conversation) { + return { + type: 'message', + messageType: 'error', + content: 'Undo unavailable.', + }; + } + + const messages = conversation.messages; + const lastUserIndex = messages.findLastIndex((m) => m.type === 'user'); + + // User message and a model response to undo turn + if (messages.length < 2) { + return { + type: 'message', + messageType: 'info', + content: 'Nothing to undo.', + }; + } + + const targetId = messages[lastUserIndex].id; + const lastUserPrompt = messages[lastUserIndex].content; + + await rewindConversation( + context, + client, + recordingService, + targetId, + partToString(lastUserPrompt), + ); + + return { + type: 'message', + messageType: 'info', + content: 'Undid last turn.', + }; + }, +};