From 6c6761862427279455256e7c99cc45a2f17583ca Mon Sep 17 00:00:00 2001 From: Louis Jimenez Date: Fri, 20 Jun 2025 00:39:15 -0400 Subject: [PATCH] Make checkpoints configurable in settings.json (#1251) --- .../cli/src/config/config.integration.test.ts | 18 ++++++++++++++++++ packages/cli/src/config/config.ts | 6 +++--- packages/cli/src/config/settings.ts | 5 +++++ packages/cli/src/gemini.tsx | 2 +- packages/cli/src/ui/App.test.tsx | 2 +- .../src/ui/hooks/slashCommandProcessor.test.ts | 2 +- .../cli/src/ui/hooks/slashCommandProcessor.ts | 8 ++++---- .../cli/src/ui/hooks/useGeminiStream.test.tsx | 2 +- packages/cli/src/ui/hooks/useGeminiStream.ts | 6 +++--- packages/cli/src/utils/cleanup.ts | 5 +++-- packages/core/src/config/config.ts | 15 ++++++++++----- 11 files changed, 50 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/config/config.integration.test.ts b/packages/cli/src/config/config.integration.test.ts index 4a965a1f..de329384 100644 --- a/packages/cli/src/config/config.integration.test.ts +++ b/packages/cli/src/config/config.integration.test.ts @@ -186,4 +186,22 @@ describe('Configuration Integration Tests', () => { expect(config.getFileFilteringRespectGitIgnore()).toBe(false); }); }); + + describe('Checkpointing Configuration', () => { + it('should enable checkpointing when the setting is true', async () => { + const configParams: ConfigParameters = { + cwd: '/tmp', + contentGeneratorConfig: TEST_CONTENT_GENERATOR_CONFIG, + embeddingModel: 'test-embedding-model', + sandbox: false, + targetDir: tempDir, + debugMode: false, + checkpointing: true, + }; + + const config = new Config(configParams); + + expect(config.getCheckpointingEnabled()).toBe(true); + }); + }); }); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 6e52c6e2..afc63b78 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -49,7 +49,7 @@ interface CliArgs { show_memory_usage: boolean | undefined; yolo: boolean | undefined; telemetry: boolean | undefined; - checkpoint: boolean | undefined; + checkpointing: boolean | undefined; telemetryTarget: string | undefined; telemetryOtlpEndpoint: string | undefined; telemetryLogPrompts: boolean | undefined; @@ -122,7 +122,7 @@ async function parseArguments(): Promise { description: 'Enable or disable logging of user prompts for telemetry. Overrides settings files.', }) - .option('checkpoint', { + .option('checkpointing', { alias: 'c', type: 'boolean', description: 'Enables checkpointing of file edits', @@ -229,7 +229,7 @@ export async function loadCliConfig( }, // Git-aware file filtering settings fileFilteringRespectGitIgnore: settings.fileFiltering?.respectGitIgnore, - checkpoint: argv.checkpoint, + checkpointing: argv.checkpointing || settings.checkpointing?.enabled, proxy: process.env.HTTPS_PROXY || process.env.https_proxy || diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index a90ed2d8..b63f5bb6 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -27,6 +27,10 @@ export enum SettingScope { Workspace = 'Workspace', } +export interface CheckpointingSettings { + enabled?: boolean; +} + export interface AccessibilitySettings { disableLoadingPhrases?: boolean; } @@ -47,6 +51,7 @@ export interface Settings { telemetry?: TelemetrySettings; preferredEditor?: string; bugCommand?: BugCommandSettings; + checkpointing?: CheckpointingSettings; // Git-aware file filtering settings fileFiltering?: { diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 8dd52117..10352f0b 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -51,7 +51,7 @@ export async function main() { // Initialize centralized FileDiscoveryService config.getFileService(); - if (config.getCheckpointEnabled()) { + if (config.getCheckpointingEnabled()) { try { await config.getGitService(); } catch { diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index dca24b5c..c60e5143 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -123,7 +123,7 @@ vi.mock('@gemini-cli/core', async (importOriginal) => { getAccessibility: vi.fn(() => opts.accessibility ?? {}), getProjectRoot: vi.fn(() => opts.projectRoot), getGeminiClient: vi.fn(() => ({})), - getCheckpointEnabled: vi.fn(() => opts.checkpoint ?? true), + getCheckpointingEnabled: vi.fn(() => opts.checkpointing ?? true), getAllGeminiMdFilenames: vi.fn(() => ['GEMINI.md']), }; }); diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts index 04931c7f..03f8cc9c 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts @@ -135,7 +135,7 @@ describe('useSlashCommandProcessor', () => { getSandbox: vi.fn(() => 'test-sandbox'), getModel: vi.fn(() => 'test-model'), getProjectRoot: vi.fn(() => '/test/dir'), - getCheckpointEnabled: vi.fn(() => true), + getCheckpointingEnabled: vi.fn(() => true), getBugCommand: vi.fn(() => undefined), } as unknown as Config; mockCorgiMode = vi.fn(); diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index ee7b55cb..8195f005 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -647,7 +647,7 @@ Add any other context about the problem here. description: 'resume from conversation checkpoint. Usage: /resume [tag]', completion: async () => { - const geminiDir = config?.getGeminiDir(); + const geminiDir = config?.getProjectTempDir(); if (!geminiDir) { return []; } @@ -805,14 +805,14 @@ Add any other context about the problem here. }, ]; - if (config?.getCheckpointEnabled()) { + if (config?.getCheckpointingEnabled()) { commands.push({ name: 'restore', description: 'restore a tool call. This will reset the conversation and file history to the state it was in when the tool call was suggested', action: async (_mainCommand, subCommand, _args) => { - const checkpointDir = config?.getGeminiDir() - ? path.join(config.getGeminiDir(), 'checkpoints') + const checkpointDir = config?.getProjectTempDir() + ? path.join(config.getProjectTempDir(), 'checkpoints') : undefined; if (!checkpointDir) { diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx index 6fcdcf34..487738c3 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx +++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx @@ -280,7 +280,7 @@ describe('useGeminiStream', () => { () => ({ getToolSchemaList: vi.fn(() => []) }) as any, ), getProjectRoot: vi.fn(() => '/test/dir'), - getCheckpointEnabled: vi.fn(() => false), + getCheckpointingEnabled: vi.fn(() => false), getGeminiClient: mockGetGeminiClient, addHistory: vi.fn(), } as unknown as Config; diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index 86c35836..234652db 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -638,7 +638,7 @@ export const useGeminiStream = ( useEffect(() => { const saveRestorableToolCalls = async () => { - if (!config.getCheckpointEnabled()) { + if (!config.getCheckpointingEnabled()) { return; } const restorableToolCalls = toolCalls.filter( @@ -649,8 +649,8 @@ export const useGeminiStream = ( ); if (restorableToolCalls.length > 0) { - const checkpointDir = config.getGeminiDir() - ? path.join(config.getGeminiDir(), 'checkpoints') + const checkpointDir = config.getProjectTempDir() + ? path.join(config.getProjectTempDir(), 'checkpoints') : undefined; if (!checkpointDir) { diff --git a/packages/cli/src/utils/cleanup.ts b/packages/cli/src/utils/cleanup.ts index 1e483373..747bbf8e 100644 --- a/packages/cli/src/utils/cleanup.ts +++ b/packages/cli/src/utils/cleanup.ts @@ -6,10 +6,11 @@ import { promises as fs } from 'fs'; import { join } from 'path'; +import { getProjectTempDir } from '@gemini-cli/core'; export async function cleanupCheckpoints() { - const geminiDir = join(process.cwd(), '.gemini'); - const checkpointsDir = join(geminiDir, 'checkpoints'); + const tempDir = getProjectTempDir(process.cwd()); + const checkpointsDir = join(tempDir, 'checkpoints'); try { await fs.rm(checkpointsDir, { recursive: true, force: true }); } catch { diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index ff458505..514fc717 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -27,6 +27,7 @@ import { GeminiClient } from '../core/client.js'; import { GEMINI_CONFIG_DIR as GEMINI_DIR } from '../tools/memoryTool.js'; import { FileDiscoveryService } from '../services/fileDiscoveryService.js'; import { GitService } from '../services/gitService.js'; +import { getProjectTempDir } from '../utils/paths.js'; import { initializeTelemetry, DEFAULT_TELEMETRY_TARGET, @@ -104,7 +105,7 @@ export interface ConfigParameters { accessibility?: AccessibilitySettings; telemetry?: TelemetrySettings; fileFilteringRespectGitIgnore?: boolean; - checkpoint?: boolean; + checkpointing?: boolean; proxy?: string; cwd: string; fileDiscoveryService?: FileDiscoveryService; @@ -138,7 +139,7 @@ export class Config { private readonly fileFilteringRespectGitIgnore: boolean; private fileDiscoveryService: FileDiscoveryService | null = null; private gitService: GitService | undefined = undefined; - private readonly checkpoint: boolean; + private readonly checkpointing: boolean; private readonly proxy: string | undefined; private readonly cwd: string; private readonly bugCommand: BugCommandSettings | undefined; @@ -173,7 +174,7 @@ export class Config { this.fileFilteringRespectGitIgnore = params.fileFilteringRespectGitIgnore ?? true; - this.checkpoint = params.checkpoint ?? false; + this.checkpointing = params.checkpointing ?? false; this.proxy = params.proxy; this.cwd = params.cwd ?? process.cwd(); this.fileDiscoveryService = params.fileDiscoveryService ?? null; @@ -325,12 +326,16 @@ export class Config { return path.join(this.targetDir, GEMINI_DIR); } + getProjectTempDir(): string { + return getProjectTempDir(this.getProjectRoot()); + } + getFileFilteringRespectGitIgnore(): boolean { return this.fileFilteringRespectGitIgnore; } - getCheckpointEnabled(): boolean { - return this.checkpoint; + getCheckpointingEnabled(): boolean { + return this.checkpointing; } getProxy(): string | undefined {