diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 8c3cd9900c..b03ae3e8f5 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -26,7 +26,9 @@ import * as ServerConfig from '@google/gemini-cli-core'; import { isWorkspaceTrusted } from './trustedFolders.js'; import { ExtensionManager } from './extension-manager.js'; -import { RESUME_LATEST } from '../utils/sessionUtils.js'; +import { + RESUME_LAST_IN_CURRENT_FOLDER, +} from '../utils/sessionUtils.js'; vi.mock('./trustedFolders.js', () => ({ isWorkspaceTrusted: vi.fn(() => ({ isTrusted: true, source: 'file' })), // Default to trusted @@ -548,14 +550,27 @@ describe('parseArguments', () => { } }); - it('should return RESUME_LATEST constant when --resume is passed without a value', async () => { + it('should return RESUME_LAST_IN_CURRENT_FOLDER when --resume is passed without a value', async () => { const originalIsTTY = process.stdin.isTTY; process.stdin.isTTY = true; // Make it interactive to avoid validation error process.argv = ['node', 'script.js', '--resume']; try { const argv = await parseArguments(createTestMergedSettings()); - expect(argv.resume).toBe(RESUME_LATEST); + expect(argv.resume).toBe(RESUME_LAST_IN_CURRENT_FOLDER); + expect(argv.resume).toBe('__last_in_current_folder__'); + } finally { + process.stdin.isTTY = originalIsTTY; + } + }); + + it('should keep explicit --resume latest unchanged', async () => { + const originalIsTTY = process.stdin.isTTY; + process.stdin.isTTY = true; + process.argv = ['node', 'script.js', '--resume', 'latest']; + + try { + const argv = await parseArguments(createTestMergedSettings()); expect(argv.resume).toBe('latest'); } finally { process.stdin.isTTY = originalIsTTY; diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 7bd34929b3..3d8db9daf9 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -53,7 +53,10 @@ import { import { loadSandboxConfig } from './sandboxConfig.js'; import { resolvePath } from '../utils/resolvePath.js'; -import { RESUME_LATEST } from '../utils/sessionUtils.js'; +import { + RESUME_LAST_IN_CURRENT_FOLDER, + RESUME_LATEST, +} from '../utils/sessionUtils.js'; import { isWorkspaceTrusted } from './trustedFolders.js'; import { createPolicyEngineConfig } from './policy.js'; @@ -81,7 +84,11 @@ export interface CliArgs { experimentalAcp: boolean | undefined; extensions: string[] | undefined; listExtensions: boolean | undefined; - resume: string | typeof RESUME_LATEST | undefined; + resume: + | string + | typeof RESUME_LATEST + | typeof RESUME_LAST_IN_CURRENT_FOLDER + | undefined; listSessions: boolean | undefined; deleteSession: string | undefined; includeDirectories: string[] | undefined; @@ -224,14 +231,14 @@ export async function parseArguments( // one, and not being passed at all. skipValidation: true, description: - 'Resume a previous session by name, index, UUID, or "latest" (e.g. --resume my-chat-abc12)', + 'Resume a previous session by name, index, UUID, or "latest". If passed without a value, resumes the most recent session from the current folder when available.', coerce: (value: string): string => { // When --resume passed with a value (`gemini --resume 123`): value = "123" (string) // When --resume passed without a value (`gemini --resume`): value = "" (string) // When --resume not passed at all: this `coerce` function is not called at all, and // `yargsInstance.argv.resume` is undefined. if (value === '') { - return RESUME_LATEST; + return RESUME_LAST_IN_CURRENT_FOLDER; } return value; }, diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 284153ffc0..7b092d2d5a 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -109,6 +109,9 @@ import { runDeferredCommand } from './deferred.js'; import { SlashCommandConflictHandler } from './services/SlashCommandConflictHandler.js'; const SLOW_RENDER_MS = 200; +type ResumedSessionDataWithNotice = ResumedSessionData & { + resumeNotice?: string; +}; export function validateDnsResolutionOrder( order: string | undefined, @@ -677,17 +680,19 @@ export async function main() { ]; // Handle --resume flag - let resumedSessionData: ResumedSessionData | undefined = undefined; + let resumedSessionData: ResumedSessionDataWithNotice | undefined = undefined; if (argv.resume) { const sessionSelector = new SessionSelector(config); try { const result = await sessionSelector.resolveSession(argv.resume); - resumedSessionData = { + const resolvedSessionData: ResumedSessionDataWithNotice = { conversation: result.sessionData, filePath: result.sessionPath, + resumeNotice: result.resumeNotice, }; + resumedSessionData = resolvedSessionData; // Use the existing session ID to continue recording to the same session - config.setSessionId(resumedSessionData.conversation.sessionId); + config.setSessionId(resolvedSessionData.conversation.sessionId); } catch (error) { coreEvents.emitFeedback( 'error', diff --git a/packages/cli/src/ui/components/SessionBrowser.test.tsx b/packages/cli/src/ui/components/SessionBrowser.test.tsx index 2aef319b20..9ead8ab72c 100644 --- a/packages/cli/src/ui/components/SessionBrowser.test.tsx +++ b/packages/cli/src/ui/components/SessionBrowser.test.tsx @@ -221,6 +221,52 @@ describe('SessionBrowser component', () => { expect(lastFrame()).toMatchSnapshot(); }); + it('highlights the latest session in the current folder', () => { + const currentFolderLatest = createSession({ + id: 'folder-latest', + sessionName: 'folder-latest-abcde', + projectRoot: process.cwd(), + lastUpdated: '2025-01-03T10:00:00Z', + index: 0, + }); + const currentFolderOlder = createSession({ + id: 'folder-older', + sessionName: 'folder-older-abcde', + projectRoot: process.cwd(), + lastUpdated: '2025-01-01T10:00:00Z', + index: 1, + }); + const otherFolder = createSession({ + id: 'other-folder', + sessionName: 'other-folder-abcde', + projectRoot: '/tmp/other-project', + lastUpdated: '2025-01-04T10:00:00Z', + index: 2, + }); + + const config = createMockConfig(); + const onResumeSession = vi.fn(); + const onDeleteSession = vi.fn().mockResolvedValue(undefined); + const onRenameSession = vi.fn(); + const onExit = vi.fn(); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain( + 'Latest in this folder: folder-latest-abcde', + ); + expect(lastFrame()).toContain('[last in folder]'); + }); + it('enters search mode, filters sessions, and renders match snippets', async () => { const searchSession = createSession({ id: 'search1', diff --git a/packages/cli/src/ui/components/SessionBrowser.tsx b/packages/cli/src/ui/components/SessionBrowser.tsx index a83818ca5f..e7f0ff3dbc 100644 --- a/packages/cli/src/ui/components/SessionBrowser.tsx +++ b/packages/cli/src/ui/components/SessionBrowser.tsx @@ -88,6 +88,8 @@ export interface SessionBrowserState { endIndex: number; /** Sessions visible on current page */ visibleSessions: SessionInfo[]; + /** Latest session id in the current folder (if any) */ + lastSessionInCurrentFolderId?: string; // State setters /** Update sessions array */ @@ -432,10 +434,35 @@ const isSessionFromCurrentFolder = (projectRoot?: string): boolean => { } }; +const parseTimestamp = (timestamp: string): number => { + const parsed = new Date(timestamp).getTime(); + return Number.isNaN(parsed) ? 0 : parsed; +}; + +const getLastSessionInCurrentFolderId = ( + sessions: SessionInfo[], +): string | undefined => { + const currentFolderSessions = sessions.filter((session) => + isSessionFromCurrentFolder(session.projectRoot), + ); + if (currentFolderSessions.length === 0) { + return undefined; + } + + const latest = currentFolderSessions.reduce((best, candidate) => + parseTimestamp(candidate.lastUpdated) > parseTimestamp(best.lastUpdated) + ? candidate + : best, + ); + return latest.id; +}; + const SessionDetailsPanel = ({ session, + lastSessionInCurrentFolderId, }: { session: SessionInfo | undefined; + lastSessionInCurrentFolderId?: string; }): React.JSX.Element | null => { if (!session) { return null; @@ -462,10 +489,38 @@ const SessionDetailsPanel = ({ {formatTimestamp(session.lastUpdated)} + {session.id === lastSessionInCurrentFolderId && ( + + This is your latest session in this folder. + + )} ); }; +const CurrentFolderSessionHint = ({ + state, +}: { + state: SessionBrowserState; +}): React.JSX.Element | null => { + if (!state.lastSessionInCurrentFolderId) { + return null; + } + + const latestCurrentFolderSession = state.sessions.find( + (session) => session.id === state.lastSessionInCurrentFolderId, + ); + if (!latestCurrentFolderSession) { + return null; + } + + return ( + + Latest in this folder: {latestCurrentFolderSession.sessionName} + + ); +}; + /** * Match snippet display component for search results. */ @@ -518,6 +573,8 @@ const SessionItem = ({ state.startIndex + state.visibleSessions.indexOf(session); const isActive = originalIndex === state.activeIndex; const isDisabled = session.isCurrentSession; + const isLastSessionInCurrentFolder = + session.id === state.lastSessionInCurrentFolderId; const isCurrentFolder = isSessionFromCurrentFolder(session.projectRoot); const activeColor = isCurrentFolder ? Colors.AccentCyan : Colors.AccentPurple; const textColor = (c: string = Colors.Foreground) => { @@ -535,6 +592,9 @@ const SessionItem = ({ if (session.isCurrentSession) { additionalInfo = ' (current)'; } + if (isLastSessionInCurrentFolder) { + additionalInfo += ' [last in folder]'; + } // Show match snippets if searching and matches exist if ( @@ -606,7 +666,15 @@ const SessionItem = ({ {truncatedMessage} {additionalInfo && ( - + {additionalInfo} )} @@ -686,6 +754,10 @@ export const useSessionBrowserState = ( }, [searchQuery]); const totalSessions = filteredAndSortedSessions.length; + const lastSessionInCurrentFolderId = useMemo( + () => getLastSessionInCurrentFolderId(sessions), + [sessions], + ); const startIndex = scrollOffset; const endIndex = Math.min(scrollOffset + SESSIONS_PER_PAGE, totalSessions); const visibleSessions = filteredAndSortedSessions.slice(startIndex, endIndex); @@ -718,6 +790,7 @@ export const useSessionBrowserState = ( terminalWidth, filteredAndSortedSessions, totalSessions, + lastSessionInCurrentFolderId, startIndex, endIndex, visibleSessions, @@ -734,6 +807,8 @@ const useLoadSessions = (config: Config, state: SessionBrowserState) => { setSessions, setLoading, setError, + setActiveIndex, + setScrollOffset, isSearchMode, hasLoadedFullContent, setHasLoadedFullContent, @@ -747,6 +822,25 @@ const useLoadSessions = (config: Config, state: SessionBrowserState) => { config.getProjectRoot(), ); setSessions(sessionData); + const latestCurrentFolderSessionId = + getLastSessionInCurrentFolderId(sessionData); + if (latestCurrentFolderSessionId) { + const sortedByDefault = sortSessions(sessionData, 'date', false); + const targetIndex = sortedByDefault.findIndex( + (session) => session.id === latestCurrentFolderSessionId, + ); + if (targetIndex >= 0) { + setActiveIndex(targetIndex); + setScrollOffset( + Math.max( + 0, + targetIndex >= SESSIONS_PER_PAGE + ? targetIndex - SESSIONS_PER_PAGE + 1 + : 0, + ), + ); + } + } setLoading(false); } catch (err) { setError( @@ -758,7 +852,14 @@ const useLoadSessions = (config: Config, state: SessionBrowserState) => { // eslint-disable-next-line @typescript-eslint/no-floating-promises loadSessions(); - }, [config, setSessions, setLoading, setError]); + }, [ + config, + setSessions, + setLoading, + setError, + setActiveIndex, + setScrollOffset, + ]); useEffect(() => { const loadFullContent = async () => { @@ -1056,6 +1157,7 @@ export function SessionBrowserView({ return ( + {state.isSearchMode && } {state.isRenameMode && } @@ -1066,7 +1168,12 @@ export function SessionBrowserView({ )} - {!state.isSearchMode && } + {!state.isSearchMode && ( + + )} ); } diff --git a/packages/cli/src/ui/hooks/useSessionResume.test.ts b/packages/cli/src/ui/hooks/useSessionResume.test.ts index 9350cc167a..a3a051ed4d 100644 --- a/packages/cli/src/ui/hooks/useSessionResume.test.ts +++ b/packages/cli/src/ui/hooks/useSessionResume.test.ts @@ -16,6 +16,11 @@ import type { } from '@google/gemini-cli-core'; import type { UseHistoryManagerReturn } from './useHistoryManager.js'; import type { HistoryItemWithoutId } from '../types.js'; +import { MessageType } from '../types.js'; + +type ResumedSessionDataWithNotice = ResumedSessionData & { + resumeNotice?: string; +}; describe('useSessionResume', () => { // Mock dependencies @@ -75,7 +80,7 @@ describe('useSessionResume', () => { { role: 'model' as const, parts: [{ text: 'Hi there!' }] }, ]; - const resumedData: ResumedSessionData = { + const resumedData: ResumedSessionDataWithNotice = { conversation: { sessionId: 'test-123', projectHash: 'project-123', @@ -130,7 +135,7 @@ describe('useSessionResume', () => { const clientHistory = [ { role: 'user' as const, parts: [{ text: 'Hello' }] }, ]; - const resumedData: ResumedSessionData = { + const resumedData: ResumedSessionDataWithNotice = { conversation: { sessionId: 'test-123', projectHash: 'project-123', @@ -157,7 +162,7 @@ describe('useSessionResume', () => { it('should handle empty history arrays', async () => { const { result } = renderHook(() => useSessionResume(getDefaultProps())); - const resumedData: ResumedSessionData = { + const resumedData: ResumedSessionDataWithNotice = { conversation: { sessionId: 'test-123', projectHash: 'project-123', @@ -178,6 +183,35 @@ describe('useSessionResume', () => { expect(mockGeminiClient.resumeChat).toHaveBeenCalledWith([], resumedData); }); + it('should add an info notice to history when resumeNotice is provided', async () => { + const { result } = renderHook(() => useSessionResume(getDefaultProps())); + + const resumedData: ResumedSessionDataWithNotice = { + conversation: { + sessionId: 'test-123', + projectHash: 'project-123', + startTime: '2025-01-01T00:00:00Z', + lastUpdated: '2025-01-01T01:00:00Z', + messages: [] as MessageRecord[], + }, + filePath: '/path/to/session.json', + resumeNotice: 'Resumed most recent session in this folder: test-session-abcde', + }; + + await act(async () => { + await result.current.loadHistoryForResume([], [], resumedData); + }); + + expect(mockHistoryManager.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: resumedData.resumeNotice, + }, + expect.any(Number), + true, + ); + }); + it('should restore directories from resumed session data', async () => { const mockAddDirectories = vi .fn() diff --git a/packages/cli/src/ui/hooks/useSessionResume.ts b/packages/cli/src/ui/hooks/useSessionResume.ts index 9889c4bd12..e7f314f741 100644 --- a/packages/cli/src/ui/hooks/useSessionResume.ts +++ b/packages/cli/src/ui/hooks/useSessionResume.ts @@ -11,17 +11,22 @@ import { type ResumedSessionData, } from '@google/gemini-cli-core'; import type { Part } from '@google/genai'; +import { MessageType } from '../types.js'; import type { HistoryItemWithoutId } from '../types.js'; import type { UseHistoryManagerReturn } from './useHistoryManager.js'; import { convertSessionToHistoryFormats } from './useSessionBrowser.js'; +type ResumedSessionDataWithNotice = ResumedSessionData & { + resumeNotice?: string; +}; + interface UseSessionResumeParams { config: Config; historyManager: UseHistoryManagerReturn; refreshStatic: () => void; isGeminiClientInitialized: boolean; setQuittingMessages: (messages: null) => void; - resumedSessionData?: ResumedSessionData; + resumedSessionData?: ResumedSessionDataWithNotice; isAuthenticating: boolean; } @@ -54,7 +59,7 @@ export function useSessionResume({ async ( uiHistory: HistoryItemWithoutId[], clientHistory: Array<{ role: 'user' | 'model'; parts: Part[] }>, - resumedData: ResumedSessionData, + resumedData: ResumedSessionDataWithNotice, ) => { // Wait for the client. if (!isGeminiClientInitialized) { @@ -84,6 +89,17 @@ export function useSessionResume({ // Give the history to the Gemini client. await config.getGeminiClient()?.resumeChat(clientHistory, resumedData); + + if (resumedData.resumeNotice) { + historyManagerRef.current.addItem( + { + type: MessageType.INFO, + text: resumedData.resumeNotice, + }, + Date.now(), + true, + ); + } } catch (error) { coreEvents.emitFeedback( 'error', diff --git a/packages/cli/src/utils/sessionUtils.test.ts b/packages/cli/src/utils/sessionUtils.test.ts index 01adfd5442..ad5ded169f 100644 --- a/packages/cli/src/utils/sessionUtils.test.ts +++ b/packages/cli/src/utils/sessionUtils.test.ts @@ -6,6 +6,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { + RESUME_LAST_IN_CURRENT_FOLDER, SessionSelector, extractFirstUserMessage, formatRelativeTime, @@ -49,11 +50,14 @@ describe('SessionSelector', () => { } }); - const createChatsDir = async () => { - const projectDir = path.join(tmpDir, 'project-a'); + const createChatsDir = async ( + projectId = 'project-a', + root = projectRoot, + ) => { + const projectDir = path.join(tmpDir, projectId); const chatsDir = path.join(projectDir, 'chats'); await fs.mkdir(chatsDir, { recursive: true }); - await fs.writeFile(path.join(projectDir, '.project_root'), projectRoot); + await fs.writeFile(path.join(projectDir, '.project_root'), root); return chatsDir; }; @@ -245,6 +249,146 @@ describe('SessionSelector', () => { expect(result.sessionData.messages[0].content).toBe('Latest session'); }); + it('should resolve the latest session from the current folder for bare --resume', async () => { + const sessionInCurrentFolderOld = randomUUID(); + const sessionInCurrentFolderLatest = randomUUID(); + const sessionInOtherFolderLatest = randomUUID(); + + const projectAChatsDir = await createChatsDir('project-a', projectRoot); + const projectBChatsDir = await createChatsDir( + 'project-b', + '/workspace/project-b', + ); + + await fs.writeFile( + path.join( + projectAChatsDir, + `${SESSION_FILE_PREFIX}2024-01-01T10-00-${sessionInCurrentFolderOld.slice(0, 8)}.json`, + ), + JSON.stringify( + { + sessionId: sessionInCurrentFolderOld, + projectHash: 'project-a-hash', + startTime: '2024-01-01T10:00:00.000Z', + lastUpdated: '2024-01-01T10:05:00.000Z', + messages: [ + { + type: 'user', + content: 'old current-folder session', + id: 'msg1', + timestamp: '2024-01-01T10:00:00.000Z', + }, + ], + }, + null, + 2, + ), + ); + + await fs.writeFile( + path.join( + projectAChatsDir, + `${SESSION_FILE_PREFIX}2024-01-01T11-00-${sessionInCurrentFolderLatest.slice(0, 8)}.json`, + ), + JSON.stringify( + { + sessionId: sessionInCurrentFolderLatest, + projectHash: 'project-a-hash', + startTime: '2024-01-01T11:00:00.000Z', + lastUpdated: '2024-01-01T11:30:00.000Z', + messages: [ + { + type: 'user', + content: 'latest current-folder session', + id: 'msg1', + timestamp: '2024-01-01T11:00:00.000Z', + }, + ], + }, + null, + 2, + ), + ); + + await fs.writeFile( + path.join( + projectBChatsDir, + `${SESSION_FILE_PREFIX}2024-01-01T12-00-${sessionInOtherFolderLatest.slice(0, 8)}.json`, + ), + JSON.stringify( + { + sessionId: sessionInOtherFolderLatest, + projectHash: 'project-b-hash', + startTime: '2024-01-01T12:00:00.000Z', + lastUpdated: '2024-01-01T12:30:00.000Z', + messages: [ + { + type: 'user', + content: 'latest other-folder session', + id: 'msg1', + timestamp: '2024-01-01T12:00:00.000Z', + }, + ], + }, + null, + 2, + ), + ); + + const sessionSelector = new SessionSelector(config); + const result = await sessionSelector.resolveSession( + RESUME_LAST_IN_CURRENT_FOLDER, + ); + + expect(result.sessionData.sessionId).toBe(sessionInCurrentFolderLatest); + expect(result.resumeNotice).toContain( + 'Resumed most recent session in this folder', + ); + }); + + it('should fall back to global latest when current folder has no sessions for bare --resume', async () => { + const sessionInOtherFolderLatest = randomUUID(); + const projectBChatsDir = await createChatsDir( + 'project-b', + '/workspace/project-b', + ); + + await fs.writeFile( + path.join( + projectBChatsDir, + `${SESSION_FILE_PREFIX}2024-01-01T12-00-${sessionInOtherFolderLatest.slice(0, 8)}.json`, + ), + JSON.stringify( + { + sessionId: sessionInOtherFolderLatest, + projectHash: 'project-b-hash', + startTime: '2024-01-01T12:00:00.000Z', + lastUpdated: '2024-01-01T12:30:00.000Z', + messages: [ + { + type: 'user', + content: 'latest other-folder session', + id: 'msg1', + timestamp: '2024-01-01T12:00:00.000Z', + }, + ], + }, + null, + 2, + ), + ); + + const sessionSelector = new SessionSelector(config); + const result = await sessionSelector.resolveSession( + RESUME_LAST_IN_CURRENT_FOLDER, + ); + + expect(result.sessionData.sessionId).toBe(sessionInOtherFolderLatest); + expect(result.resumeNotice).toContain( + 'No previous sessions found in this folder', + ); + }); + it('should deduplicate sessions by ID', async () => { const sessionId = randomUUID(); diff --git a/packages/cli/src/utils/sessionUtils.ts b/packages/cli/src/utils/sessionUtils.ts index 883026e761..735395bdb3 100644 --- a/packages/cli/src/utils/sessionUtils.ts +++ b/packages/cli/src/utils/sessionUtils.ts @@ -31,9 +31,14 @@ import { /** * Constant for the resume "latest" identifier. - * Used when --resume is passed without a value to select the most recent session. + * Used when --resume latest is passed to select the most recent session globally. */ export const RESUME_LATEST = 'latest'; +/** + * Internal identifier used when --resume is passed without a value. + * Prefers the most recent session from the current folder, falling back to global latest. + */ +export const RESUME_LAST_IN_CURRENT_FOLDER = '__last_in_current_folder__'; /** * Error codes for session-related errors. @@ -157,6 +162,7 @@ export interface SessionSelectionResult { sessionPath: string; sessionData: ConversationRecord; displayInfo: string; + resumeNotice?: string; } export interface RenameSessionResult { @@ -638,6 +644,35 @@ export async function deleteSessionArtifacts(session: SessionInfo): Promise + new Date(a.startTime).getTime() - new Date(b.startTime).getTime(), + ); + return sorted[sorted.length - 1]; + } + + private getLatestSessionFromCurrentFolder( + sessions: SessionInfo[], + ): SessionInfo | undefined { + const currentProjectRoot = this.config.getProjectRoot(); + const matchingSessions = sessions.filter( + (session) => + session.projectRoot && + path.resolve(session.projectRoot) === path.resolve(currentProjectRoot), + ); + + if (matchingSessions.length === 0) { + return undefined; + } + + const sorted = [...matchingSessions].sort( + (a, b) => + new Date(a.lastUpdated).getTime() - new Date(b.lastUpdated).getTime(), + ); + return sorted[sorted.length - 1]; + } + /** * Lists all available sessions globally across projects. */ @@ -707,20 +742,31 @@ export class SessionSelector { async resolveSession(resumeArg: string): Promise { let selectedSession: SessionInfo; - if (resumeArg === RESUME_LATEST) { + let resumeNotice: string | undefined; + if (resumeArg === RESUME_LAST_IN_CURRENT_FOLDER) { const sessions = await this.listSessions(); if (sessions.length === 0) { throw new Error('No previous sessions found.'); } - // Sort by startTime (oldest first, so newest sessions get highest numbers) - sessions.sort( - (a, b) => - new Date(a.startTime).getTime() - new Date(b.startTime).getTime(), - ); + const currentFolderSession = + this.getLatestSessionFromCurrentFolder(sessions); + if (currentFolderSession) { + selectedSession = currentFolderSession; + resumeNotice = `Resumed most recent session in this folder: ${selectedSession.sessionName}`; + } else { + selectedSession = this.getLatestSession(sessions); + resumeNotice = `No previous sessions found in this folder. Resumed latest global session: ${selectedSession.sessionName}`; + } + } else if (resumeArg === RESUME_LATEST) { + const sessions = await this.listSessions(); - selectedSession = sessions[sessions.length - 1]; + if (sessions.length === 0) { + throw new Error('No previous sessions found.'); + } + + selectedSession = this.getLatestSession(sessions); } else { try { selectedSession = await this.findSession(resumeArg); @@ -736,7 +782,7 @@ export class SessionSelector { } } - return this.selectSession(selectedSession); + return this.selectSession(selectedSession, resumeNotice); } /** @@ -744,6 +790,7 @@ export class SessionSelector { */ private async selectSession( sessionInfo: SessionInfo, + resumeNotice?: string, ): Promise { const sessionPath = sessionInfo.sessionPath; @@ -758,6 +805,7 @@ export class SessionSelector { sessionPath: sessionInfo.sessionPath, sessionData, displayInfo, + resumeNotice, }; } catch (error) { throw new Error(