fix(core): dynamic session ID injection to resolve resume bugs (#24972)

This commit is contained in:
Tommaso Sciortino
2026-04-08 23:27:24 +00:00
committed by GitHub
parent 80764c8bb5
commit d06dba3538
33 changed files with 165 additions and 189 deletions

View File

@@ -15,7 +15,7 @@ import {
} from './sessionUtils.js';
import {
SESSION_FILE_PREFIX,
type Config,
type Storage,
type MessageRecord,
CoreToolCallStatus,
} from '@google/gemini-cli-core';
@@ -25,20 +25,17 @@ import { randomUUID } from 'node:crypto';
describe('SessionSelector', () => {
let tmpDir: string;
let config: Config;
let storage: Storage;
beforeEach(async () => {
// Create a temporary directory for testing
tmpDir = path.join(process.cwd(), '.tmp-test-sessions');
await fs.mkdir(tmpDir, { recursive: true });
// Mock config
config = {
storage: {
getProjectTempDir: () => tmpDir,
},
getSessionId: () => 'current-session-id',
} as Partial<Config> as Config;
// Mock storage
storage = {
getProjectTempDir: () => tmpDir,
} as Partial<Storage> as Storage;
});
afterEach(async () => {
@@ -104,7 +101,7 @@ describe('SessionSelector', () => {
JSON.stringify(session2, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
// Test resolving by UUID
const result1 = await sessionSelector.resolveSession(sessionId1);
@@ -170,7 +167,7 @@ describe('SessionSelector', () => {
JSON.stringify(session2, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
// Test resolving by index (1-based)
const result1 = await sessionSelector.resolveSession('1');
@@ -234,7 +231,7 @@ describe('SessionSelector', () => {
JSON.stringify(session2, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
// Test resolving latest
const result = await sessionSelector.resolveSession('latest');
@@ -271,7 +268,7 @@ describe('SessionSelector', () => {
JSON.stringify(session, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
// Test resolving by UUID with leading/trailing spaces
const result = await sessionSelector.resolveSession(` ${sessionId} `);
@@ -334,7 +331,7 @@ describe('SessionSelector', () => {
JSON.stringify(sessionDuplicate, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
const sessions = await sessionSelector.listSessions();
expect(sessions.length).toBe(1);
@@ -373,7 +370,7 @@ describe('SessionSelector', () => {
JSON.stringify(session1, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
await expect(
sessionSelector.resolveSession('invalid-uuid'),
@@ -389,14 +386,11 @@ describe('SessionSelector', () => {
const chatsDir = path.join(tmpDir, 'chats');
await fs.mkdir(chatsDir, { recursive: true });
const emptyConfig = {
storage: {
getProjectTempDir: () => tmpDir,
},
getSessionId: () => 'current-session-id',
} as Partial<Config> as Config;
const emptyStorage = {
getProjectTempDir: () => tmpDir,
} as Partial<Storage> as Storage;
const sessionSelector = new SessionSelector(emptyConfig);
const sessionSelector = new SessionSelector(emptyStorage);
await expect(sessionSelector.resolveSession('latest')).rejects.toSatisfy(
(error) => {
@@ -469,7 +463,7 @@ describe('SessionSelector', () => {
JSON.stringify(sessionSystemOnly, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
const sessions = await sessionSelector.listSessions();
// Should only list the session with user message
@@ -508,7 +502,7 @@ describe('SessionSelector', () => {
JSON.stringify(sessionGeminiOnly, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
const sessions = await sessionSelector.listSessions();
// Should list the session with gemini message
@@ -574,7 +568,7 @@ describe('SessionSelector', () => {
JSON.stringify(subagentSession, null, 2),
);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(storage);
const sessions = await sessionSelector.listSessions();
// Should only list the main session

View File

@@ -9,7 +9,7 @@ import {
partListUnionToString,
SESSION_FILE_PREFIX,
CoreToolCallStatus,
type Config,
type Storage,
type ConversationRecord,
type MessageRecord,
} from '@google/gemini-cli-core';
@@ -399,17 +399,14 @@ export const getSessionFiles = async (
* Utility class for session discovery and selection.
*/
export class SessionSelector {
constructor(private config: Config) {}
constructor(private storage: Storage) {}
/**
* Lists all available sessions for the current project.
*/
async listSessions(): Promise<SessionInfo[]> {
const chatsDir = path.join(
this.config.storage.getProjectTempDir(),
'chats',
);
return getSessionFiles(chatsDir, this.config.getSessionId());
const chatsDir = path.join(this.storage.getProjectTempDir(), 'chats');
return getSessionFiles(chatsDir);
}
/**
@@ -452,10 +449,7 @@ export class SessionSelector {
return sortedSessions[index - 1];
}
const chatsDir = path.join(
this.config.storage.getProjectTempDir(),
'chats',
);
const chatsDir = path.join(this.storage.getProjectTempDir(), 'chats');
throw SessionError.invalidSessionIdentifier(trimmedIdentifier, chatsDir);
}
@@ -507,10 +501,7 @@ export class SessionSelector {
private async selectSession(
sessionInfo: SessionInfo,
): Promise<SessionSelectionResult> {
const chatsDir = path.join(
this.config.storage.getProjectTempDir(),
'chats',
);
const chatsDir = path.join(this.storage.getProjectTempDir(), 'chats');
const sessionPath = path.join(chatsDir, sessionInfo.fileName);
try {

View File

@@ -21,7 +21,7 @@ export async function listSessions(config: Config): Promise<void> {
// Generate summary for most recent session if needed
await generateSummary(config);
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(config.storage);
const sessions = await sessionSelector.listSessions();
if (sessions.length === 0) {
@@ -55,7 +55,7 @@ export async function deleteSession(
config: Config,
sessionIndex: string,
): Promise<void> {
const sessionSelector = new SessionSelector(config);
const sessionSelector = new SessionSelector(config.storage);
const sessions = await sessionSelector.listSessions();
if (sessions.length === 0) {