chore(shell): Enable interactive shell by default (#10661)

This commit is contained in:
Gal Zahavi
2025-10-08 13:28:19 -07:00
committed by GitHub
parent b0b1be0c2a
commit 467a305f26
12 changed files with 19 additions and 18 deletions
+2 -2
View File
@@ -206,8 +206,8 @@ Settings are organized into categories. All settings should be placed within the
- **Default:** `undefined` - **Default:** `undefined`
- **`tools.shell.enableInteractiveShell`** (boolean): - **`tools.shell.enableInteractiveShell`** (boolean):
- **Description:** Enables interactive terminal for running shell commands. If an interactive session cannot be started, it will fall back to a standard shell.
Use `node-pty` for an interactive shell experience. Fallback to `child_process` still applies. Defaults to `false`. - **Default:** `true`
- **`tools.core`** (array of strings): - **`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`. - **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`.
+2 -1
View File
@@ -737,7 +737,8 @@ export async function loadCliConfig(
interactive, interactive,
trustedFolder, trustedFolder,
useRipgrep: settings.tools?.useRipgrep, useRipgrep: settings.tools?.useRipgrep,
shouldUseNodePtyShell: settings.tools?.shell?.enableInteractiveShell, enableInteractiveShell:
settings.tools?.shell?.enableInteractiveShell ?? true,
skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck, skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
enablePromptCompletion: settings.general?.enablePromptCompletion ?? false, enablePromptCompletion: settings.general?.enablePromptCompletion ?? false,
truncateToolOutputThreshold: settings.tools?.truncateToolOutputThreshold, truncateToolOutputThreshold: settings.tools?.truncateToolOutputThreshold,
+1 -1
View File
@@ -110,7 +110,7 @@ const MIGRATION_MAP: Record<string, string> = {
preferredEditor: 'general.preferredEditor', preferredEditor: 'general.preferredEditor',
sandbox: 'tools.sandbox', sandbox: 'tools.sandbox',
selectedAuthType: 'security.auth.selectedType', selectedAuthType: 'security.auth.selectedType',
shouldUseNodePtyShell: 'tools.shell.enableInteractiveShell', enableInteractiveShell: 'tools.shell.enableInteractiveShell',
shellPager: 'tools.shell.pager', shellPager: 'tools.shell.pager',
shellShowColor: 'tools.shell.showColor', shellShowColor: 'tools.shell.showColor',
skipNextSpeakerCheck: 'model.skipNextSpeakerCheck', skipNextSpeakerCheck: 'model.skipNextSpeakerCheck',
+1 -1
View File
@@ -713,7 +713,7 @@ const SETTINGS_SCHEMA = {
label: 'Enable Interactive Shell', label: 'Enable Interactive Shell',
category: 'Tools', category: 'Tools',
requiresRestart: true, requiresRestart: true,
default: false, default: true,
description: description:
'Use node-pty for an interactive shell experience. Fallback to child_process still applies.', 'Use node-pty for an interactive shell experience. Fallback to child_process still applies.',
showInDialog: true, showInDialog: true,
@@ -70,7 +70,7 @@ describe('ShellProcessor', () => {
mockConfig = { mockConfig = {
getTargetDir: vi.fn().mockReturnValue('/test/dir'), getTargetDir: vi.fn().mockReturnValue('/test/dir'),
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT), getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
getShouldUseNodePtyShell: vi.fn().mockReturnValue(false), getEnableInteractiveShell: vi.fn().mockReturnValue(false),
getShellExecutionConfig: vi.fn().mockReturnValue({}), getShellExecutionConfig: vi.fn().mockReturnValue({}),
}; };
@@ -171,7 +171,7 @@ export class ShellProcessor implements IPromptProcessor {
config.getTargetDir(), config.getTargetDir(),
() => {}, () => {},
new AbortController().signal, new AbortController().signal,
config.getShouldUseNodePtyShell(), config.getEnableInteractiveShell(),
shellExecutionConfig, shellExecutionConfig,
); );
@@ -93,7 +93,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
const isThisShellFocusable = const isThisShellFocusable =
(name === SHELL_COMMAND_NAME || name === 'Shell') && (name === SHELL_COMMAND_NAME || name === 'Shell') &&
status === ToolCallStatus.Executing && status === ToolCallStatus.Executing &&
config?.getShouldUseNodePtyShell(); config?.getEnableInteractiveShell();
const shouldShowFocusHint = const shouldShowFocusHint =
isThisShellFocusable && (showFocusHint || userHasFocused); isThisShellFocusable && (showFocusHint || userHasFocused);
@@ -65,7 +65,7 @@ describe('useShellCommandProcessor', () => {
setShellInputFocusedMock = vi.fn(); setShellInputFocusedMock = vi.fn();
mockConfig = { mockConfig = {
getTargetDir: () => '/test/dir', getTargetDir: () => '/test/dir',
getShouldUseNodePtyShell: () => false, getEnableInteractiveShell: () => false,
getShellExecutionConfig: () => ({ getShellExecutionConfig: () => ({
terminalHeight: 20, terminalHeight: 20,
terminalWidth: 80, terminalWidth: 80,
@@ -160,7 +160,7 @@ export const useShellCommandProcessor = (
if (isBinaryStream) break; if (isBinaryStream) break;
// PTY provides the full screen state, so we just replace. // PTY provides the full screen state, so we just replace.
// Child process provides chunks, so we append. // Child process provides chunks, so we append.
if (config.getShouldUseNodePtyShell()) { if (config.getEnableInteractiveShell()) {
cumulativeStdout = event.chunk; cumulativeStdout = event.chunk;
shouldUpdate = true; shouldUpdate = true;
} else if ( } else if (
@@ -221,7 +221,7 @@ export const useShellCommandProcessor = (
} }
}, },
abortSignal, abortSignal,
config.getShouldUseNodePtyShell(), config.getEnableInteractiveShell(),
shellExecutionConfig, shellExecutionConfig,
); );
+5 -5
View File
@@ -253,7 +253,7 @@ export interface ConfigParameters {
interactive?: boolean; interactive?: boolean;
trustedFolder?: boolean; trustedFolder?: boolean;
useRipgrep?: boolean; useRipgrep?: boolean;
shouldUseNodePtyShell?: boolean; enableInteractiveShell?: boolean;
skipNextSpeakerCheck?: boolean; skipNextSpeakerCheck?: boolean;
shellExecutionConfig?: ShellExecutionConfig; shellExecutionConfig?: ShellExecutionConfig;
extensionManagement?: boolean; extensionManagement?: boolean;
@@ -342,7 +342,7 @@ export class Config {
private readonly interactive: boolean; private readonly interactive: boolean;
private readonly trustedFolder: boolean | undefined; private readonly trustedFolder: boolean | undefined;
private readonly useRipgrep: boolean; private readonly useRipgrep: boolean;
private readonly shouldUseNodePtyShell: boolean; private readonly enableInteractiveShell: boolean;
private readonly skipNextSpeakerCheck: boolean; private readonly skipNextSpeakerCheck: boolean;
private shellExecutionConfig: ShellExecutionConfig; private shellExecutionConfig: ShellExecutionConfig;
private readonly extensionManagement: boolean = true; private readonly extensionManagement: boolean = true;
@@ -432,7 +432,7 @@ export class Config {
this.interactive = params.interactive ?? false; this.interactive = params.interactive ?? false;
this.trustedFolder = params.trustedFolder; this.trustedFolder = params.trustedFolder;
this.useRipgrep = params.useRipgrep ?? true; this.useRipgrep = params.useRipgrep ?? true;
this.shouldUseNodePtyShell = params.shouldUseNodePtyShell ?? false; this.enableInteractiveShell = params.enableInteractiveShell ?? false;
this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? true; this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? true;
this.shellExecutionConfig = { this.shellExecutionConfig = {
terminalWidth: params.shellExecutionConfig?.terminalWidth ?? 80, terminalWidth: params.shellExecutionConfig?.terminalWidth ?? 80,
@@ -942,8 +942,8 @@ export class Config {
return this.useRipgrep; return this.useRipgrep;
} }
getShouldUseNodePtyShell(): boolean { getEnableInteractiveShell(): boolean {
return this.shouldUseNodePtyShell; return this.enableInteractiveShell;
} }
getSkipNextSpeakerCheck(): boolean { getSkipNextSpeakerCheck(): boolean {
+1 -1
View File
@@ -60,7 +60,7 @@ describe('ShellTool', () => {
.fn() .fn()
.mockReturnValue(createMockWorkspaceContext('/test/dir')), .mockReturnValue(createMockWorkspaceContext('/test/dir')),
getGeminiClient: vi.fn(), getGeminiClient: vi.fn(),
getShouldUseNodePtyShell: vi.fn().mockReturnValue(false), getEnableInteractiveShell: vi.fn().mockReturnValue(false),
isInteractive: vi.fn().mockReturnValue(true), isInteractive: vi.fn().mockReturnValue(true),
} as unknown as Config; } as unknown as Config;
+1 -1
View File
@@ -251,7 +251,7 @@ export class ShellToolInvocation extends BaseToolInvocation<
} }
}, },
signal, signal,
this.config.getShouldUseNodePtyShell(), this.config.getEnableInteractiveShell(),
shellExecutionConfig ?? {}, shellExecutionConfig ?? {},
); );