From f3918f950af44de7774c7e802b704e2814aa2d96 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Wed, 12 Nov 2025 08:48:11 -0500 Subject: [PATCH 1/8] Bump Version --- aider/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/__init__.py b/aider/__init__.py index 71d22aafe3d..dc47253c91e 100644 --- a/aider/__init__.py +++ b/aider/__init__.py @@ -1,6 +1,6 @@ from packaging import version -__version__ = "0.88.15.dev" +__version__ = "0.88.16.dev" safe_version = __version__ try: From 7581b4f44283e3dedb36b2539f1061b12f7d2325 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Wed, 12 Nov 2025 21:30:39 -0500 Subject: [PATCH 2/8] Make drop command work more intuitively by refreshing the Coder class on completion --- aider/coders/base_coder.py | 2 +- aider/commands.py | 125 ++++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 3f78eaf50f4..1bbb5682972 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -2512,7 +2512,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 diff --git a/aider/commands.py b/aider/commands.py index b9ea302db89..1a2802350da 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -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)" From 7da1c153dcdaf9affadd1e56aa5e9ffeffe45fd6 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Wed, 12 Nov 2025 21:48:59 -0500 Subject: [PATCH 3/8] Maintain spinner and secondary prompt through repeated tool call confirmations --- aider/coders/base_coder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 1bbb5682972..04b32bcb5d0 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -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 From 686ab35259c186f38f3d4bd1d22db04dd31db218 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Wed, 12 Nov 2025 22:13:40 -0500 Subject: [PATCH 4/8] #88: The markdown renderer in the non-streaming mode might play a role in messed up output since the markdown output is weird all on it's own --- aider/io.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/aider/io.py b/aider/io.py index b307790d576..310992e4100 100644 --- a/aider/io.py +++ b/aider/io.py @@ -1324,12 +1324,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) From 89d9169ec0259da061cc212c94739220c7cdd7df Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Wed, 12 Nov 2025 22:21:02 -0500 Subject: [PATCH 5/8] Add --heading to rg so it doesn't duplicate file names on every line --- aider/tools/grep.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aider/tools/grep.py b/aider/tools/grep.py index 4be93b162c8..158e75826e6 100644 --- a/aider/tools/grep.py +++ b/aider/tools/grep.py @@ -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 From 006af2fc85399a3ea43153c0db8b045df4be9d66 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Wed, 12 Nov 2025 23:18:53 -0500 Subject: [PATCH 6/8] #126: A more rigorous way to prevent confirmations from being recorded in the prompt history --- aider/io.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/aider/io.py b/aider/io.py index 310992e4100..afb9e971075 100644 --- a/aider/io.py +++ b/aider/io.py @@ -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 @@ -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: @@ -413,7 +413,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() @@ -478,6 +477,7 @@ def __init__( # 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: @@ -1012,6 +1012,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() @@ -1055,6 +1062,7 @@ def acknowledge_confirmation(self): return outstanding_confirmation @restore_multiline_async + @without_input_history async def confirm_ask( self, *args, @@ -1482,6 +1490,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() From 2f7d08a33e93bd2a2e5563b715fbbe5b07873ba7 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Wed, 12 Nov 2025 23:45:05 -0500 Subject: [PATCH 7/8] #116 and #134: One liner for this PR, ensure line is a string so string methods work on it --- aider/io.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aider/io.py b/aider/io.py index afb9e971075..59d98c1c5b0 100644 --- a/aider/io.py +++ b/aider/io.py @@ -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 == "{": From 42adc49522e9d4e6575aa13dbb5abbea6bbad033 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Wed, 12 Nov 2025 23:50:49 -0500 Subject: [PATCH 8/8] Set up state tracking variables before any functions using them --- aider/io.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/aider/io.py b/aider/io.py index 59d98c1c5b0..6b18569490b 100644 --- a/aider/io.py +++ b/aider/io.py @@ -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: @@ -464,17 +475,6 @@ 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")