mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(core): reset session-scoped state on resumption (#26342)
This commit is contained in:
@@ -23,6 +23,7 @@ import { createMockSandboxConfig } from '@google/gemini-cli-test-utils';
|
||||
import { DEFAULT_MAX_ATTEMPTS } from '../utils/retry.js';
|
||||
import { ExperimentFlags } from '../code_assist/experiments/flagNames.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { coreEvents } from '../utils/events.js';
|
||||
import { ApprovalMode } from '../policy/types.js';
|
||||
import {
|
||||
HookType,
|
||||
@@ -1940,6 +1941,70 @@ describe('Server Config (config.ts)', () => {
|
||||
expect(config.getSessionId()).toBe('session-two');
|
||||
expect(config.getApprovedPlanPath()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('performs a comprehensive reset of all session-scoped state when sessionId changes', async () => {
|
||||
const config = new Config({
|
||||
...baseParams,
|
||||
sessionId: 'session-one',
|
||||
plan: true,
|
||||
tracker: true,
|
||||
});
|
||||
|
||||
await config.initialize();
|
||||
|
||||
// 1. "Dirty" the session state
|
||||
const oldTrackerService = config.getTrackerService();
|
||||
config.setApprovedPlanPath('/tmp/plan.md');
|
||||
config.topicState.setTopic('Old Topic', 'Old Intent');
|
||||
config.getSkillManager().activateSkill('old-skill');
|
||||
config.getModelAvailabilityService().markTerminal('model-1', 'quota');
|
||||
config.setLatestApiRequest({} as never);
|
||||
|
||||
// Interface to access private fields without 'any'
|
||||
interface PrivateConfig {
|
||||
modelQuotas: Map<string, unknown>;
|
||||
lastEmittedQuotaRemaining: number | undefined;
|
||||
lastEmittedQuotaLimit: number | undefined;
|
||||
lastQuotaFetchTime: number;
|
||||
hasAccessToPreviewModel: boolean | null;
|
||||
}
|
||||
const configInternal = config as unknown as PrivateConfig;
|
||||
|
||||
// Mock internal quota state
|
||||
configInternal.modelQuotas.set('model-1', { remaining: 0, limit: 100 });
|
||||
configInternal.lastEmittedQuotaRemaining = 0;
|
||||
configInternal.lastEmittedQuotaLimit = 100;
|
||||
configInternal.lastQuotaFetchTime = 12345;
|
||||
configInternal.hasAccessToPreviewModel = true;
|
||||
|
||||
// Listen for quota event
|
||||
const emitQuotaSpy = vi.spyOn(coreEvents, 'emitQuotaChanged');
|
||||
|
||||
// 2. Trigger session change
|
||||
config.setSessionId('session-two');
|
||||
|
||||
// 3. Verify EVERYTHING is reset
|
||||
expect(config.getSessionId()).toBe('session-two');
|
||||
expect(config.getApprovedPlanPath()).toBeUndefined();
|
||||
expect(config.topicState.getTopic()).toBeUndefined();
|
||||
expect(config.topicState.getIntent()).toBeUndefined();
|
||||
expect(config.getSkillManager().isSkillActive('old-skill')).toBe(false);
|
||||
expect(config.getTrackerService()).not.toBe(oldTrackerService);
|
||||
expect(
|
||||
config.getModelAvailabilityService().snapshot('model-1').available,
|
||||
).toBe(true);
|
||||
expect(config.getLatestApiRequest()).toBeUndefined();
|
||||
|
||||
// Quota resets
|
||||
expect(configInternal.modelQuotas.size).toBe(0);
|
||||
expect(configInternal.lastEmittedQuotaRemaining).toBeUndefined();
|
||||
expect(configInternal.lastEmittedQuotaLimit).toBeUndefined();
|
||||
expect(configInternal.lastQuotaFetchTime).toBe(0);
|
||||
expect(configInternal.hasAccessToPreviewModel).toBeNull();
|
||||
|
||||
// Event emission
|
||||
expect(emitQuotaSpy).toHaveBeenCalledWith(undefined, undefined, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GemmaModelRouterSettings', () => {
|
||||
|
||||
@@ -1803,6 +1803,24 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
this._sessionId = sessionId;
|
||||
this.storage.setSessionId(sessionId);
|
||||
this.trackerService = undefined;
|
||||
this.approvedPlanPath = undefined;
|
||||
this.topicState.reset();
|
||||
this.skillManager.reset();
|
||||
this.latestApiRequest = undefined;
|
||||
this.lastModeSwitchTime = performance.now();
|
||||
this.compressionTruncationCounter = 0;
|
||||
this.quotaErrorOccurred = false;
|
||||
this.creditsNotificationShown = false;
|
||||
this.modelAvailabilityService.reset();
|
||||
this.modelQuotas.clear();
|
||||
this.lastRetrievedQuota = undefined;
|
||||
this.lastQuotaFetchTime = 0;
|
||||
this.hasAccessToPreviewModel = null;
|
||||
|
||||
// Force an event emission to clear the UI display
|
||||
coreEvents.emitQuotaChanged(undefined, undefined, undefined);
|
||||
this.lastEmittedQuotaRemaining = undefined;
|
||||
this.lastEmittedQuotaLimit = undefined;
|
||||
|
||||
if (previousPlansDir) {
|
||||
this.refreshSessionScopedPlansDirectory(previousPlansDir);
|
||||
@@ -1811,7 +1829,6 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
|
||||
resetNewSessionState(sessionId: string): void {
|
||||
this.setSessionId(sessionId);
|
||||
this.approvedPlanPath = undefined;
|
||||
}
|
||||
|
||||
setTerminalBackground(terminalBackground: string | undefined): void {
|
||||
|
||||
@@ -318,6 +318,20 @@ description: project-desc
|
||||
expect(service.isAdminEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset active skill names', () => {
|
||||
const service = new SkillManager();
|
||||
service.activateSkill('skill-1');
|
||||
service.activateSkill('skill-2');
|
||||
|
||||
expect(service.isSkillActive('skill-1')).toBe(true);
|
||||
expect(service.isSkillActive('skill-2')).toBe(true);
|
||||
|
||||
service.reset();
|
||||
|
||||
expect(service.isSkillActive('skill-1')).toBe(false);
|
||||
expect(service.isSkillActive('skill-2')).toBe(false);
|
||||
});
|
||||
|
||||
describe('Conflict Detection', () => {
|
||||
it('should emit UI warning when a non-built-in skill is overridden', async () => {
|
||||
const emitFeedbackSpy = vi.spyOn(coreEvents, 'emitFeedback');
|
||||
|
||||
@@ -26,6 +26,13 @@ export class SkillManager {
|
||||
this.skills = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets session-scoped state (active skill names).
|
||||
*/
|
||||
reset(): void {
|
||||
this.activeSkillNames.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets administrative settings for skills.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user