mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-02 05:01:04 -07:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bae474d68f | |||
| fa26a2cb56 | |||
| f1974faac6 | |||
| 5b6fbcd0af | |||
| 2aea682c8f | |||
| 89f2afef02 | |||
| e824cb897a | |||
| 7766b36411 | |||
| fa1e95b360 | |||
| b3b5df96b7 | |||
| 84c3ee1f05 | |||
| 8849319e8b | |||
| 0b3653262c | |||
| fff88e794d | |||
| 395af2a1e4 | |||
| a45e42cf30 | |||
| 0d2ad092ec | |||
| 72068d40b6 |
+37
-16
@@ -706,12 +706,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;
|
||||
if (argv.resume) {
|
||||
if (resumeArg) {
|
||||
const sessionSelector = new SessionSelector(config);
|
||||
try {
|
||||
const result = await sessionSelector.resolveSession(argv.resume);
|
||||
const result = await sessionSelector.resolveSession(resumeArg);
|
||||
resumedSessionData = {
|
||||
conversation: result.sessionData,
|
||||
filePath: result.sessionPath,
|
||||
@@ -719,14 +726,18 @@ export async function main() {
|
||||
// Use the existing session ID to continue recording to the same session
|
||||
config.setSessionId(resumedSessionData.conversation.sessionId);
|
||||
} 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.code === 'NO_SESSIONS_FOUND'
|
||||
) {
|
||||
// No sessions to resume — start a fresh session with a warning
|
||||
startupWarnings.push({
|
||||
id: 'resume-no-sessions',
|
||||
message: error.message,
|
||||
id: 'resume-failure',
|
||||
message: `${error.message} Started a new session.`,
|
||||
priority: WarningPriority.High,
|
||||
});
|
||||
} else {
|
||||
@@ -740,6 +751,14 @@ export async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// When this is an auto-restart (not explicit --resume), clear the
|
||||
// original --prompt so it doesn't get submitted again — the resumed
|
||||
// session already contains it.
|
||||
if (isAutoRestart && resumedSessionData) {
|
||||
config.clearQuestion();
|
||||
input = undefined;
|
||||
}
|
||||
|
||||
cliStartupHandle?.end();
|
||||
// Render UI, passing necessary config values. Check that there is no command line question.
|
||||
if (config.isInteractive()) {
|
||||
@@ -795,7 +814,7 @@ export async function main() {
|
||||
await config.getHookSystem()?.fireSessionEndEvent(SessionEndReason.Exit);
|
||||
});
|
||||
|
||||
if (!input) {
|
||||
if (!input && !resumedSessionData) {
|
||||
debugLogger.error(
|
||||
`No input provided via stdin. Input can be provided by piping data into gemini or using the --prompt option.`,
|
||||
);
|
||||
@@ -804,15 +823,17 @@ export async function main() {
|
||||
}
|
||||
|
||||
const prompt_id = Math.random().toString(16).slice(2);
|
||||
logUserPrompt(
|
||||
config,
|
||||
new UserPromptEvent(
|
||||
input.length,
|
||||
prompt_id,
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
input,
|
||||
),
|
||||
);
|
||||
if (input) {
|
||||
logUserPrompt(
|
||||
config,
|
||||
new UserPromptEvent(
|
||||
input.length,
|
||||
prompt_id,
|
||||
config.getContentGeneratorConfig()?.authType,
|
||||
input,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const authType = await validateNonInteractiveAuth(
|
||||
settings.merged.security.auth.selectedType,
|
||||
|
||||
@@ -50,7 +50,7 @@ import { TextOutput } from './ui/utils/textOutput.js';
|
||||
interface RunNonInteractiveParams {
|
||||
config: Config;
|
||||
settings: LoadedSettings;
|
||||
input: string;
|
||||
input?: string;
|
||||
prompt_id: string;
|
||||
resumedSessionData?: ResumedSessionData;
|
||||
}
|
||||
@@ -237,56 +237,62 @@ export async function runNonInteractive({
|
||||
});
|
||||
}
|
||||
|
||||
let query: Part[] | undefined;
|
||||
let currentMessages: Content[] = [];
|
||||
|
||||
if (isSlashCommand(input)) {
|
||||
const slashCommandResult = await handleSlashCommand(
|
||||
input,
|
||||
abortController,
|
||||
config,
|
||||
settings,
|
||||
);
|
||||
// If a slash command is found and returns a prompt, use it.
|
||||
// Otherwise, slashCommandResult falls through to the default prompt
|
||||
// handling.
|
||||
if (slashCommandResult) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
query = slashCommandResult as Part[];
|
||||
}
|
||||
}
|
||||
// When resuming with no new input (e.g. auto-restart), skip sending
|
||||
// a new user message — the resumed session already contains it.
|
||||
if (input) {
|
||||
let query: Part[] | undefined;
|
||||
|
||||
if (!query) {
|
||||
const { processedQuery, error } = await handleAtCommand({
|
||||
query: input,
|
||||
config,
|
||||
addItem: (_item, _timestamp) => 0,
|
||||
onDebugMessage: () => {},
|
||||
messageId: Date.now(),
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
if (error || !processedQuery) {
|
||||
// An error occurred during @include processing (e.g., file not found).
|
||||
// The error message is already logged by handleAtCommand.
|
||||
throw new FatalInputError(
|
||||
error || 'Exiting due to an error processing the @ command.',
|
||||
if (isSlashCommand(input)) {
|
||||
const slashCommandResult = await handleSlashCommand(
|
||||
input,
|
||||
abortController,
|
||||
config,
|
||||
settings,
|
||||
);
|
||||
// If a slash command is found and returns a prompt, use it.
|
||||
// Otherwise, slashCommandResult falls through to the default prompt
|
||||
// handling.
|
||||
if (slashCommandResult) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
query = slashCommandResult as Part[];
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
query = processedQuery as Part[];
|
||||
}
|
||||
|
||||
// Emit user message event for streaming JSON
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.MESSAGE,
|
||||
timestamp: new Date().toISOString(),
|
||||
role: 'user',
|
||||
content: input,
|
||||
});
|
||||
}
|
||||
if (!query) {
|
||||
const { processedQuery, error } = await handleAtCommand({
|
||||
query: input,
|
||||
config,
|
||||
addItem: (_item, _timestamp) => 0,
|
||||
onDebugMessage: () => {},
|
||||
messageId: Date.now(),
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
let currentMessages: Content[] = [{ role: 'user', parts: query }];
|
||||
if (error || !processedQuery) {
|
||||
// An error occurred during @include processing (e.g., file not found).
|
||||
// The error message is already logged by handleAtCommand.
|
||||
throw new FatalInputError(
|
||||
error || 'Exiting due to an error processing the @ command.',
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
query = processedQuery as Part[];
|
||||
}
|
||||
|
||||
// Emit user message event for streaming JSON
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.MESSAGE,
|
||||
timestamp: new Date().toISOString(),
|
||||
role: 'user',
|
||||
content: input,
|
||||
});
|
||||
}
|
||||
|
||||
currentMessages = [{ role: 'user', parts: query }];
|
||||
}
|
||||
|
||||
let turnCount = 0;
|
||||
while (true) {
|
||||
|
||||
@@ -796,7 +796,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
Logging in with Google... Restarting Gemini CLI to continue.
|
||||
----------------------------------------------------------------
|
||||
`);
|
||||
await relaunchApp();
|
||||
await relaunchApp(config.getSessionId());
|
||||
}
|
||||
}
|
||||
setAuthState(AuthState.Authenticated);
|
||||
@@ -2487,7 +2487,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
});
|
||||
}
|
||||
}
|
||||
await relaunchApp();
|
||||
await relaunchApp(config.getSessionId());
|
||||
},
|
||||
handleNewAgentsSelect: async (choice: NewAgentsChoice) => {
|
||||
if (newAgents && choice === NewAgentsChoice.ACKNOWLEDGE) {
|
||||
|
||||
@@ -85,6 +85,7 @@ describe('AuthDialog', () => {
|
||||
props = {
|
||||
config: {
|
||||
isBrowserLaunchSuppressed: vi.fn().mockReturnValue(false),
|
||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||
} as unknown as Config,
|
||||
settings: {
|
||||
merged: {
|
||||
@@ -357,6 +358,13 @@ describe('AuthDialog', () => {
|
||||
.spyOn(process, 'exit')
|
||||
.mockImplementation(() => undefined as never);
|
||||
const logSpy = vi.spyOn(debugLogger, 'log').mockImplementation(() => {});
|
||||
const originalSend = process.send;
|
||||
process.send = vi.fn(
|
||||
(_msg: unknown, callback?: (err: Error | null) => void) => {
|
||||
if (callback) callback(null);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
vi.mocked(props.config.isBrowserLaunchSuppressed).mockReturnValue(true);
|
||||
mockedValidateAuthMethod.mockReturnValue(null);
|
||||
|
||||
@@ -376,6 +384,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
exitSpy.mockRestore();
|
||||
logSpy.mockRestore();
|
||||
process.send = originalSend;
|
||||
vi.useRealTimers();
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -132,7 +132,7 @@ export function AuthDialog({
|
||||
config.isBrowserLaunchSuppressed()
|
||||
) {
|
||||
setExiting(true);
|
||||
setTimeout(relaunchApp, 100);
|
||||
setTimeout(() => relaunchApp(config.getSessionId()), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,15 @@
|
||||
*/
|
||||
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
vi,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { LoginWithGoogleRestartDialog } from './LoginWithGoogleRestartDialog.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
@@ -35,13 +43,27 @@ describe('LoginWithGoogleRestartDialog', () => {
|
||||
|
||||
const mockConfig = {
|
||||
getRemoteAdminSettings: vi.fn(),
|
||||
getSessionId: vi.fn().mockReturnValue('mock-session-id'),
|
||||
} as unknown as Config;
|
||||
|
||||
let originalSend: typeof process.send;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
exitSpy.mockClear();
|
||||
vi.useRealTimers();
|
||||
_resetRelaunchStateForTesting();
|
||||
originalSend = process.send;
|
||||
process.send = vi.fn((_message, callback) => {
|
||||
if (typeof callback === 'function') {
|
||||
callback(null);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.send = originalSend;
|
||||
});
|
||||
|
||||
it('renders correctly', async () => {
|
||||
|
||||
@@ -35,7 +35,7 @@ export const LoginWithGoogleRestartDialog = ({
|
||||
});
|
||||
}
|
||||
}
|
||||
await relaunchApp();
|
||||
await relaunchApp(config.getSessionId());
|
||||
}, 100);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ export const DialogManager = ({
|
||||
<Box flexDirection="column">
|
||||
<SettingsDialog
|
||||
onSelect={() => uiActions.closeSettingsDialog()}
|
||||
onRestartRequest={relaunchApp}
|
||||
onRestartRequest={() => relaunchApp(config.getSessionId())}
|
||||
availableTerminalHeight={terminalHeight - staticExtraHeight}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -18,6 +18,7 @@ import * as process from 'node:process';
|
||||
import * as path from 'node:path';
|
||||
import { relaunchApp } from '../../utils/processUtils.js';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import {
|
||||
ExitCodes,
|
||||
type FolderDiscoveryResults,
|
||||
@@ -45,6 +46,7 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
|
||||
isRestarting,
|
||||
discoveryResults,
|
||||
}) => {
|
||||
const config = useConfig();
|
||||
const [exiting, setExiting] = useState(false);
|
||||
const { terminalHeight, terminalWidth, constrainHeight } = useUIState();
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
@@ -54,12 +56,12 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
|
||||
useEffect(() => {
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
if (isRestarting) {
|
||||
timer = setTimeout(relaunchApp, 250);
|
||||
timer = setTimeout(() => relaunchApp(config.getSessionId()), 250);
|
||||
}
|
||||
return () => {
|
||||
if (timer) clearTimeout(timer);
|
||||
};
|
||||
}, [isRestarting]);
|
||||
}, [isRestarting, config]);
|
||||
|
||||
const handleExit = useCallback(() => {
|
||||
setExiting(true);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { relaunchApp } from '../../utils/processUtils.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { type RestartReason } from '../hooks/useIdeTrustListener.js';
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
|
||||
@@ -16,11 +17,12 @@ interface IdeTrustChangeDialogProps {
|
||||
}
|
||||
|
||||
export const IdeTrustChangeDialog = ({ reason }: IdeTrustChangeDialogProps) => {
|
||||
const config = useConfig();
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (key.name === 'r' || key.name === 'R') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
relaunchApp();
|
||||
relaunchApp(config.getSessionId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as path from 'node:path';
|
||||
import { TrustLevel } from '../../config/trustedFolders.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { usePermissionsModifyTrust } from '../hooks/usePermissionsModifyTrust.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { relaunchApp } from '../../utils/processUtils.js';
|
||||
@@ -33,6 +34,7 @@ export function PermissionsModifyTrustDialog({
|
||||
const currentDirectory = targetDirectory ?? process.cwd();
|
||||
const dirName = path.basename(currentDirectory);
|
||||
const parentFolder = path.basename(path.dirname(currentDirectory));
|
||||
const config = useConfig();
|
||||
|
||||
const TRUST_LEVEL_ITEMS = [
|
||||
{
|
||||
@@ -72,7 +74,7 @@ export function PermissionsModifyTrustDialog({
|
||||
void (async () => {
|
||||
const success = await commitTrustLevelChange();
|
||||
if (success) {
|
||||
void relaunchApp();
|
||||
void relaunchApp(config.getSessionId());
|
||||
} else {
|
||||
onExit();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
RELAUNCH_EXIT_CODE,
|
||||
relaunchApp,
|
||||
@@ -22,17 +22,42 @@ describe('processUtils', () => {
|
||||
.spyOn(process, 'exit')
|
||||
.mockReturnValue(undefined as never);
|
||||
const runExitCleanup = vi.spyOn(cleanup, 'runExitCleanup');
|
||||
const originalSend = process.send;
|
||||
|
||||
beforeEach(() => {
|
||||
_resetRelaunchStateForTesting();
|
||||
process.send = vi.fn(
|
||||
(_msg: unknown, callback?: (err: Error | null) => void) => {
|
||||
if (callback) callback(null);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => vi.clearAllMocks());
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
process.send = originalSend;
|
||||
});
|
||||
|
||||
it('should wait for updates, run cleanup, and exit with the relaunch code', async () => {
|
||||
it('should not send IPC message when no sessionId is provided', async () => {
|
||||
await relaunchApp();
|
||||
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');
|
||||
expect(handleAutoUpdate.waitForUpdateCompletion).toHaveBeenCalledTimes(1);
|
||||
expect(runExitCleanup).toHaveBeenCalledTimes(1);
|
||||
expect(process.send).toHaveBeenCalledWith(
|
||||
{
|
||||
type: 'relaunch-session',
|
||||
sessionId: 'custom-session-id',
|
||||
},
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(processExit).toHaveBeenCalledWith(RELAUNCH_EXIT_CODE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,10 +22,23 @@ export function _resetRelaunchStateForTesting(): void {
|
||||
isRelaunching = false;
|
||||
}
|
||||
|
||||
export async function relaunchApp(): Promise<void> {
|
||||
export async function relaunchApp(sessionId?: string): Promise<void> {
|
||||
if (isRelaunching) return;
|
||||
isRelaunching = true;
|
||||
await waitForUpdateCompletion();
|
||||
await runExitCleanup();
|
||||
|
||||
if (process.send && sessionId) {
|
||||
await new Promise<void>((resolve) => {
|
||||
process.send!(
|
||||
{
|
||||
type: 'relaunch-session',
|
||||
sessionId,
|
||||
},
|
||||
() => resolve(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
process.exit(RELAUNCH_EXIT_CODE);
|
||||
}
|
||||
|
||||
@@ -315,6 +315,51 @@ describe('relaunchAppInChildProcess', () => {
|
||||
// Should default to exit code 1
|
||||
expect(processExitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
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'];
|
||||
|
||||
let spawnCount = 0;
|
||||
mockedSpawn.mockImplementation(() => {
|
||||
spawnCount++;
|
||||
const mockChild = createMockChildProcess(0, false);
|
||||
|
||||
if (spawnCount === 1) {
|
||||
// First run: send the resume session ID, then exit with RELAUNCH_EXIT_CODE
|
||||
setImmediate(() => {
|
||||
mockChild.emit('message', {
|
||||
type: 'relaunch-session',
|
||||
sessionId: 'test-session-123',
|
||||
});
|
||||
mockChild.emit('close', RELAUNCH_EXIT_CODE);
|
||||
});
|
||||
} else if (spawnCount === 2) {
|
||||
// Second run: exit normally
|
||||
setImmediate(() => {
|
||||
mockChild.emit('close', 0);
|
||||
});
|
||||
}
|
||||
|
||||
return mockChild;
|
||||
});
|
||||
|
||||
const promise = relaunchAppInChildProcess([], []);
|
||||
await expect(promise).rejects.toThrow('PROCESS_EXIT_CALLED');
|
||||
|
||||
expect(mockedSpawn).toHaveBeenCalledTimes(2);
|
||||
|
||||
// First spawn should not have the env var set
|
||||
const firstEnv = mockedSpawn.mock.calls[0][2]?.env;
|
||||
expect(firstEnv?.['GEMINI_RESUME_SESSION_ID']).toBeUndefined();
|
||||
|
||||
// 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];
|
||||
expect(secondArgs).not.toContain('--resume');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ export async function relaunchAppInChildProcess(
|
||||
}
|
||||
|
||||
let latestAdminSettings = remoteAdminSettings;
|
||||
let resumeSessionId: string | undefined = undefined;
|
||||
|
||||
const runner = () => {
|
||||
// process.argv is [node, script, ...args]
|
||||
@@ -55,7 +56,11 @@ export async function relaunchAppInChildProcess(
|
||||
...additionalScriptArgs,
|
||||
...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.
|
||||
process.stdin.pause();
|
||||
@@ -69,11 +74,16 @@ export async function relaunchAppInChildProcess(
|
||||
child.send({ type: 'admin-settings', settings: latestAdminSettings });
|
||||
}
|
||||
|
||||
child.on('message', (msg: { type?: string; settings?: unknown }) => {
|
||||
if (msg.type === 'admin-settings-update' && msg.settings) {
|
||||
latestAdminSettings = msg.settings as AdminControlsSettings;
|
||||
}
|
||||
});
|
||||
child.on(
|
||||
'message',
|
||||
(msg: { type?: string; settings?: unknown; sessionId?: string }) => {
|
||||
if (msg.type === 'admin-settings-update' && msg.settings) {
|
||||
latestAdminSettings = msg.settings as AdminControlsSettings;
|
||||
} else if (msg.type === 'relaunch-session' && msg.sessionId) {
|
||||
resumeSessionId = msg.sessionId;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
child.on('error', reject);
|
||||
|
||||
@@ -631,7 +631,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
private readonly targetDir: string;
|
||||
private workspaceContext: WorkspaceContext;
|
||||
private readonly debugMode: boolean;
|
||||
private readonly question: string | undefined;
|
||||
private question: string | undefined;
|
||||
readonly enableConseca: boolean;
|
||||
|
||||
private readonly coreTools: string[] | undefined;
|
||||
@@ -1669,6 +1669,10 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
return this.question;
|
||||
}
|
||||
|
||||
clearQuestion(): void {
|
||||
this.question = undefined;
|
||||
}
|
||||
|
||||
getHasAccessToPreviewModel(): boolean {
|
||||
return this.hasAccessToPreviewModel !== false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user