Skip to content
2 changes: 1 addition & 1 deletion aider/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from packaging import version

__version__ = "0.88.15.dev"
__version__ = "0.88.16.dev"
safe_version = __version__

try:
Expand Down
3 changes: 2 additions & 1 deletion aider/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2227,6 +2227,7 @@ async def process_tool_calls(self, tool_call_response):
self._print_tool_call_info(server_tool_calls)

if await self.io.confirm_ask("Run tools?", group_response="Run MCP Tools"):
await self.io.recreate_input()
tool_responses = await self._execute_tool_calls(server_tool_calls)

# Add all tool responses
Expand Down Expand Up @@ -2512,7 +2513,7 @@ async def get_server_tools(server):
)
return (server.name, server_tools)
except Exception as e:
if server.name != "unnamed-server":
if server.name != "unnamed-server" and server.name != "local_tools":
self.io.tool_warning(f"Error initializing MCP server {server.name}: {e}")
return None

Expand Down
125 changes: 73 additions & 52 deletions aider/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1051,66 +1051,87 @@ def _handle_read_only_files(self, expanded_word, file_set, description=""):
file_set.remove(matched_file)
self.io.tool_output(f"Removed {description} file {matched_file} from the chat")

def cmd_drop(self, args=""):
async def cmd_drop(self, args=""):
"Remove files from the chat session to free up context space"

if not args.strip():
if self.original_read_only_fnames:
self.io.tool_output(
"Dropping all files from the chat session except originally read-only files."
)
else:
self.io.tool_output("Dropping all files from the chat session.")
self._drop_all_files()
try:
if not args.strip():
if self.original_read_only_fnames:
self.io.tool_output(
"Dropping all files from the chat session except originally read-only"
" files."
)
else:
self.io.tool_output("Dropping all files from the chat session.")
self._drop_all_files()

# Recalculate context block tokens after dropping all files
if hasattr(self.coder, "use_enhanced_context") and self.coder.use_enhanced_context:
if hasattr(self.coder, "_calculate_context_block_tokens"):
self.coder._calculate_context_block_tokens()
return
# Recalculate context block tokens after dropping all files
if hasattr(self.coder, "use_enhanced_context") and self.coder.use_enhanced_context:
if hasattr(self.coder, "_calculate_context_block_tokens"):
self.coder._calculate_context_block_tokens()

filenames = parse_quoted_filenames(args)
files_changed = False
return

for word in filenames:
# Expand tilde in the path
expanded_word = os.path.expanduser(word)
filenames = parse_quoted_filenames(args)
files_changed = False

# Handle read-only files
self._handle_read_only_files(
expanded_word, self.coder.abs_read_only_fnames, "read-only"
)
self._handle_read_only_files(
expanded_word, self.coder.abs_read_only_stubs_fnames, "read-only (stub)"
)
for word in filenames:
# Expand tilde in the path
expanded_word = os.path.expanduser(word)

# For editable files, use glob if word contains glob chars, otherwise use substring
if any(c in expanded_word for c in "*?[]"):
matched_files = self.glob_filtered_to_repo(expanded_word)
else:
# Use substring matching like we do for read-only files
matched_files = [
self.coder.get_rel_fname(f) for f in self.coder.abs_fnames if expanded_word in f
]
# Handle read-only files
self._handle_read_only_files(
expanded_word, self.coder.abs_read_only_fnames, "read-only"
)
self._handle_read_only_files(
expanded_word, self.coder.abs_read_only_stubs_fnames, "read-only (stub)"
)

if not matched_files:
matched_files.append(expanded_word)

for matched_file in matched_files:
abs_fname = self.coder.abs_root_path(matched_file)
if abs_fname in self.coder.abs_fnames:
self.coder.abs_fnames.remove(abs_fname)
self.io.tool_output(f"Removed {matched_file} from the chat")
files_changed = True

# Recalculate context block tokens if any files were changed and using agent mode
if (
files_changed
and hasattr(self.coder, "use_enhanced_context")
and self.coder.use_enhanced_context
):
if hasattr(self.coder, "_calculate_context_block_tokens"):
self.coder._calculate_context_block_tokens()
# For editable files, use glob if word contains glob chars, otherwise use substring
if any(c in expanded_word for c in "*?[]"):
matched_files = self.glob_filtered_to_repo(expanded_word)
else:
# Use substring matching like we do for read-only files
matched_files = [
self.coder.get_rel_fname(f)
for f in self.coder.abs_fnames
if expanded_word in f
]

if not matched_files:
matched_files.append(expanded_word)

for matched_file in matched_files:
abs_fname = self.coder.abs_root_path(matched_file)
if abs_fname in self.coder.abs_fnames:
self.coder.abs_fnames.remove(abs_fname)
self.io.tool_output(f"Removed {matched_file} from the chat")
files_changed = True

