mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-23 17:56:55 -07:00
Merge branch 'main' into pr4-cli
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
shutdownTelemetry,
|
||||
isTelemetrySdkInitialized,
|
||||
ExitCodes,
|
||||
resetBrowserSession,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
|
||||
@@ -72,6 +73,13 @@ export async function runExitCleanup() {
|
||||
}
|
||||
cleanupFunctions.length = 0; // Clear the array
|
||||
|
||||
// Close persistent browser sessions before disposing config
|
||||
try {
|
||||
await resetBrowserSession();
|
||||
} catch (_) {
|
||||
// Ignore errors during browser cleanup
|
||||
}
|
||||
|
||||
if (configForTelemetry) {
|
||||
try {
|
||||
await configForTelemetry.dispose();
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
coreEvents,
|
||||
getErrorType,
|
||||
getErrorMessage,
|
||||
getErrorType,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { runSyncCleanup } from './cleanup.js';
|
||||
|
||||
@@ -178,7 +179,7 @@ export function handleCancellationError(config: Config): never {
|
||||
timestamp: new Date().toISOString(),
|
||||
status: 'error',
|
||||
error: {
|
||||
type: 'FatalCancellationError',
|
||||
type: getErrorType(cancellationError),
|
||||
message: cancellationError.message,
|
||||
},
|
||||
stats: streamFormatter.convertToStreamStats(metrics, 0),
|
||||
@@ -219,7 +220,7 @@ export function handleMaxTurnsExceededError(config: Config): never {
|
||||
timestamp: new Date().toISOString(),
|
||||
status: 'error',
|
||||
error: {
|
||||
type: 'FatalTurnLimitedError',
|
||||
type: getErrorType(maxTurnsError),
|
||||
message: maxTurnsError.message,
|
||||
},
|
||||
stats: streamFormatter.convertToStreamStats(metrics, 0),
|
||||
|
||||
@@ -197,7 +197,9 @@ describe('handleAutoUpdate', () => {
|
||||
|
||||
expect(updateEventEmitter.emit).toHaveBeenCalledTimes(1);
|
||||
expect(updateEventEmitter.emit).toHaveBeenCalledWith('update-received', {
|
||||
...mockUpdateInfo,
|
||||
message: 'An update is available!\nPlease update manually.',
|
||||
isUpdating: false,
|
||||
});
|
||||
expect(mockSpawn).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -236,7 +238,9 @@ describe('handleAutoUpdate', () => {
|
||||
|
||||
expect(updateEventEmitter.emit).toHaveBeenCalledTimes(1);
|
||||
expect(updateEventEmitter.emit).toHaveBeenCalledWith('update-received', {
|
||||
...mockUpdateInfo,
|
||||
message: 'An update is available!\nCannot determine update command.',
|
||||
isUpdating: false,
|
||||
});
|
||||
expect(mockSpawn).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -253,7 +257,9 @@ describe('handleAutoUpdate', () => {
|
||||
|
||||
expect(updateEventEmitter.emit).toHaveBeenCalledTimes(1);
|
||||
expect(updateEventEmitter.emit).toHaveBeenCalledWith('update-received', {
|
||||
...mockUpdateInfo,
|
||||
message: 'An update is available!\nThis is an additional message.',
|
||||
isUpdating: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -102,17 +102,22 @@ export function handleAutoUpdate(
|
||||
combinedMessage += `\n${installationInfo.updateMessage}`;
|
||||
}
|
||||
|
||||
updateEventEmitter.emit('update-received', {
|
||||
message: combinedMessage,
|
||||
});
|
||||
|
||||
if (
|
||||
!installationInfo.updateCommand ||
|
||||
!settings.merged.general.enableAutoUpdate
|
||||
) {
|
||||
updateEventEmitter.emit('update-received', {
|
||||
...info,
|
||||
message: combinedMessage,
|
||||
isUpdating: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
updateEventEmitter.emit('update-received', {
|
||||
...info,
|
||||
message: combinedMessage,
|
||||
isUpdating: true,
|
||||
});
|
||||
if (_updateInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,8 @@ describe('Session Cleanup (Refactored)', () => {
|
||||
);
|
||||
// Session directory
|
||||
await fs.mkdir(path.join(testTempDir, sessionId), { recursive: true });
|
||||
// Subagent chats directory
|
||||
await fs.mkdir(path.join(chatsDir, sessionId), { recursive: true });
|
||||
}
|
||||
|
||||
async function seedSessions() {
|
||||
@@ -274,6 +276,7 @@ describe('Session Cleanup (Refactored)', () => {
|
||||
existsSync(path.join(toolOutputsDir, `session-${sessions[1].id}`)),
|
||||
).toBe(false);
|
||||
expect(existsSync(path.join(testTempDir, sessions[1].id))).toBe(false); // Session directory should be deleted
|
||||
expect(existsSync(path.join(chatsDir, sessions[1].id))).toBe(false); // Subagent chats directory should be deleted
|
||||
});
|
||||
|
||||
it('should NOT delete sessions within the cutoff date', async () => {
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
Storage,
|
||||
TOOL_OUTPUTS_DIR,
|
||||
type Config,
|
||||
deleteSessionArtifactsAsync,
|
||||
deleteSubagentSessionDirAndArtifactsAsync,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Settings, SessionRetentionSettings } from '../config/settings.js';
|
||||
import { getAllSessionFiles, type SessionFileEntry } from './sessionUtils.js';
|
||||
@@ -59,48 +61,18 @@ function deriveShortIdFromFileName(fileName: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the log path for a session ID.
|
||||
*/
|
||||
function getSessionLogPath(tempDir: string, safeSessionId: string): string {
|
||||
return path.join(tempDir, 'logs', `session-${safeSessionId}.jsonl`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up associated artifacts (logs, tool-outputs, directory) for a session.
|
||||
*/
|
||||
async function deleteSessionArtifactsAsync(
|
||||
async function cleanupSessionAndSubagentsAsync(
|
||||
sessionId: string,
|
||||
config: Config,
|
||||
): Promise<void> {
|
||||
const tempDir = config.storage.getProjectTempDir();
|
||||
const chatsDir = path.join(tempDir, 'chats');
|
||||
|
||||
// Cleanup logs
|
||||
const logsDir = path.join(tempDir, 'logs');
|
||||
const safeSessionId = sanitizeFilenamePart(sessionId);
|
||||
const logPath = getSessionLogPath(tempDir, safeSessionId);
|
||||
if (logPath.startsWith(logsDir)) {
|
||||
await fs.unlink(logPath).catch(() => {});
|
||||
}
|
||||
|
||||
// Cleanup tool outputs
|
||||
const toolOutputDir = path.join(
|
||||
tempDir,
|
||||
TOOL_OUTPUTS_DIR,
|
||||
`session-${safeSessionId}`,
|
||||
);
|
||||
const toolOutputsBase = path.join(tempDir, TOOL_OUTPUTS_DIR);
|
||||
if (toolOutputDir.startsWith(toolOutputsBase)) {
|
||||
await fs
|
||||
.rm(toolOutputDir, { recursive: true, force: true })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
// Cleanup session directory
|
||||
const sessionDir = path.join(tempDir, safeSessionId);
|
||||
if (safeSessionId && sessionDir.startsWith(tempDir + path.sep)) {
|
||||
await fs.rm(sessionDir, { recursive: true, force: true }).catch(() => {});
|
||||
}
|
||||
await deleteSessionArtifactsAsync(sessionId, tempDir);
|
||||
await deleteSubagentSessionDirAndArtifactsAsync(sessionId, chatsDir, tempDir);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +173,7 @@ export async function cleanupExpiredSessions(
|
||||
await fs.unlink(filePath);
|
||||
|
||||
if (fullSessionId) {
|
||||
await deleteSessionArtifactsAsync(fullSessionId, config);
|
||||
await cleanupSessionAndSubagentsAsync(fullSessionId, config);
|
||||
}
|
||||
result.deleted++;
|
||||
} else {
|
||||
@@ -230,7 +202,7 @@ export async function cleanupExpiredSessions(
|
||||
|
||||
const sessionId = sessionToDelete.sessionInfo?.id;
|
||||
if (sessionId) {
|
||||
await deleteSessionArtifactsAsync(sessionId, config);
|
||||
await cleanupSessionAndSubagentsAsync(sessionId, config);
|
||||
}
|
||||
|
||||
if (config.getDebugMode()) {
|
||||
|
||||
@@ -97,7 +97,7 @@ export async function deleteSession(
|
||||
try {
|
||||
// Use ChatRecordingService to delete the session
|
||||
const chatRecordingService = new ChatRecordingService(config);
|
||||
chatRecordingService.deleteSession(sessionToDelete.file);
|
||||
await chatRecordingService.deleteSession(sessionToDelete.file);
|
||||
|
||||
const time = formatRelativeTime(sessionToDelete.lastUpdated);
|
||||
writeToStdout(
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('terminal notifications', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns false without writing on non-macOS platforms', async () => {
|
||||
it('emits notification on non-macOS platforms', async () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'linux',
|
||||
configurable: true,
|
||||
@@ -54,8 +54,8 @@ describe('terminal notifications', () => {
|
||||
body: 'b',
|
||||
});
|
||||
|
||||
expect(shown).toBe(false);
|
||||
expect(writeToStdout).not.toHaveBeenCalled();
|
||||
expect(shown).toBe(true);
|
||||
expect(writeToStdout).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns false without writing when disabled', async () => {
|
||||
@@ -69,6 +69,7 @@ describe('terminal notifications', () => {
|
||||
});
|
||||
|
||||
it('emits OSC 9 notification when supported terminal is detected', async () => {
|
||||
vi.stubEnv('WT_SESSION', '');
|
||||
vi.stubEnv('TERM_PROGRAM', 'iTerm.app');
|
||||
|
||||
const shown = await notifyViaTerminal(true, {
|
||||
@@ -126,6 +127,7 @@ describe('terminal notifications', () => {
|
||||
});
|
||||
|
||||
it('strips terminal control sequences and newlines from payload text', async () => {
|
||||
vi.stubEnv('WT_SESSION', '');
|
||||
vi.stubEnv('TERM_PROGRAM', 'iTerm.app');
|
||||
|
||||
const shown = await notifyViaTerminal(true, {
|
||||
|
||||
@@ -75,17 +75,10 @@ export function buildRunEventNotificationContent(
|
||||
|
||||
export function isNotificationsEnabled(settings: LoadedSettings): boolean {
|
||||
const general = settings.merged.general as
|
||||
| {
|
||||
enableNotifications?: boolean;
|
||||
enableMacOsNotifications?: boolean;
|
||||
}
|
||||
| { enableNotifications?: boolean }
|
||||
| undefined;
|
||||
|
||||
return (
|
||||
process.platform === 'darwin' &&
|
||||
(general?.enableNotifications === true ||
|
||||
general?.enableMacOsNotifications === true)
|
||||
);
|
||||
return general?.enableNotifications === true;
|
||||
}
|
||||
|
||||
function buildTerminalNotificationMessage(
|
||||
@@ -112,7 +105,7 @@ export async function notifyViaTerminal(
|
||||
notificationsEnabled: boolean,
|
||||
content: RunEventNotificationContent,
|
||||
): Promise<boolean> {
|
||||
if (!notificationsEnabled || process.platform !== 'darwin') {
|
||||
if (!notificationsEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user