Skip to content
This repository was archived by the owner on Apr 18, 2026. It is now read-only.

Commit e6a486b

Browse files
committed
✨ ui updated
1 parent 30d4015 commit e6a486b

11 files changed

Lines changed: 574 additions & 125 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ $ python run.py
100100
| Search engine with bypass | Active ||
101101
| Command Permission System | Active ||
102102
| Advanced History Management System | Active ||
103+
| Automatic Prompt Improvement System | Active ||
103104

104105
<details>
105106
<summary>Other features</summary>
@@ -130,7 +131,7 @@ $ python run.py
130131

131132
And coming soon...
132133

133-
# Frequently Asked Questions
134+
## Frequently Asked Questions
134135

135136
<details>
136137
<summary>What is the Multi-Key Pool System?</summary>

dymo-code.spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ hiddenimports = [
9898
'src.utils',
9999
'src.utils.basics',
100100
'src.ignore_patterns',
101+
'src.prompt_enhancer',
101102
# Cloudscraper bypass modules
102103
'src.utils.bypasses',
103104
'src.utils.bypasses.cloudscraper',

src/agent.py

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
from .ui import (
3030
console, display_tool_call, display_tool_result, display_executed_tool,
3131
display_code_execution_result, display_info, display_warning,
32-
display_key_rotation_notice, display_model_fallback_notice, display_provider_exhausted_notice
32+
display_key_rotation_notice, display_model_fallback_notice, display_provider_exhausted_notice,
33+
display_assistant_response
3334
)
3435
from .logger import log_error, log_api_error, log_tool_error, log_debug
3536
from .history import history_manager
@@ -402,6 +403,20 @@ def _save_conversation(self):
402403
"""Save the current conversation state"""
403404
history_manager.update_conversation(self.messages)
404405

406+
def _filter_valid_tool_calls(self, tool_calls: List[Any]) -> List[Any]:
407+
"""
408+
Filter out tool calls for unknown/invalid tools.
409+
Some models (especially Llama) may hallucinate tool names like 'commentary'.
410+
"""
411+
valid_calls = []
412+
for tc in tool_calls:
413+
tool_name = tc.name if hasattr(tc, 'name') else tc.get('name', '')
414+
if tool_name in TOOLS:
415+
valid_calls.append(tc)
416+
else:
417+
log_debug(f"Ignoring unknown tool call: {tool_name}")
418+
return valid_calls
419+
405420
def _parse_tool_calls_from_text(self, text: str) -> List[ToolCall]:
406421
"""
407422
Parse tool calls that appear as text in the response.
@@ -668,8 +683,7 @@ def _process_tool_calls_with_retry(
668683
follow_up_response = ""
669684
pending_tool_calls = []
670685
has_content = False
671-
live_display = None
672-
chunk_count = 0 # Buffer: only update display every N chunks.
686+
chunk_count = 0 # Buffer counter for streaming.
673687

674688
# Stream with Live panel for smooth updates.
675689
for chunk in client.stream_chat(
@@ -680,32 +694,26 @@ def _process_tool_calls_with_retry(
680694
if chunk.content:
681695
if not has_content:
682696
has_content = True
683-
# Stop spinner and start Live display
697+
# Stop spinner when streaming starts
684698
self._update_status("streaming", "")
685-
live_display = Live(
686-
Panel(Markdown(follow_up_response or "..."), title="Assistant", title_align="left", border_style=COLORS['secondary'], box=ROUNDED),
687-
console=console,
688-
refresh_per_second=4,
689-
transient=True
690-
)
691-
live_display.start()
692699

693700
follow_up_response += chunk.content
694701
chunk_count += 1
695-
# Update the live panel only every 5 chunks to reduce re-renders.
696-
if live_display and chunk_count % 5 == 0: live_display.update(Panel(Markdown(follow_up_response), title="Assistant", title_align="left", border_style=COLORS['secondary'], box=ROUNDED))
697702

698703
if chunk.tool_calls:
699-
pending_tool_calls.extend(chunk.tool_calls)
704+
# Filter out invalid/unknown tool calls
705+
valid_calls = self._filter_valid_tool_calls(chunk.tool_calls)
706+
pending_tool_calls.extend(valid_calls)
700707

701-
# Stop live display and show final panel.
702-
if live_display: live_display.stop()
703-
if has_content and follow_up_response: console.print(Panel(Markdown(follow_up_response), title="Assistant", title_align="left", border_style=COLORS['secondary'], box=ROUNDED))
708+
# Show final response using centralized display
709+
if has_content and follow_up_response:
710+
display_assistant_response(follow_up_response)
704711

705712
# Check for tool calls in text response
706713
if not pending_tool_calls and follow_up_response:
707714
text_tool_calls = self._parse_tool_calls_from_text(follow_up_response)
708-
if text_tool_calls: pending_tool_calls.extend(text_tool_calls)
715+
if text_tool_calls:
716+
pending_tool_calls.extend(text_tool_calls)
709717

710718
# If the model wants to use more tools, process them (for multi-step tasks)
711719
if pending_tool_calls and allow_more_tools:
@@ -783,8 +791,7 @@ def chat(self, user_input: str, _retry_count: int = 0) -> str:
783791
# Get all tools including MCP tools.
784792
all_tools = get_all_tool_definitions()
785793
has_started_streaming = False
786-
live_display = None
787-
chunk_count = 0 # Buffer: only update display every N chunks.
794+
chunk_count = 0 # Buffer counter for streaming.
788795

789796
# Update status to generating.
790797
self._update_status("generating", "")
@@ -798,28 +805,16 @@ def chat(self, user_input: str, _retry_count: int = 0) -> str:
798805
if chunk.content:
799806
if not has_started_streaming:
800807
has_started_streaming = True
801-
# Stop spinner before showing content
808+
# Stop spinner when streaming starts
802809
self._update_status("streaming", "")
803-
# Start Live display for streaming
804-
live_display = Live(
805-
Panel(Markdown(response_text or "..."), title="Assistant", title_align="left", border_style=COLORS['secondary'], box=ROUNDED),
806-
console=console,
807-
refresh_per_second=4,
808-
transient=True
809-
)
810-
live_display.start()
811810

812811
response_text += chunk.content
813812
chunk_count += 1
814-
# Update the live panel only every 5 chunks to reduce re-renders
815-
if live_display and chunk_count % 5 == 0:
816-
live_display.update(
817-
Panel(Markdown(response_text), title="Assistant", title_align="left", border_style=COLORS['secondary'], box=ROUNDED)
818-
)
819813

820-
# Collect tool calls
814+
# Collect tool calls (filter out unknown tools)
821815
if chunk.tool_calls:
822-
pending_tool_calls.extend(chunk.tool_calls)
816+
valid_calls = self._filter_valid_tool_calls(chunk.tool_calls)
817+
pending_tool_calls.extend(valid_calls)
823818

824819
# Collect executed tools (from Groq built-in tools like code_interpreter)
825820
if chunk.executed_tools:
@@ -829,11 +824,10 @@ def chat(self, user_input: str, _retry_count: int = 0) -> str:
829824
if chunk.reasoning:
830825
log_debug(f"Model reasoning: {chunk.reasoning[:200]}...")
831826

832-
# Stop live display and show final panel
833-
if live_display:
834-
live_display.stop()
835-
if has_started_streaming and response_text:
836-
console.print(Panel(Markdown(response_text), title="Assistant", title_align="left", border_style=COLORS['secondary'], box=ROUNDED))
827+
# Show final response using centralized display (only if we have content and no tool calls)
828+
# If there are tool calls, the response will be shown after tool execution
829+
if has_started_streaming and response_text and not pending_tool_calls:
830+
display_assistant_response(response_text)
837831

838832
# Display executed tools (code execution, web search, etc.)
839833
if executed_tools_list:
@@ -849,6 +843,7 @@ def chat(self, user_input: str, _retry_count: int = 0) -> str:
849843
text_tool_calls = self._parse_tool_calls_from_text(response_text)
850844
if text_tool_calls:
851845
log_debug(f"Found {len(text_tool_calls)} tool call(s) in text response")
846+
# Already filtered by _parse_tool_calls_from_text
852847
pending_tool_calls.extend(text_tool_calls)
853848

854849
# Process tool calls if any

src/async_input.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,34 @@ def _windows_input_loop(self):
225225
sys.stdout.write(buffer)
226226
sys.stdout.flush()
227227
elif "@" in buffer:
228-
# Path autocomplete
228+
# Path autocomplete with drill-down for directories
229229
path_suggestions = get_path_suggestions(buffer)
230230
if path_suggestions:
231231
# Find @ position and replace path part
232232
at_index = buffer.rfind("@")
233-
new_path = path_suggestions[0]["path"]
233+
selected = path_suggestions[0]
234+
new_path = selected["path"]
234235
buffer = buffer[:at_index + 1] + new_path
236+
237+
# If it's a directory, keep drilling down
238+
# (get_path_suggestions will show contents on next Tab)
235239
self._clear_line()
236240
self._show_prompt()
237241
sys.stdout.write(buffer)
238242
sys.stdout.flush()
243+
244+
# Show hint for directories with content
245+
if selected["is_dir"] and selected.get("file_count", 0) + selected.get("dir_count", 0) > 0:
246+
meta = selected.get("meta", "")
247+
if meta:
248+
sys.stdout.write(f" [{meta}]")
249+
sys.stdout.flush()
250+
# Clear the hint after showing
251+
time.sleep(0.5)
252+
self._clear_line()
253+
self._show_prompt()
254+
sys.stdout.write(buffer)
255+
sys.stdout.flush()
239256
continue
240257

241258
# Special keys (arrows)

src/config.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,26 @@ def items(self):
355355
3. **NEVER GIVE UP**: On error → analyze → fix → retry until success
356356
4. **VERIFY**: Run code, read files to confirm changes work
357357
358+
## PROBLEM SOLVING - CRITICAL
359+
When asked to compare, find missing items, or sync between sources:
360+
1. **GATHER ALL DATA FIRST**: Read/list BOTH sources completely before concluding
361+
2. **BUILD EXPLICIT LISTS**: Create lists of items from each source
362+
3. **COMPARE SYSTEMATICALLY**: Check each item from source A against source B
363+
4. **LIST DIFFERENCES**: Explicitly state what's missing/different
364+
5. **EXECUTE ALL FIXES**: Don't stop after one fix - complete the entire task
365+
366+
Example: "Add missing algorithms from folder A to file B"
367+
- Step 1: List ALL files in folder A → ["algo1", "algo2", "algo3", "algo4"]
368+
- Step 2: Read file B and extract ALL existing algorithms → ["algo1", "algo2"]
369+
- Step 3: Compare: Missing = ["algo3", "algo4"]
370+
- Step 4: Add EACH missing item, don't stop until all are added
371+
- Step 5: Verify by re-reading file B
372+
373+
## TASK ITERATION
374+
- If user says "there are more" or "not fixed" → RE-ANALYZE from scratch
375+
- Never assume previous analysis was complete
376+
- Re-read sources, rebuild lists, find what was missed
377+
358378
## TASK DIVISION (spawn_agents)
359379
For 3+ independent subtasks, use spawn_agents to parallelize work.
360380

src/interactive_input.py

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,46 +221,76 @@ def _render_path_suggestions(self) -> Panel:
221221
table = Table(show_header=False, box=None, padding=(0, 1))
222222
table.add_column("Icon", width=3)
223223
table.add_column("Name", style="white")
224-
table.add_column("Type", style=f"{COLORS['muted']}")
224+
table.add_column("Info", style=f"{COLORS['muted']}")
225225

226-
for i, path_info in enumerate(self.suggestions[:8]):
226+
for i, path_info in enumerate(self.suggestions[:10]):
227227
icon = path_info["icon"]
228228
name = path_info["name"]
229-
meta = "directory" if path_info["is_dir"] else "file"
229+
# Use meta if available (contains file count for dirs, size for files)
230+
meta = path_info.get("meta", "directory" if path_info["is_dir"] else "file")
231+
232+
# Add visual hint for directories with content
233+
if path_info["is_dir"]:
234+
name_display = f"{name}/"
235+
# Add arrow hint to indicate drill-down
236+
if path_info.get("file_count", 0) > 0 or path_info.get("dir_count", 0) > 0:
237+
name_display += " →"
238+
else:
239+
name_display = name
230240

231241
if i == self.selected_index:
232242
table.add_row(
233243
f"[{COLORS['primary']}]{icon}[/]",
234-
f"[bold {COLORS['primary']}]{name}[/]",
244+
f"[bold {COLORS['primary']}]{name_display}[/]",
235245
f"[{COLORS['primary']}]{meta}[/]"
236246
)
237247
else:
238-
table.add_row(icon, name, meta)
248+
table.add_row(icon, name_display, meta)
249+
250+
# Add hint at the bottom
251+
hint = "[Tab] select [Enter] confirm [Esc] cancel"
252+
if any(s["is_dir"] for s in self.suggestions[:10]):
253+
hint = "[Tab] drill-down [Enter] confirm folder [Esc] cancel"
239254

240255
return Panel(
241256
table,
242257
title="[bold]@ Path[/bold]",
258+
subtitle=f"[{COLORS['muted']}]{hint}[/]",
243259
border_style=COLORS['primary'],
244260
box=ROUNDED,
245261
padding=(0, 1)
246262
)
247263

248-
def _complete_selected(self):
249-
"""Complete input with selected suggestion"""
264+
def _complete_selected(self, force_close: bool = False):
265+
"""
266+
Complete input with selected suggestion.
267+
For directories: drill-down to show contents (unless force_close=True)
268+
For files: complete and close suggestions
269+
"""
250270
if self.suggestions and 0 <= self.selected_index < len(self.suggestions):
251271
if self.suggestion_type == "command":
252272
cmd = self.suggestions[self.selected_index]
253273
self.buffer = f"/{cmd.name}"
254274
if cmd.has_args:
255275
self.buffer += " "
276+
self.showing_suggestions = False
256277
else:
257278
# Path completion
258279
at_index = self.buffer.rfind("@")
259280
path_info = self.suggestions[self.selected_index]
260281
self.buffer = self.buffer[:at_index + 1] + path_info["path"]
261282

283+
# For directories: drill-down (show contents) unless force_close
284+
if path_info["is_dir"] and not force_close:
285+
# Update suggestions to show directory contents
286+
self._update_suggestions()
287+
self.selected_index = 0
288+
# Keep showing suggestions
289+
else:
290+
# For files or forced close: close suggestions
291+
self.showing_suggestions = False
292+
262293
self.cursor_pos = len(self.buffer)
263-
self.showing_suggestions = False
264294

265295
def get_input(self, prompt_prefix: str = "") -> str:
266296
"""
@@ -279,8 +309,18 @@ def get_input(self, prompt_prefix: str = "") -> str:
279309
if msvcrt.kbhit():
280310
char = msvcrt.getwch()
281311

282-
# Enter - submit
312+
# Enter - submit or confirm selection
283313
if char == '\r':
314+
# If showing path suggestions and a directory is selected, confirm it
315+
if self.showing_suggestions and self.suggestion_type == "path":
316+
if self.suggestions and 0 <= self.selected_index < len(self.suggestions):
317+
path_info = self.suggestions[self.selected_index]
318+
# Complete the selection (force close for directories)
319+
self._complete_selected(force_close=True)
320+
self._clear_line()
321+
self._render_input_line()
322+
continue # Don't submit yet, let user continue typing or submit
323+
284324
console.print() # New line
285325
result = self.buffer.strip()
286326
if result:
@@ -354,8 +394,12 @@ def get_input(self, prompt_prefix: str = "") -> str:
354394
self._clear_line()
355395
self._render_input_line()
356396

357-
# Show suggestions if typing a command
358-
if self.showing_suggestions:
397+
# Show suggestions if typing a command or path
398+
# Always show after typing / in a path context
399+
if self.showing_suggestions or (char == "/" and "@" in self.buffer):
400+
if "@" in self.buffer and char == "/":
401+
# Force update suggestions when typing / in path
402+
self._update_suggestions()
359403
self._render_suggestions()
360404

361405

src/main.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -904,12 +904,10 @@ def main():
904904
else:
905905
# Get input from user using async input handler
906906
user_input = async_input.get_input()
907+
# No need to print - prompt_toolkit already shows it while typing
907908

908909
if not user_input: continue
909910

910-
# Show the user's input so it stays visible in chat history
911-
async_input.print_submitted_input(user_input)
912-
913911
# Handle commands
914912
command_result = handle_command(user_input, agent, queue_manager, command_handler)
915913

0 commit comments

Comments
 (0)