feat(core,cli): implement session-linked tool output storage and cleanup (#18416)

This commit is contained in:
Abhi
2026-02-06 01:36:42 -05:00
committed by GitHub
parent 8ec176e005
commit 30354892b3
12 changed files with 442 additions and 386 deletions

View File

@@ -8,8 +8,9 @@ import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import {
debugLogger,
sanitizeFilenamePart,
Storage,
TOOL_OUTPUT_DIR,
TOOL_OUTPUTS_DIR,
type Config,
} from '@google/gemini-cli-core';
import type { Settings, SessionRetentionSettings } from '../config/settings.js';
@@ -101,6 +102,19 @@ export async function cleanupExpiredSessions(
} catch {
/* ignore if log doesn't exist */
}
// ALSO cleanup tool outputs for this session
const safeSessionId = sanitizeFilenamePart(sessionId);
const toolOutputDir = path.join(
config.storage.getProjectTempDir(),
TOOL_OUTPUTS_DIR,
`session-${safeSessionId}`,
);
try {
await fs.rm(toolOutputDir, { recursive: true, force: true });
} catch {
/* ignore if doesn't exist */
}
}
if (config.getDebugMode()) {
@@ -350,7 +364,7 @@ export async function cleanupToolOutputFiles(
const retentionConfig = settings.general.sessionRetention;
const tempDir =
projectTempDir ?? new Storage(process.cwd()).getProjectTempDir();
const toolOutputDir = path.join(tempDir, TOOL_OUTPUT_DIR);
const toolOutputDir = path.join(tempDir, TOOL_OUTPUTS_DIR);
// Check if directory exists
try {
@@ -360,15 +374,16 @@ export async function cleanupToolOutputFiles(
return result;
}
// Get all files in the tool_output directory
// Get all entries in the tool-outputs directory
const entries = await fs.readdir(toolOutputDir, { withFileTypes: true });
const files = entries.filter((e) => e.isFile());
result.scanned = files.length;
result.scanned = entries.length;
if (files.length === 0) {
if (entries.length === 0) {
return result;
}
const files = entries.filter((e) => e.isFile());
// Get file stats for age-based cleanup (parallel for better performance)
const fileStatsResults = await Promise.all(
files.map(async (file) => {
@@ -430,6 +445,43 @@ export async function cleanupToolOutputFiles(
}
}
// For now, continue to cleanup individual files in the root tool-outputs dir
// but also scan and cleanup expired session subdirectories.
const subdirs = entries.filter(
(e) => e.isDirectory() && e.name.startsWith('session-'),
);
for (const subdir of subdirs) {
try {
// Security: Validate that the subdirectory name is a safe filename part
// and doesn't attempt path traversal.
if (subdir.name !== sanitizeFilenamePart(subdir.name)) {
debugLogger.debug(
`Skipping unsafe tool-output subdirectory: ${subdir.name}`,
);
continue;
}
const subdirPath = path.join(toolOutputDir, subdir.name);
const stat = await fs.stat(subdirPath);
let shouldDelete = false;
if (retentionConfig.maxAge) {
const maxAgeMs = parseRetentionPeriod(retentionConfig.maxAge);
const cutoffDate = new Date(now.getTime() - maxAgeMs);
if (stat.mtime < cutoffDate) {
shouldDelete = true;
}
}
if (shouldDelete) {
await fs.rm(subdirPath, { recursive: true, force: true });
result.deleted++; // Count as one "unit" of deletion for stats
}
} catch (error) {
debugLogger.debug(`Failed to cleanup subdir ${subdir.name}: ${error}`);
}
}
// Delete the files
for (const fileName of filesToDelete) {
try {