diff --git a/cli/src/commands/gemini.ts b/cli/src/commands/gemini.ts index 964bdb7e9..09b4f95f6 100644 --- a/cli/src/commands/gemini.ts +++ b/cli/src/commands/gemini.ts @@ -15,6 +15,7 @@ export const geminiCommand: CommandDefinition = { startingMode?: 'local' | 'remote' permissionMode?: GeminiPermissionMode model?: string + resumeSessionId?: string } = {} for (let i = 0; i < commandArgs.length; i++) { @@ -30,6 +31,12 @@ export const geminiCommand: CommandDefinition = { } } else if (arg === '--yolo') { options.permissionMode = 'yolo' + } else if (arg === '--resume') { + const sessionId = commandArgs[++i] + if (!sessionId) { + throw new Error('Missing --resume value') + } + options.resumeSessionId = sessionId } else if (arg === '--model') { const model = commandArgs[++i] if (!model) { diff --git a/cli/src/gemini/geminiRemoteLauncher.ts b/cli/src/gemini/geminiRemoteLauncher.ts index 47e631798..40b8ffa61 100644 --- a/cli/src/gemini/geminiRemoteLauncher.ts +++ b/cli/src/gemini/geminiRemoteLauncher.ts @@ -54,7 +54,6 @@ class GeminiRemoteLauncher extends RemoteLauncherBase { const backend = createGeminiBackend({ model: runtimeConfig.model, token: runtimeConfig.token, - resumeSessionId: session.sessionId, hookSettingsPath: this.hookSettingsPath, cwd: session.path, permissionMode: session.getPermissionMode() as string | undefined @@ -69,10 +68,33 @@ class GeminiRemoteLauncher extends RemoteLauncherBase { await backend.initialize(); - const acpSessionId = await backend.newSession({ - cwd: session.path, - mcpServers: toAcpMcpServers(mcpServers) - }); + const resumeSessionId = session.sessionId; + const acpMcpServers = toAcpMcpServers(mcpServers); + let acpSessionId: string; + if (resumeSessionId) { + try { + acpSessionId = await backend.loadSession({ + sessionId: resumeSessionId, + cwd: session.path, + mcpServers: acpMcpServers + }); + } catch (error) { + logger.warn('[gemini-remote] resume failed, starting new session', error); + session.sendSessionEvent({ + type: 'message', + message: 'Gemini resume failed; starting a new session.' + }); + acpSessionId = await backend.newSession({ + cwd: session.path, + mcpServers: acpMcpServers + }); + } + } else { + acpSessionId = await backend.newSession({ + cwd: session.path, + mcpServers: acpMcpServers + }); + } session.onSessionFound(acpSessionId); this.permissionHandler = new GeminiPermissionHandler( @@ -115,12 +137,13 @@ class GeminiRemoteLauncher extends RemoteLauncherBase { this.handleAgentMessage(message); }); } catch (error) { - logger.warn('[gemini-remote] prompt failed', error); + const errorMessage = error instanceof Error ? error.message : String(error); + logger.warn('[gemini-remote] prompt failed', { message: errorMessage }); session.sendSessionEvent({ type: 'message', - message: 'Gemini prompt failed. Check logs for details.' + message: `Gemini prompt failed: ${errorMessage}` }); - messageBuffer.addMessage('Gemini prompt failed', 'status'); + messageBuffer.addMessage(`Gemini prompt failed: ${errorMessage}`, 'status'); } finally { session.onThinkingChange(false); await this.permissionHandler?.cancelAll('Prompt finished'); diff --git a/cli/src/gemini/loop.ts b/cli/src/gemini/loop.ts index d234200ec..c913944c0 100644 --- a/cli/src/gemini/loop.ts +++ b/cli/src/gemini/loop.ts @@ -19,6 +19,7 @@ interface GeminiLoopOptions { model?: string; hookSettingsPath?: string; allowedTools?: string[]; + resumeSessionId?: string; onSessionReady?: (session: GeminiSession) => void; } @@ -31,7 +32,7 @@ export async function geminiLoop(opts: GeminiLoopOptions): Promise { api: opts.api, client: opts.session, path: opts.path, - sessionId: null, + sessionId: opts.resumeSessionId ?? null, logPath, messageQueue: opts.messageQueue, onModeChange: opts.onModeChange, @@ -41,6 +42,10 @@ export async function geminiLoop(opts: GeminiLoopOptions): Promise { permissionMode: opts.permissionMode ?? 'default' }); + if (opts.resumeSessionId) { + session.onSessionFound(opts.resumeSessionId); + } + await runLocalRemoteSession({ session, startingMode: opts.startingMode, diff --git a/cli/src/gemini/runGemini.test.ts b/cli/src/gemini/runGemini.test.ts index be216e333..2069eeffa 100644 --- a/cli/src/gemini/runGemini.test.ts +++ b/cli/src/gemini/runGemini.test.ts @@ -106,4 +106,26 @@ describe('runGemini', () => { expect(harness.bootstrapArgs[0]?.model).toBeUndefined(); expect(harness.geminiLoopArgs[0]?.model).toBe('gemini-2.5-pro'); }); + + it('passes resumeSessionId through to geminiLoop', async () => { + resolveGeminiRuntimeConfigMock.mockReturnValue({ + model: 'gemini-2.5-pro', + modelSource: 'default' + }); + + await runGemini({ resumeSessionId: 'a6157ffa-f692-4b73-82d5-63d42177f4f9' }); + + expect(harness.geminiLoopArgs[0]?.resumeSessionId).toBe('a6157ffa-f692-4b73-82d5-63d42177f4f9'); + }); + + it('does not set resumeSessionId when not provided', async () => { + resolveGeminiRuntimeConfigMock.mockReturnValue({ + model: 'gemini-2.5-pro', + modelSource: 'default' + }); + + await runGemini({}); + + expect(harness.geminiLoopArgs[0]?.resumeSessionId).toBeUndefined(); + }); }); diff --git a/cli/src/gemini/runGemini.ts b/cli/src/gemini/runGemini.ts index 704d440b4..9c2a9c1a8 100644 --- a/cli/src/gemini/runGemini.ts +++ b/cli/src/gemini/runGemini.ts @@ -21,6 +21,7 @@ export async function runGemini(opts: { startingMode?: 'local' | 'remote'; permissionMode?: PermissionMode; model?: string; + resumeSessionId?: string; } = {}): Promise { const workingDirectory = getInvokedCwd(); const startedBy = opts.startedBy ?? 'terminal'; @@ -149,6 +150,7 @@ export async function runGemini(opts: { permissionMode: currentPermissionMode, model: resolvedModel, hookSettingsPath, + resumeSessionId: opts.resumeSessionId, onModeChange: createModeChangeHandler(session), onSessionReady: (instance) => { sessionWrapperRef.current = instance;