# Recalculate context block tokens if any files were changed and using agent mode
if (
files_changed
and hasattr(self.coder, "use_enhanced_context")
and self.coder.use_enhanced_context
):
if hasattr(self.coder, "_calculate_context_block_tokens"):
self.coder._calculate_context_block_tokens()
finally:
if self.coder.repo_map:
map_tokens = self.coder.repo_map.max_map_tokens
map_mul_no_files = self.coder.repo_map.map_mul_no_files
else:
map_tokens = 0
map_mul_no_files = 1

raise SwitchCoder(
edit_format=self.coder.edit_format,
summarize_from_coder=False,
from_coder=self.coder,
map_tokens=map_tokens,
map_mul_no_files=map_mul_no_files,
show_announcements=False,
)

def cmd_git(self, args):
"Run a git command (output excluded from chat)"
Expand Down
48 changes: 28 additions & 20 deletions aider/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def without_input_history(func):
"""Decorator to temporarily disable history saving for the prompt session buffer."""

@functools.wraps(func)
def wrapper(self, *args, **kwargs):
async def wrapper(self, *args, **kwargs):
orig_buf_append = None
try:
orig_buf_append = self.prompt_session.default_buffer.append_to_history
Expand All @@ -105,7 +105,7 @@ def wrapper(self, *args, **kwargs):
pass

try:
return func(self, *args, **kwargs)
return await func(self, *args, **kwargs)
except Exception:
raise
finally:
Expand Down Expand Up @@ -340,6 +340,17 @@ def __init__(
self.notifications = notifications
self.verbose = verbose

# Variables used to interface with base_coder
self.coder = None
self.input_task = None
self.processing_task = None

# State tracking for confirmation input
self.confirmation_in_progress = False
self.confirmation_acknowledgement = False
self.confirmation_input_active = False
self.saved_input_text = ""

if notifications and notifications_command is None:
self.notifications_command = self.get_default_notification_command()
else:
Expand Down Expand Up @@ -413,7 +424,6 @@ def __init__(
self.dry_run = dry_run

current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.append_chat_history(f"\n# aider chat started at {current_time}\n\n")

self.is_dumb_terminal = is_dumb_terminal()
self.is_tty = sys.stdout.isatty()
Expand Down Expand Up @@ -465,19 +475,9 @@ def __init__(
self.file_watcher = file_watcher
self.root = root

# Variables used to interface with base_coder
self.coder = None
self.input_task = None
self.processing_task = None
self.confirmation_in_progress = False
self.confirmation_acknowledgement = False

# State tracking for confirmation input
self.confirmation_input_active = False
self.saved_input_text = ""

# Validate color settings after console is initialized
self._validate_color_settings()
self.append_chat_history(f"\n# aider chat started at {current_time}\n\n")

def _spinner_supports_unicode(self) -> bool:
if not self.is_tty:
Expand Down Expand Up @@ -908,6 +908,8 @@ def get_continuation(width, line_number, is_soft_wrap):
if self.clipboard_watcher:
self.clipboard_watcher.stop()

line = line or ""

if line.strip("\r\n") and not multiline_input:
stripped = line.strip("\r\n")
if stripped == "{":
Expand Down Expand Up @@ -1012,6 +1014,13 @@ def user_input(self, inp, log_only=True):
if not log_only:
self.display_user_input(inp)

if (
len(inp) <= 1
or self.confirmation_in_progress
or self.get_confirmation_acknowledgement()
):
return

prefix = "####"
if inp:
hist = inp.splitlines()
Expand Down Expand Up @@ -1055,6 +1064,7 @@ def acknowledge_confirmation(self):
return outstanding_confirmation

@restore_multiline_async
@without_input_history
async def confirm_ask(
self,
*args,
Expand Down Expand Up @@ -1324,12 +1334,7 @@ def assistant_output(self, message, pretty=None):
if pretty is None:
pretty = self.pretty

if pretty:
show_resp = Markdown(
message, style=self.assistant_output_color, code_theme=self.code_theme
)
else:
show_resp = Text(message or "(empty response)")
show_resp = Text(message or "(empty response)")

self.stream_print(show_resp)

Expand Down Expand Up @@ -1487,6 +1492,9 @@ def toggle_multiline_mode(self):
)

def append_chat_history(self, text, linebreak=False, blockquote=False, strip=True):
if self.confirmation_in_progress or self.get_confirmation_acknowledgement():
return

if blockquote:
if strip:
text = text.strip()
Expand Down
3 changes: 3 additions & 0 deletions aider/tools/grep.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ def _execute_grep(
cmd_args.append("-n") # Line numbers for rg and grep
# ag includes line numbers by default

if tool_name in ["rg"]:
cmd_args.append("--heading") # Filename above output for ripgrep

# Context lines (Before and After)
if context_before > 0:
# All tools use -B for lines before
Expand Down
Loading