Skip to content

Commit f18a419

Browse files
committed
Attempt to prevent exploration loops (works-ish but models are disobedient)
1 parent e86af64 commit f18a419

11 files changed

Lines changed: 448 additions & 627 deletions

aider/coders/base_coder.py

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,9 @@ async def _run_patched(self, with_message=None, preproc=True):
10571057
self.show_announcements()
10581058
self.suppress_announcements_for_next_prompt = False
10591059

1060+
# Stop spinner before showing announcements or getting input
1061+
self.io.stop_spinner()
1062+
10601063
self.copy_context()
10611064
self.input_task = asyncio.create_task(self.get_input())
10621065
input_task = self.input_task
@@ -1080,6 +1083,7 @@ async def _run_patched(self, with_message=None, preproc=True):
10801083
await processing_task
10811084
except asyncio.CancelledError:
10821085
pass
1086+
self.io.stop_spinner()
10831087
processing_task = None
10841088

10851089
try:
@@ -1097,16 +1101,22 @@ async def _run_patched(self, with_message=None, preproc=True):
10971101
except (asyncio.CancelledError, KeyboardInterrupt):
10981102
pass
10991103
processing_task = None
1104+
# Stop spinner when processing task completes
1105+
self.io.stop_spinner()
11001106

11011107
if user_message and self.run_one_completed and self.compact_context_completed:
11021108
processing_task = asyncio.create_task(
11031109
self._processing_logic(user_message, preproc)
11041110
)
1111+
# Start spinner for processing task
1112+
self.io.start_spinner("Processing...")
11051113
user_message = None # Clear message after starting task
11061114
except KeyboardInterrupt:
11071115
if processing_task:
11081116
processing_task.cancel()
11091117
processing_task = None
1118+
# Stop spinner when processing task is cancelled
1119+
self.io.stop_spinner()
11101120
if input_task:
11111121
self.io.set_placeholder("")
11121122
input_task.cancel()
@@ -1176,6 +1186,9 @@ async def run_one(self, user_message, preproc):
11761186
else:
11771187
message = user_message
11781188

1189+
if self.commands.is_command(user_message):
1190+
return
1191+
11791192
while True:
11801193
self.reflected_message = None
11811194
self.tool_reflection = False
@@ -1881,7 +1894,7 @@ async def send_message(self, inp):
18811894
]
18821895
return
18831896

1884-
edited = self.apply_updates()
1897+
edited = await self.apply_updates()
18851898

18861899
if edited:
18871900
self.aider_edited_files.update(edited)
@@ -2496,6 +2509,48 @@ async def check_for_file_mentions(self, content):
24962509
return prompts.added_files.format(fnames=", ".join(added_fnames))
24972510

24982511
async def send(self, messages, model=None, functions=None, tools=None):
2512+
# Add tool usage context if this is a navigator coder with tool history
2513+
if hasattr(self, "tool_usage_history") and self.tool_usage_history:
2514+
# Get the last user message
2515+
last_user_message = messages[-1]
2516+
repetitive_tools = (
2517+
self._get_repetitive_tools() if hasattr(self, "_get_repetitive_tools") else set()
2518+
)
2519+
2520+
if repetitive_tools:
2521+
tool_context = (
2522+
self._generate_tool_context(repetitive_tools)
2523+
if hasattr(self, "_generate_tool_context")
2524+
else ""
2525+
)
2526+
2527+
if tool_context and "content" in last_user_message:
2528+
# Add tool context to the user message
2529+
if messages[-1].get("role") == "user":
2530+
messages[-1][
2531+
"content"
2532+
] = f"{tool_context}\n\n{last_user_message['content']}"
2533+
2534+
# Filter out repetitive tools from the context
2535+
if tools:
2536+
tools = [
2537+
tool
2538+
for tool in tools
2539+
if tool.get("function", {}).get("name", "") not in repetitive_tools
2540+
]
2541+
if functions:
2542+
functions = [
2543+
func
2544+
for func in functions
2545+
if func.get("function", {}).get("name", "") not in repetitive_tools
2546+
]
2547+
2548+
if self.verbose:
2549+
self.io.tool_output(
2550+
"Temporarily hiding repetitive tool(s) to encourage progress:"
2551+
f" {', '.join(sorted(repetitive_tools))}"
2552+
)
2553+
24992554
self.got_reasoning_content = False
25002555
self.ended_reasoning_content = False
25012556

@@ -2996,7 +3051,7 @@ def check_for_dirty_commit(self, path):
29963051
self.io.tool_output(f"Committing {path} before applying edits.")
29973052
self.need_commit_before_edits.add(path)
29983053

2999-
def allowed_to_edit(self, path):
3054+
async def allowed_to_edit(self, path):
30003055
full_path = self.abs_root_path(path)
30013056
if self.repo:
30023057
need_to_add = not self.repo.path_in_repo(path)
@@ -3031,7 +3086,7 @@ def allowed_to_edit(self, path):
30313086
self.check_added_files()
30323087
return True
30333088

3034-
if not self.io.confirm_ask(
3089+
if not await self.io.confirm_ask(
30353090
"Allow edits to file that has not been added to the chat?",
30363091
subject=path,
30373092
):
@@ -3074,7 +3129,7 @@ def check_added_files(self):
30743129
self.io.tool_warning(urls.edit_errors)
30753130
self.warning_given = True
30763131

3077-
def prepare_to_edit(self, edits):
3132+
async def prepare_to_edit(self, edits):
30783133
res = []
30793134
seen = dict()
30803135

@@ -3090,7 +3145,7 @@ def prepare_to_edit(self, edits):
30903145
if path in seen:
30913146
allowed = seen[path]
30923147
else:
3093-
allowed = self.allowed_to_edit(path)
3148+
allowed = await self.allowed_to_edit(path)
30943149
seen[path] = allowed
30953150

30963151
if allowed:
@@ -3101,12 +3156,12 @@ def prepare_to_edit(self, edits):
31013156

31023157
return res
31033158

3104-
def apply_updates(self):
3159+
async def apply_updates(self):
31053160
edited = set()
31063161
try:
31073162
edits = self.get_edits()
31083163
edits = self.apply_edits_dry_run(edits)
3109-
edits = self.prepare_to_edit(edits)
3164+
edits = await self.prepare_to_edit(edits)
31103165
edited = set(edit[0] for edit in edits)
31113166

31123167
self.apply_edits(edits)

aider/coders/editblock_func_coder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def render_incremental_response(self, final=False):
9292
res = json.dumps(args, indent=4)
9393
return res
9494

95-
def _update_files(self):
95+
async def _update_files(self):
9696
name = self.partial_response_function_call.get("name")
9797

9898
if name and name != "replace_lines":
@@ -121,7 +121,7 @@ def _update_files(self):
121121
if updated and not updated.endswith("\n"):
122122
updated += "\n"
123123

124-
full_path = self.allowed_to_edit(path)
124+
full_path = await self.allowed_to_edit(path)
125125
if not full_path:
126126
continue
127127
content = self.io.read_text(full_path)

0 commit comments

Comments
 (0)