Add CommandInteractive tool

This commit is contained in:
Amar Sood (tekacs) 2025-04-12 04:55:00 -04:00
parent 9e1900a126
commit dfd248245c
2 changed files with 49 additions and 4 deletions

View file

@ -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,6 +577,12 @@ 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':
@ -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):
"""

View file

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