mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
fix(patch): cherry-pick 467a305 to release/v0.9.0-preview.0-pr-10661 to patch version v0.9.0-preview.0 and create version 0.9.0-preview.1 (#10817)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
This commit is contained in:
@@ -206,8 +206,8 @@ Settings are organized into categories. All settings should be placed within the
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`tools.shell.enableInteractiveShell`** (boolean):
|
||||
|
||||
Use `node-pty` for an interactive shell experience. Fallback to `child_process` still applies. Defaults to `false`.
|
||||
- **Description:** Enables interactive terminal for running shell commands. If an interactive session cannot be started, it will fall back to a standard shell.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`tools.core`** (array of strings):
|
||||
- **Description:** This can be used to restrict the set of built-in tools [with an allowlist](../cli/enterprise.md#restricting-tool-access). See [Built-in Tools](../core/tools-api.md#built-in-tools) for a list of core tools. The match semantics are the same as `tools.allowed`.
|
||||
|
||||
@@ -18,93 +18,87 @@ describe('Interactive Mode', () => {
|
||||
await rig.cleanup();
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'should trigger chat compression with /compress command',
|
||||
async () => {
|
||||
await rig.setup('interactive-compress-test');
|
||||
//TODO - https://github.com/google-gemini/gemini-cli/issues/10770
|
||||
it.skip('should trigger chat compression with /compress command', async () => {
|
||||
await rig.setup('interactive-compress-test');
|
||||
|
||||
const { ptyProcess } = rig.runInteractive();
|
||||
const { ptyProcess } = rig.runInteractive();
|
||||
|
||||
let fullOutput = '';
|
||||
ptyProcess.onData((data) => (fullOutput += data));
|
||||
let fullOutput = '';
|
||||
ptyProcess.onData((data) => (fullOutput += data));
|
||||
|
||||
const authDialogAppeared = await rig.waitForText(
|
||||
'How would you like to authenticate',
|
||||
5000,
|
||||
);
|
||||
const authDialogAppeared = await rig.waitForText(
|
||||
'How would you like to authenticate',
|
||||
5000,
|
||||
);
|
||||
|
||||
// select the second option if auth dialog come's up
|
||||
if (authDialogAppeared) {
|
||||
ptyProcess.write('2');
|
||||
}
|
||||
// select the second option if auth dialog come's up
|
||||
if (authDialogAppeared) {
|
||||
ptyProcess.write('2');
|
||||
}
|
||||
|
||||
// Wait for the app to be ready
|
||||
const isReady = await rig.waitForText('Type your message', 15000);
|
||||
expect(
|
||||
isReady,
|
||||
'CLI did not start up in interactive mode correctly',
|
||||
).toBe(true);
|
||||
// Wait for the app to be ready
|
||||
const isReady = await rig.waitForText('Type your message', 15000);
|
||||
expect(isReady, 'CLI did not start up in interactive mode correctly').toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
const longPrompt =
|
||||
'Dont do anything except returning a 1000 token long paragragh with the <name of the scientist who discovered theory of relativity> at the end to indicate end of response. This is a moderately long sentence.';
|
||||
const longPrompt =
|
||||
'Dont do anything except returning a 1000 token long paragragh with the <name of the scientist who discovered theory of relativity> at the end to indicate end of response. This is a moderately long sentence.';
|
||||
|
||||
await type(ptyProcess, longPrompt);
|
||||
await type(ptyProcess, '\r');
|
||||
await type(ptyProcess, longPrompt);
|
||||
await type(ptyProcess, '\r');
|
||||
|
||||
await rig.waitForText('einstein', 25000);
|
||||
await rig.waitForText('einstein', 25000);
|
||||
|
||||
await type(ptyProcess, '/compress');
|
||||
// A small delay to allow React to re-render the command list.
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await type(ptyProcess, '\r');
|
||||
await type(ptyProcess, '/compress');
|
||||
// A small delay to allow React to re-render the command list.
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await type(ptyProcess, '\r');
|
||||
|
||||
const foundEvent = await rig.waitForTelemetryEvent(
|
||||
'chat_compression',
|
||||
90000,
|
||||
);
|
||||
expect(foundEvent, 'chat_compression telemetry event was not found').toBe(
|
||||
true,
|
||||
);
|
||||
},
|
||||
);
|
||||
const foundEvent = await rig.waitForTelemetryEvent(
|
||||
'chat_compression',
|
||||
90000,
|
||||
);
|
||||
expect(foundEvent, 'chat_compression telemetry event was not found').toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'should handle compression failure on token inflation',
|
||||
async () => {
|
||||
await rig.setup('interactive-compress-test');
|
||||
//TODO - https://github.com/google-gemini/gemini-cli/issues/10769
|
||||
it.skip('should handle compression failure on token inflation', async () => {
|
||||
await rig.setup('interactive-compress-test');
|
||||
|
||||
const { ptyProcess } = rig.runInteractive();
|
||||
const { ptyProcess } = rig.runInteractive();
|
||||
|
||||
let fullOutput = '';
|
||||
ptyProcess.onData((data) => (fullOutput += data));
|
||||
let fullOutput = '';
|
||||
ptyProcess.onData((data) => (fullOutput += data));
|
||||
|
||||
const authDialogAppeared = await rig.waitForText(
|
||||
'How would you like to authenticate',
|
||||
5000,
|
||||
);
|
||||
const authDialogAppeared = await rig.waitForText(
|
||||
'How would you like to authenticate',
|
||||
5000,
|
||||
);
|
||||
|
||||
// select the second option if auth dialog come's up
|
||||
if (authDialogAppeared) {
|
||||
ptyProcess.write('2');
|
||||
}
|
||||
// select the second option if auth dialog come's up
|
||||
if (authDialogAppeared) {
|
||||
ptyProcess.write('2');
|
||||
}
|
||||
|
||||
// Wait for the app to be ready
|
||||
const isReady = await rig.waitForText('Type your message', 25000);
|
||||
expect(
|
||||
isReady,
|
||||
'CLI did not start up in interactive mode correctly',
|
||||
).toBe(true);
|
||||
// Wait for the app to be ready
|
||||
const isReady = await rig.waitForText('Type your message', 25000);
|
||||
expect(isReady, 'CLI did not start up in interactive mode correctly').toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
await type(ptyProcess, '/compress');
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await type(ptyProcess, '\r');
|
||||
await type(ptyProcess, '/compress');
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await type(ptyProcess, '\r');
|
||||
|
||||
const compressionFailed = await rig.waitForText(
|
||||
'compression was not beneficial',
|
||||
25000,
|
||||
);
|
||||
const compressionFailed = await rig.waitForText(
|
||||
'compression was not beneficial',
|
||||
25000,
|
||||
);
|
||||
|
||||
expect(compressionFailed).toBe(true);
|
||||
},
|
||||
);
|
||||
expect(compressionFailed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -192,7 +192,8 @@ describe('mcp server with cyclic tool schema is detected', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('mcp tool list should include tool with cyclic tool schema', async () => {
|
||||
//TODO - https://github.com/google-gemini/gemini-cli/issues/10735
|
||||
it.skip('mcp tool list should include tool with cyclic tool schema', async () => {
|
||||
const tool_list_output = await rig.run('/mcp list');
|
||||
expect(tool_list_output).toContain('tool_with_cyclic_schema');
|
||||
});
|
||||
|
||||
@@ -197,7 +197,8 @@ describe('run_shell_command', () => {
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should combine multiple --allowed-tools flags', async () => {
|
||||
//TODO - https://github.com/google-gemini/gemini-cli/issues/10737
|
||||
it.skip('should combine multiple --allowed-tools flags', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should combine multiple --allowed-tools flags');
|
||||
|
||||
@@ -226,7 +227,8 @@ describe('run_shell_command', () => {
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow all with "ShellTool" and other specifics', async () => {
|
||||
//TODO - https://github.com/google-gemini/gemini-cli/issues/10768
|
||||
it.skip('should allow all with "ShellTool" and other specifics', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should allow all with "ShellTool" and other specifics');
|
||||
|
||||
|
||||
@@ -210,7 +210,8 @@ describe('simple-mcp-server', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should add two numbers', async () => {
|
||||
//TODO -https://github.com/google-gemini/gemini-cli/issues/10738
|
||||
it.skip('should add two numbers', async () => {
|
||||
// Test directory is already set up in before hook
|
||||
// Just run the command - MCP server config is in settings.json
|
||||
const output = await rig.run('add 5 and 10');
|
||||
|
||||
@@ -737,7 +737,8 @@ export async function loadCliConfig(
|
||||
interactive,
|
||||
trustedFolder,
|
||||
useRipgrep: settings.tools?.useRipgrep,
|
||||
shouldUseNodePtyShell: settings.tools?.shell?.enableInteractiveShell,
|
||||
enableInteractiveShell:
|
||||
settings.tools?.shell?.enableInteractiveShell ?? true,
|
||||
skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
|
||||
enablePromptCompletion: settings.general?.enablePromptCompletion ?? false,
|
||||
truncateToolOutputThreshold: settings.tools?.truncateToolOutputThreshold,
|
||||
|
||||
@@ -869,7 +869,8 @@ describe('extension tests', () => {
|
||||
expect(mockLogExtensionInstallEvent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show users information on their mcp server when installing', async () => {
|
||||
//TODO - https://github.com/google-gemini/gemini-cli/issues/10739
|
||||
it.skip('should show users information on their mcp server when installing', async () => {
|
||||
const consoleInfoSpy = vi.spyOn(console, 'info');
|
||||
const sourceExtDir = createExtension({
|
||||
extensionsDir: tempHomeDir,
|
||||
|
||||
@@ -110,7 +110,7 @@ const MIGRATION_MAP: Record<string, string> = {
|
||||
preferredEditor: 'general.preferredEditor',
|
||||
sandbox: 'tools.sandbox',
|
||||
selectedAuthType: 'security.auth.selectedType',
|
||||
shouldUseNodePtyShell: 'tools.shell.enableInteractiveShell',
|
||||
enableInteractiveShell: 'tools.shell.enableInteractiveShell',
|
||||
shellPager: 'tools.shell.pager',
|
||||
shellShowColor: 'tools.shell.showColor',
|
||||
skipNextSpeakerCheck: 'model.skipNextSpeakerCheck',
|
||||
|
||||
@@ -713,7 +713,7 @@ const SETTINGS_SCHEMA = {
|
||||
label: 'Enable Interactive Shell',
|
||||
category: 'Tools',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
default: true,
|
||||
description:
|
||||
'Use node-pty for an interactive shell experience. Fallback to child_process still applies.',
|
||||
showInDialog: true,
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('ShellProcessor', () => {
|
||||
mockConfig = {
|
||||
getTargetDir: vi.fn().mockReturnValue('/test/dir'),
|
||||
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
|
||||
getShouldUseNodePtyShell: vi.fn().mockReturnValue(false),
|
||||
getEnableInteractiveShell: vi.fn().mockReturnValue(false),
|
||||
getShellExecutionConfig: vi.fn().mockReturnValue({}),
|
||||
};
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ export class ShellProcessor implements IPromptProcessor {
|
||||
config.getTargetDir(),
|
||||
() => {},
|
||||
new AbortController().signal,
|
||||
config.getShouldUseNodePtyShell(),
|
||||
config.getEnableInteractiveShell(),
|
||||
shellExecutionConfig,
|
||||
);
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ describe('setupGithubCommand', async () => {
|
||||
if (scratchDir) await fs.rm(scratchDir, { recursive: true });
|
||||
});
|
||||
|
||||
it('returns a tool action to download github workflows and handles paths', async () => {
|
||||
//TODO - https://github.com/google-gemini/gemini-cli/issues/10740
|
||||
it.skip('returns a tool action to download github workflows and handles paths', async () => {
|
||||
const fakeRepoOwner = 'fake';
|
||||
const fakeRepoName = 'repo';
|
||||
const fakeRepoRoot = scratchDir;
|
||||
|
||||
@@ -93,7 +93,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
||||
const isThisShellFocusable =
|
||||
(name === SHELL_COMMAND_NAME || name === 'Shell') &&
|
||||
status === ToolCallStatus.Executing &&
|
||||
config?.getShouldUseNodePtyShell();
|
||||
config?.getEnableInteractiveShell();
|
||||
|
||||
const shouldShowFocusHint =
|
||||
isThisShellFocusable && (showFocusHint || userHasFocused);
|
||||
|
||||
@@ -65,7 +65,7 @@ describe('useShellCommandProcessor', () => {
|
||||
setShellInputFocusedMock = vi.fn();
|
||||
mockConfig = {
|
||||
getTargetDir: () => '/test/dir',
|
||||
getShouldUseNodePtyShell: () => false,
|
||||
getEnableInteractiveShell: () => false,
|
||||
getShellExecutionConfig: () => ({
|
||||
terminalHeight: 20,
|
||||
terminalWidth: 80,
|
||||
|
||||
@@ -160,7 +160,7 @@ export const useShellCommandProcessor = (
|
||||
if (isBinaryStream) break;
|
||||
// PTY provides the full screen state, so we just replace.
|
||||
// Child process provides chunks, so we append.
|
||||
if (config.getShouldUseNodePtyShell()) {
|
||||
if (config.getEnableInteractiveShell()) {
|
||||
cumulativeStdout = event.chunk;
|
||||
shouldUpdate = true;
|
||||
} else if (
|
||||
@@ -221,7 +221,7 @@ export const useShellCommandProcessor = (
|
||||
}
|
||||
},
|
||||
abortSignal,
|
||||
config.getShouldUseNodePtyShell(),
|
||||
config.getEnableInteractiveShell(),
|
||||
shellExecutionConfig,
|
||||
);
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ export interface ConfigParameters {
|
||||
interactive?: boolean;
|
||||
trustedFolder?: boolean;
|
||||
useRipgrep?: boolean;
|
||||
shouldUseNodePtyShell?: boolean;
|
||||
enableInteractiveShell?: boolean;
|
||||
skipNextSpeakerCheck?: boolean;
|
||||
shellExecutionConfig?: ShellExecutionConfig;
|
||||
extensionManagement?: boolean;
|
||||
@@ -333,7 +333,7 @@ export class Config {
|
||||
private readonly interactive: boolean;
|
||||
private readonly trustedFolder: boolean | undefined;
|
||||
private readonly useRipgrep: boolean;
|
||||
private readonly shouldUseNodePtyShell: boolean;
|
||||
private readonly enableInteractiveShell: boolean;
|
||||
private readonly skipNextSpeakerCheck: boolean;
|
||||
private shellExecutionConfig: ShellExecutionConfig;
|
||||
private readonly extensionManagement: boolean = true;
|
||||
@@ -423,7 +423,7 @@ export class Config {
|
||||
this.interactive = params.interactive ?? false;
|
||||
this.trustedFolder = params.trustedFolder;
|
||||
this.useRipgrep = params.useRipgrep ?? true;
|
||||
this.shouldUseNodePtyShell = params.shouldUseNodePtyShell ?? false;
|
||||
this.enableInteractiveShell = params.enableInteractiveShell ?? false;
|
||||
this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? true;
|
||||
this.shellExecutionConfig = {
|
||||
terminalWidth: params.shellExecutionConfig?.terminalWidth ?? 80,
|
||||
@@ -933,8 +933,8 @@ export class Config {
|
||||
return this.useRipgrep;
|
||||
}
|
||||
|
||||
getShouldUseNodePtyShell(): boolean {
|
||||
return this.shouldUseNodePtyShell;
|
||||
getEnableInteractiveShell(): boolean {
|
||||
return this.enableInteractiveShell;
|
||||
}
|
||||
|
||||
getSkipNextSpeakerCheck(): boolean {
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('ShellTool', () => {
|
||||
.fn()
|
||||
.mockReturnValue(createMockWorkspaceContext('/test/dir')),
|
||||
getGeminiClient: vi.fn(),
|
||||
getShouldUseNodePtyShell: vi.fn().mockReturnValue(false),
|
||||
getEnableInteractiveShell: vi.fn().mockReturnValue(false),
|
||||
isInteractive: vi.fn().mockReturnValue(true),
|
||||
} as unknown as Config;
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ export class ShellToolInvocation extends BaseToolInvocation<
|
||||
}
|
||||
},
|
||||
signal,
|
||||
this.config.getShouldUseNodePtyShell(),
|
||||
this.config.getEnableInteractiveShell(),
|
||||
shellExecutionConfig ?? {},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user