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

View File

@@ -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`.

View File

@@ -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,

View File

@@ -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',

View File

@@ -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,

View File

@@ -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({}),
};

View File

@@ -171,7 +171,7 @@ export class ShellProcessor implements IPromptProcessor {
config.getTargetDir(),
() => {},
new AbortController().signal,
config.getShouldUseNodePtyShell(),
config.getEnableInteractiveShell(),
shellExecutionConfig,
);

View File

@@ -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);

View File

@@ -65,7 +65,7 @@ describe('useShellCommandProcessor', () => {
setShellInputFocusedMock = vi.fn();
mockConfig = {
getTargetDir: () => '/test/dir',
getShouldUseNodePtyShell: () => false,
getEnableInteractiveShell: () => false,
getShellExecutionConfig: () => ({
terminalHeight: 20,
terminalWidth: 80,

View File

@@ -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,
);

View File

@@ -253,7 +253,7 @@ export interface ConfigParameters {
interactive?: boolean;
trustedFolder?: boolean;
useRipgrep?: boolean;
shouldUseNodePtyShell?: boolean;
enableInteractiveShell?: boolean;
skipNextSpeakerCheck?: boolean;
shellExecutionConfig?: ShellExecutionConfig;
extensionManagement?: boolean;
@@ -342,7 +342,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;
@@ -432,7 +432,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,
@@ -942,8 +942,8 @@ export class Config {
return this.useRipgrep;
}
getShouldUseNodePtyShell(): boolean {
return this.shouldUseNodePtyShell;
getEnableInteractiveShell(): boolean {
return this.enableInteractiveShell;
}
getSkipNextSpeakerCheck(): boolean {

View File

@@ -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;

View File

@@ -251,7 +251,7 @@ export class ShellToolInvocation extends BaseToolInvocation<
}
},
signal,
this.config.getShouldUseNodePtyShell(),
this.config.getEnableInteractiveShell(),
shellExecutionConfig ?? {},
);