feat: Introduce an AI-driven interactive shell mode with new

`read-shell` and `write-to-shell` tools and a configurable mode setting.
This commit is contained in:
Gaurav Ghosh
2026-03-20 13:39:10 -07:00
parent cbacdc67d0
commit 651ad63ed6
22 changed files with 906 additions and 83 deletions
@@ -56,6 +56,18 @@ export const READ_FILE_PARAM_END_LINE = 'end_line';
export const SHELL_TOOL_NAME = 'run_shell_command';
export const SHELL_PARAM_COMMAND = 'command';
export const SHELL_PARAM_IS_BACKGROUND = 'is_background';
export const SHELL_PARAM_WAIT_SECONDS = 'wait_for_output_seconds';
// -- write_to_shell --
export const WRITE_TO_SHELL_TOOL_NAME = 'write_to_shell';
export const WRITE_TO_SHELL_PARAM_PID = 'pid';
export const WRITE_TO_SHELL_PARAM_INPUT = 'input';
export const WRITE_TO_SHELL_PARAM_SPECIAL_KEYS = 'special_keys';
// -- read_shell --
export const READ_SHELL_TOOL_NAME = 'read_shell';
export const READ_SHELL_PARAM_PID = 'pid';
export const READ_SHELL_PARAM_WAIT_SECONDS = 'wait_seconds';
// -- write_file --
export const WRITE_FILE_TOOL_NAME = 'write_file';
@@ -27,6 +27,8 @@ export {
LS_TOOL_NAME,
READ_FILE_TOOL_NAME,
SHELL_TOOL_NAME,
WRITE_TO_SHELL_TOOL_NAME,
READ_SHELL_TOOL_NAME,
WRITE_FILE_TOOL_NAME,
EDIT_TOOL_NAME,
WEB_SEARCH_TOOL_NAME,
@@ -73,6 +75,12 @@ export {
LS_PARAM_IGNORE,
SHELL_PARAM_COMMAND,
SHELL_PARAM_IS_BACKGROUND,
SHELL_PARAM_WAIT_SECONDS,
WRITE_TO_SHELL_PARAM_PID,
WRITE_TO_SHELL_PARAM_INPUT,
WRITE_TO_SHELL_PARAM_SPECIAL_KEYS,
READ_SHELL_PARAM_PID,
READ_SHELL_PARAM_WAIT_SECONDS,
WEB_SEARCH_PARAM_QUERY,
WEB_FETCH_PARAM_PROMPT,
READ_MANY_PARAM_INCLUDE,
@@ -249,18 +257,21 @@ export function getShellDefinition(
enableInteractiveShell: boolean,
enableEfficiency: boolean,
enableToolSandboxing: boolean = false,
interactiveShellMode?: string,
): ToolDefinition {
return {
base: getShellDeclaration(
enableInteractiveShell,
enableEfficiency,
enableToolSandboxing,
interactiveShellMode,
),
overrides: (modelId) =>
getToolSet(modelId).run_shell_command(
enableInteractiveShell,
enableEfficiency,
enableToolSandboxing,
interactiveShellMode,
),
};
}
@@ -22,6 +22,7 @@ import {
PARAM_DIR_PATH,
SHELL_PARAM_IS_BACKGROUND,
EXIT_PLAN_PARAM_PLAN_FILENAME,
SHELL_PARAM_WAIT_SECONDS,
SKILL_PARAM_NAME,
PARAM_ADDITIONAL_PERMISSIONS,
UPDATE_TOPIC_TOOL_NAME,
@@ -36,7 +37,9 @@ import {
export function getShellToolDescription(
enableInteractiveShell: boolean,
enableEfficiency: boolean,
interactiveShellMode?: string,
): string {
const isAiMode = interactiveShellMode === 'ai';
const efficiencyGuidelines = enableEfficiency
? `
@@ -56,6 +59,11 @@ export function getShellToolDescription(
Background PIDs: Only included if background processes were started.
Process Group PGID: Only included if available.`;
if (isAiMode) {
const autoPromoteInstructions = `Commands that do not complete within \`${SHELL_PARAM_WAIT_SECONDS}\` seconds are automatically promoted to background. Once promoted, use \`write_to_shell\` and \`read_shell\` to interact with the process. Do NOT use \`&\` to background commands.`;
return `This tool executes a given shell command as \`bash -c <command>\`. ${autoPromoteInstructions} Command is executed as a subprocess that leads its own process group. Command process group can be terminated as \`kill -- -PGID\` or signaled as \`kill -s SIGNAL -- -PGID\`.${efficiencyGuidelines}${returnedInfo}`;
}
if (os.platform() === 'win32') {
const backgroundInstructions = enableInteractiveShell
? `To run a command in the background, set the \`${SHELL_PARAM_IS_BACKGROUND}\` parameter to true. Do NOT use PowerShell background constructs.`
@@ -86,12 +94,33 @@ export function getShellDeclaration(
enableInteractiveShell: boolean,
enableEfficiency: boolean,
enableToolSandboxing: boolean = false,
interactiveShellMode?: string,
): FunctionDeclaration {
const isAiMode = interactiveShellMode === 'ai';
// In AI mode, use wait_for_output_seconds instead of is_background
const backgroundParam = isAiMode
? {
[SHELL_PARAM_WAIT_SECONDS]: {
type: 'number' as const,
description:
'Max seconds to wait for command to complete before auto-promoting to background (default: 5). Set low (2-5) for commands likely to prompt for input (npx, installers, REPLs). Set high (60-300) for long builds or installs. Once promoted, use write_to_shell/read_shell to interact.',
},
}
: {
[SHELL_PARAM_IS_BACKGROUND]: {
type: 'boolean' as const,
description:
'Set to true if this command should be run in the background (e.g. for long-running servers or watchers). The command will be started, allowed to run for a brief moment to check for immediate errors, and then moved to the background.',
},
};
return {
name: SHELL_TOOL_NAME,
description: getShellToolDescription(
enableInteractiveShell,
enableEfficiency,
interactiveShellMode,
),
parametersJsonSchema: {
type: 'object',
@@ -120,6 +149,7 @@ export function getShellDeclaration(
description:
'Optional. Delay in milliseconds to wait after starting the process in the background. Useful to allow the process to start and generate initial output before returning.',
},
...backgroundParam,
...(enableToolSandboxing
? {
[PARAM_ADDITIONAL_PERMISSIONS]: {
@@ -337,11 +337,13 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
enableInteractiveShell,
enableEfficiency,
enableToolSandboxing,
interactiveShellMode,
) =>
getShellDeclaration(
enableInteractiveShell,
enableEfficiency,
enableToolSandboxing,
interactiveShellMode,
),
replace: {
@@ -344,11 +344,13 @@ export const GEMINI_3_SET: CoreToolSet = {
enableInteractiveShell,
enableEfficiency,
enableToolSandboxing,
interactiveShellMode,
) =>
getShellDeclaration(
enableInteractiveShell,
enableEfficiency,
enableToolSandboxing,
interactiveShellMode,
),
replace: {
@@ -38,6 +38,7 @@ export interface CoreToolSet {
enableInteractiveShell: boolean,
enableEfficiency: boolean,
enableToolSandboxing: boolean,
interactiveShellMode?: string,
) => FunctionDeclaration;
replace: FunctionDeclaration;
google_web_search: FunctionDeclaration;