mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-02 16:04:38 -07:00
feat(cli): add --delete flag to /exit command for session deletion (#19332)
Co-authored-by: David Pierce <davidapierce@google.com>
This commit is contained in:
@@ -735,6 +735,62 @@ describe('ChatRecordingService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteCurrentSessionAsync', () => {
|
||||
it('should asynchronously delete the current session file and tool outputs', async () => {
|
||||
await chatRecordingService.initialize();
|
||||
// Record a message to trigger the file write (writeConversation skips
|
||||
// writing when there are no messages).
|
||||
chatRecordingService.recordMessage({
|
||||
type: 'user',
|
||||
content: 'test',
|
||||
model: 'gemini-pro',
|
||||
});
|
||||
const conversationFile = chatRecordingService.getConversationFilePath();
|
||||
expect(conversationFile).not.toBeNull();
|
||||
|
||||
// Create a tool output directory matching the session ID used by
|
||||
// deleteSessionArtifactsAsync (this.sessionId = mockConfig.promptId).
|
||||
const toolOutputDir = path.join(
|
||||
testTempDir,
|
||||
'tool-outputs',
|
||||
'session-test-session-id',
|
||||
);
|
||||
fs.mkdirSync(toolOutputDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(toolOutputDir, 'output.txt'), 'data');
|
||||
|
||||
expect(fs.existsSync(conversationFile!)).toBe(true);
|
||||
expect(fs.existsSync(toolOutputDir)).toBe(true);
|
||||
|
||||
await chatRecordingService.deleteCurrentSessionAsync();
|
||||
|
||||
expect(fs.existsSync(conversationFile!)).toBe(false);
|
||||
expect(fs.existsSync(toolOutputDir)).toBe(false);
|
||||
});
|
||||
|
||||
it('should not throw if the session was never initialized', async () => {
|
||||
// conversationFile is null when not initialized
|
||||
await expect(
|
||||
chatRecordingService.deleteCurrentSessionAsync(),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw if session file does not exist on disk', async () => {
|
||||
// initialize() writes an initial metadata record synchronously, so
|
||||
// delete the file manually to simulate the "missing on disk" scenario.
|
||||
await chatRecordingService.initialize();
|
||||
const conversationFile = chatRecordingService.getConversationFilePath();
|
||||
expect(conversationFile).not.toBeNull();
|
||||
if (conversationFile && fs.existsSync(conversationFile)) {
|
||||
fs.unlinkSync(conversationFile);
|
||||
}
|
||||
expect(fs.existsSync(conversationFile!)).toBe(false);
|
||||
|
||||
await expect(
|
||||
chatRecordingService.deleteCurrentSessionAsync(),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('recordDirectories', () => {
|
||||
beforeEach(async () => {
|
||||
await chatRecordingService.initialize();
|
||||
|
||||
@@ -792,6 +792,32 @@ export class ChatRecordingService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously deletes the current session's chat file and tool outputs.
|
||||
* This encapsulates the session ID logic and uses non-blocking I/O to avoid
|
||||
* blocking the event loop on exit.
|
||||
*/
|
||||
async deleteCurrentSessionAsync(): Promise<void> {
|
||||
if (!this.conversationFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const tempDir = this.context.config.storage.getProjectTempDir();
|
||||
|
||||
// Delete the conversation file directly using the tracked path.
|
||||
await fs.promises.unlink(this.conversationFile).catch(() => {
|
||||
// File may not exist; ignore.
|
||||
});
|
||||
|
||||
// Delegate tool-output and log cleanup to the shared utility.
|
||||
await deleteSessionArtifactsAsync(this.sessionId, tempDir);
|
||||
} catch (error) {
|
||||
debugLogger.error('Error deleting current session.', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds the conversation to the state just before the specified message ID.
|
||||
* All messages from (and including) the specified ID onwards are removed.
|
||||
|
||||
Reference in New Issue
Block a user