From dfd248245c0521a26868f62724f3af3f66ad9890 Mon Sep 17 00:00:00 2001 From: "Amar Sood (tekacs)" Date: Sat, 12 Apr 2025 04:55:00 -0400 Subject: [PATCH] Add CommandInteractive tool --- aider/coders/navigator_coder.py | 49 +++++++++++++++++++++++++++++-- aider/coders/navigator_prompts.py | 4 ++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/aider/coders/navigator_coder.py b/aider/coders/navigator_coder.py index 92ed371e8..6edf488b4 100644 --- a/aider/coders/navigator_coder.py +++ b/aider/coders/navigator_coder.py @@ -18,8 +18,8 @@ from .editblock_coder import find_original_update_blocks, do_replace, find_simil from .navigator_prompts import NavigatorPrompts from aider.repo import ANY_GIT_ERROR from aider import urls -# Import run_cmd_subprocess directly for non-interactive execution -from aider.run_cmd import run_cmd_subprocess +# Import run_cmd for potentially interactive execution and run_cmd_subprocess for guaranteed non-interactive +from aider.run_cmd import run_cmd, run_cmd_subprocess # Import the change tracker from aider.change_tracker import ChangeTracker @@ -577,7 +577,13 @@ class NavigatorCoder(Coder): result_message = self._execute_command(command_string) else: result_message = "Error: Missing 'command_string' parameter for Command" - + elif norm_tool_name == 'commandinteractive': + command_string = params.get('command_string') + if command_string is not None: + result_message = self._execute_command_interactive(command_string) + else: + result_message = "Error: Missing 'command_string' parameter for CommandInteractive" + # Granular editing tools elif norm_tool_name == 'replacetext': file_path = params.get('file_path') @@ -1407,6 +1413,43 @@ Just reply with fixed versions of the {blocks} above that failed to match. # self.io.tool_error(traceback.format_exc()) return f"Error executing command: {str(e)}" + def _execute_command_interactive(self, command_string): + """ + Execute an interactive shell command using run_cmd (which uses pexpect/PTY). + """ + try: + self.io.tool_output(f"⚙️ Starting interactive shell command: {command_string}") + self.io.tool_output(">>> You may need to interact with the command below <<<") + + # Use run_cmd which handles PTY logic + exit_status, combined_output = run_cmd( + command_string, + verbose=self.verbose, # Pass verbose flag + error_print=self.io.tool_error, # Use io for error printing + cwd=self.root # Execute in the project root + ) + + self.io.tool_output(">>> Interactive command finished <<<") + + # Format the output for the result message, include more content + output_content = combined_output or "" + # Use the existing token threshold constant as the character limit for truncation + output_limit = self.large_file_token_threshold + if len(output_content) > output_limit: + # Truncate and add a clear message using the constant value + output_content = output_content[:output_limit] + f"\n... (output truncated at {output_limit} characters, based on large_file_token_threshold)" + + if exit_status == 0: + return f"Interactive command finished successfully (exit code 0). Output:\n{output_content}" + else: + return f"Interactive command finished with exit code {exit_status}. Output:\n{output_content}" + + except Exception as e: + self.io.tool_error(f"Error executing interactive shell command '{command_string}': {str(e)}") + # Optionally include traceback for debugging if verbose + # if self.verbose: + # self.io.tool_error(traceback.format_exc()) + return f"Error executing interactive command: {str(e)}" def _process_file_mentions(self, content): """ diff --git a/aider/coders/navigator_prompts.py b/aider/coders/navigator_prompts.py index 9f2f8fd1d..0d747736d 100644 --- a/aider/coders/navigator_prompts.py +++ b/aider/coders/navigator_prompts.py @@ -94,7 +94,9 @@ Act as an expert software engineer with the ability to autonomously navigate and ### Other Tools - **Command**: `[tool_call(Command, command_string="git diff HEAD~1")]` - Execute a shell command. Requires user confirmation. + Execute a *non-interactive* shell command. Requires user confirmation. Use for commands that don't need user input (e.g., `ls`, `git status`, `cat file`). +- **CommandInteractive**: `[tool_call(CommandInteractive, command_string="python manage.py shell")]` + Execute an *interactive* shell command using a pseudo-terminal (PTY). Use for commands that might require user interaction (e.g., running a shell, a development server, `ssh`). Does *not* require separate confirmation as interaction happens directly. ### Multi-Turn Exploration When you include any tool call, the system will automatically continue to the next round.