mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
chore: update restart to use env var
This commit is contained in:
@@ -693,12 +693,19 @@ export async function main() {
|
|||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Handle --resume flag
|
// Handle session resume — either from explicit --resume flag or from
|
||||||
|
// auto-restart via GEMINI_RESUME_SESSION_ID env var.
|
||||||
|
const resumeArg = argv.resume ?? process.env['GEMINI_RESUME_SESSION_ID'];
|
||||||
|
const isAutoRestart =
|
||||||
|
!argv.resume && !!process.env['GEMINI_RESUME_SESSION_ID'];
|
||||||
|
// Clean up the env var so it doesn't leak to further restarts
|
||||||
|
delete process.env['GEMINI_RESUME_SESSION_ID'];
|
||||||
|
|
||||||
let resumedSessionData: ResumedSessionData | undefined = undefined;
|
let resumedSessionData: ResumedSessionData | undefined = undefined;
|
||||||
if (argv.resume) {
|
if (resumeArg) {
|
||||||
const sessionSelector = new SessionSelector(config);
|
const sessionSelector = new SessionSelector(config);
|
||||||
try {
|
try {
|
||||||
const result = await sessionSelector.resolveSession(argv.resume);
|
const result = await sessionSelector.resolveSession(resumeArg);
|
||||||
resumedSessionData = {
|
resumedSessionData = {
|
||||||
conversation: result.sessionData,
|
conversation: result.sessionData,
|
||||||
filePath: result.sessionPath,
|
filePath: result.sessionPath,
|
||||||
@@ -706,14 +713,18 @@ export async function main() {
|
|||||||
// Use the existing session ID to continue recording to the same session
|
// Use the existing session ID to continue recording to the same session
|
||||||
config.setSessionId(resumedSessionData.conversation.sessionId);
|
config.setSessionId(resumedSessionData.conversation.sessionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (error instanceof SessionError && isAutoRestart) {
|
||||||
|
// Auto-restart tried to resume a session that doesn't exist on
|
||||||
|
// disk yet (e.g. empty session with no messages). Silently start
|
||||||
|
// a new session.
|
||||||
|
} else if (
|
||||||
error instanceof SessionError &&
|
error instanceof SessionError &&
|
||||||
error.code === 'NO_SESSIONS_FOUND'
|
error.code === 'NO_SESSIONS_FOUND'
|
||||||
) {
|
) {
|
||||||
// No sessions to resume — start a fresh session with a warning
|
// No sessions to resume — start a fresh session with a warning
|
||||||
startupWarnings.push({
|
startupWarnings.push({
|
||||||
id: 'resume-failure',
|
id: 'resume-failure',
|
||||||
message: 'Could not resume session. Started a new session.',
|
message: `${error.message} Started a new session.`,
|
||||||
priority: WarningPriority.High,
|
priority: WarningPriority.High,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -22,33 +22,27 @@ describe('processUtils', () => {
|
|||||||
.spyOn(process, 'exit')
|
.spyOn(process, 'exit')
|
||||||
.mockReturnValue(undefined as never);
|
.mockReturnValue(undefined as never);
|
||||||
const runExitCleanup = vi.spyOn(cleanup, 'runExitCleanup');
|
const runExitCleanup = vi.spyOn(cleanup, 'runExitCleanup');
|
||||||
|
const originalSend = process.send;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
_resetRelaunchStateForTesting();
|
_resetRelaunchStateForTesting();
|
||||||
|
process.send = vi.fn();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => vi.clearAllMocks());
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
it('should wait for updates, run cleanup, send resume session ID, and exit with the relaunch code', async () => {
|
|
||||||
const originalSend = process.send;
|
|
||||||
process.send = vi.fn();
|
|
||||||
|
|
||||||
await relaunchApp();
|
|
||||||
expect(handleAutoUpdate.waitForUpdateCompletion).toHaveBeenCalledTimes(1);
|
|
||||||
expect(runExitCleanup).toHaveBeenCalledTimes(1);
|
|
||||||
expect(process.send).toHaveBeenCalledWith({
|
|
||||||
type: 'relaunch-session',
|
|
||||||
sessionId: expect.any(String),
|
|
||||||
});
|
|
||||||
expect(processExit).toHaveBeenCalledWith(RELAUNCH_EXIT_CODE);
|
|
||||||
|
|
||||||
process.send = originalSend;
|
process.send = originalSend;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should wait for updates, run cleanup, send override resume session ID, and exit with the relaunch code', async () => {
|
it('should not send IPC message when no sessionId is provided', async () => {
|
||||||
const originalSend = process.send;
|
await relaunchApp();
|
||||||
process.send = vi.fn();
|
expect(handleAutoUpdate.waitForUpdateCompletion).toHaveBeenCalledTimes(1);
|
||||||
|
expect(runExitCleanup).toHaveBeenCalledTimes(1);
|
||||||
|
expect(process.send).not.toHaveBeenCalled();
|
||||||
|
expect(processExit).toHaveBeenCalledWith(RELAUNCH_EXIT_CODE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send resume session ID via IPC when sessionId is provided', async () => {
|
||||||
await relaunchApp('custom-session-id');
|
await relaunchApp('custom-session-id');
|
||||||
expect(handleAutoUpdate.waitForUpdateCompletion).toHaveBeenCalledTimes(1);
|
expect(handleAutoUpdate.waitForUpdateCompletion).toHaveBeenCalledTimes(1);
|
||||||
expect(runExitCleanup).toHaveBeenCalledTimes(1);
|
expect(runExitCleanup).toHaveBeenCalledTimes(1);
|
||||||
@@ -57,7 +51,5 @@ describe('processUtils', () => {
|
|||||||
sessionId: 'custom-session-id',
|
sessionId: 'custom-session-id',
|
||||||
});
|
});
|
||||||
expect(processExit).toHaveBeenCalledWith(RELAUNCH_EXIT_CODE);
|
expect(processExit).toHaveBeenCalledWith(RELAUNCH_EXIT_CODE);
|
||||||
|
|
||||||
process.send = originalSend;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
import { runExitCleanup } from './cleanup.js';
|
import { runExitCleanup } from './cleanup.js';
|
||||||
import { waitForUpdateCompletion } from './handleAutoUpdate.js';
|
import { waitForUpdateCompletion } from './handleAutoUpdate.js';
|
||||||
import { sessionId } from '@google/gemini-cli-core';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exit code used to signal that the CLI should be relaunched.
|
* Exit code used to signal that the CLI should be relaunched.
|
||||||
@@ -23,16 +22,16 @@ export function _resetRelaunchStateForTesting(): void {
|
|||||||
isRelaunching = false;
|
isRelaunching = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function relaunchApp(sessionIdOverride?: string): Promise<void> {
|
export async function relaunchApp(sessionId?: string): Promise<void> {
|
||||||
if (isRelaunching) return;
|
if (isRelaunching) return;
|
||||||
isRelaunching = true;
|
isRelaunching = true;
|
||||||
await waitForUpdateCompletion();
|
await waitForUpdateCompletion();
|
||||||
await runExitCleanup();
|
await runExitCleanup();
|
||||||
|
|
||||||
if (process.send) {
|
if (process.send && sessionId) {
|
||||||
process.send({
|
process.send({
|
||||||
type: 'relaunch-session',
|
type: 'relaunch-session',
|
||||||
sessionId: sessionIdOverride ?? sessionId,
|
sessionId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ describe('relaunchAppInChildProcess', () => {
|
|||||||
expect(processExitSpy).toHaveBeenCalledWith(1);
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should append --resume <sessionId> on the next spawn if relaunch-session message is received', async () => {
|
it('should set GEMINI_RESUME_SESSION_ID env var on the next spawn if relaunch-session message is received', async () => {
|
||||||
process.argv = ['/usr/bin/node', '/app/cli.js', '--some-flag'];
|
process.argv = ['/usr/bin/node', '/app/cli.js', '--some-flag'];
|
||||||
|
|
||||||
let spawnCount = 0;
|
let spawnCount = 0;
|
||||||
@@ -348,65 +348,17 @@ describe('relaunchAppInChildProcess', () => {
|
|||||||
|
|
||||||
expect(mockedSpawn).toHaveBeenCalledTimes(2);
|
expect(mockedSpawn).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
// First spawn should not have --resume
|
// First spawn should not have the env var set
|
||||||
expect(mockedSpawn.mock.calls[0][1]).not.toContain('--resume');
|
const firstEnv = mockedSpawn.mock.calls[0][2]?.env;
|
||||||
|
expect(firstEnv?.['GEMINI_RESUME_SESSION_ID']).toBeUndefined();
|
||||||
|
|
||||||
// Second spawn should have --resume test-session-123 appended
|
// Second spawn should have GEMINI_RESUME_SESSION_ID set
|
||||||
|
const secondEnv = mockedSpawn.mock.calls[1][2]?.env;
|
||||||
|
expect(secondEnv?.['GEMINI_RESUME_SESSION_ID']).toBe('test-session-123');
|
||||||
|
|
||||||
|
// Args should not contain --resume
|
||||||
const secondArgs = mockedSpawn.mock.calls[1][1];
|
const secondArgs = mockedSpawn.mock.calls[1][1];
|
||||||
expect(secondArgs).toContain('--resume');
|
expect(secondArgs).not.toContain('--resume');
|
||||||
expect(secondArgs).toContain('test-session-123');
|
|
||||||
|
|
||||||
// Check exact order at the end of arguments
|
|
||||||
expect(secondArgs.slice(-2)).toEqual(['--resume', 'test-session-123']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should strip existing --resume flags when appending new one', async () => {
|
|
||||||
process.argv = [
|
|
||||||
'/usr/bin/node',
|
|
||||||
'/app/cli.js',
|
|
||||||
'--resume',
|
|
||||||
'old-session',
|
|
||||||
'--resume=other-session',
|
|
||||||
'--flag',
|
|
||||||
];
|
|
||||||
|
|
||||||
let spawnCount = 0;
|
|
||||||
mockedSpawn.mockImplementation(() => {
|
|
||||||
spawnCount++;
|
|
||||||
const mockChild = createMockChildProcess(0, false);
|
|
||||||
|
|
||||||
if (spawnCount === 1) {
|
|
||||||
setImmediate(() => {
|
|
||||||
mockChild.emit('message', {
|
|
||||||
type: 'relaunch-session',
|
|
||||||
sessionId: 'new-session-456',
|
|
||||||
});
|
|
||||||
mockChild.emit('close', RELAUNCH_EXIT_CODE);
|
|
||||||
});
|
|
||||||
} else if (spawnCount === 2) {
|
|
||||||
setImmediate(() => {
|
|
||||||
mockChild.emit('close', 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mockChild;
|
|
||||||
});
|
|
||||||
|
|
||||||
const promise = relaunchAppInChildProcess([], []);
|
|
||||||
await expect(promise).rejects.toThrow('PROCESS_EXIT_CALLED');
|
|
||||||
|
|
||||||
expect(mockedSpawn).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
const secondArgs = mockedSpawn.mock.calls[1][1] as string[];
|
|
||||||
|
|
||||||
// Should not contain the old resumes
|
|
||||||
expect(secondArgs).not.toContain('old-session');
|
|
||||||
expect(secondArgs).not.toContain('--resume=other-session');
|
|
||||||
|
|
||||||
// Should contain the new resume at the end
|
|
||||||
expect(secondArgs.slice(-2)).toEqual(['--resume', 'new-session-456']);
|
|
||||||
// Should still contain other flags
|
|
||||||
expect(secondArgs).toContain('--flag');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,22 +47,7 @@ export async function relaunchAppInChildProcess(
|
|||||||
// process.argv is [node, script, ...args]
|
// process.argv is [node, script, ...args]
|
||||||
// We want to construct [ ...nodeArgs, script, ...scriptArgs]
|
// We want to construct [ ...nodeArgs, script, ...scriptArgs]
|
||||||
const script = process.argv[1];
|
const script = process.argv[1];
|
||||||
let scriptArgs = process.argv.slice(2);
|
const scriptArgs = process.argv.slice(2);
|
||||||
|
|
||||||
if (resumeSessionId) {
|
|
||||||
const filteredArgs: string[] = [];
|
|
||||||
for (let i = 0; i < scriptArgs.length; i++) {
|
|
||||||
if (scriptArgs[i] === '--resume') {
|
|
||||||
i++; // Skip the next argument as well
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (scriptArgs[i].startsWith('--resume=')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
filteredArgs.push(scriptArgs[i]);
|
|
||||||
}
|
|
||||||
scriptArgs = [...filteredArgs, '--resume', resumeSessionId];
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeArgs = [
|
const nodeArgs = [
|
||||||
...process.execArgv,
|
...process.execArgv,
|
||||||
@@ -71,7 +56,11 @@ export async function relaunchAppInChildProcess(
|
|||||||
...additionalScriptArgs,
|
...additionalScriptArgs,
|
||||||
...scriptArgs,
|
...scriptArgs,
|
||||||
];
|
];
|
||||||
const newEnv = { ...process.env, GEMINI_CLI_NO_RELAUNCH: 'true' };
|
const newEnv: Record<string, string | undefined> = {
|
||||||
|
...process.env,
|
||||||
|
GEMINI_CLI_NO_RELAUNCH: 'true',
|
||||||
|
GEMINI_RESUME_SESSION_ID: resumeSessionId,
|
||||||
|
};
|
||||||
|
|
||||||
// The parent process should not be reading from stdin while the child is running.
|
// The parent process should not be reading from stdin while the child is running.
|
||||||
process.stdin.pause();
|
process.stdin.pause();
|
||||||
|
|||||||
Reference in New Issue
Block a user