Skip to content

Commit 9eede45

Browse files
committed
refactor the whole console ui
1 parent f53266d commit 9eede45

File tree

12 files changed

+1343
-1130
lines changed

12 files changed

+1343
-1130
lines changed

AgentCrew/modules/chat/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
from .message_handler import MessageHandler
55

66
__all__ = ["MessageHandler"]
7-

AgentCrew/modules/chat/message/command_processor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from AgentCrew.modules.mcpclient import MCPService
1212
import shlex
1313

14+
1415
@dataclass
1516
class CommandResult:
1617
"""Result of command processing."""

AgentCrew/modules/config/config_management.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,11 @@ def _get_global_config_file_path(self) -> str:
230230
def read_global_config_data(self) -> Dict[str, Any]:
231231
"""Reads data from the global config.json file."""
232232
config_path = self._get_global_config_file_path()
233-
default_config = {"api_keys": {}, "auto_approval_tools": [], "global_settings": {"theme": "dark", "yolo_mode": False}}
233+
default_config = {
234+
"api_keys": {},
235+
"auto_approval_tools": [],
236+
"global_settings": {"theme": "dark", "yolo_mode": False},
237+
}
234238
try:
235239
if not os.path.exists(config_path):
236240
return default_config
@@ -244,7 +248,9 @@ def read_global_config_data(self) -> Dict[str, Any]:
244248
# Ensure api_keys key exists and is a dict
245249
if "api_keys" not in data or not isinstance(data.get("api_keys"), dict):
246250
data["api_keys"] = {}
247-
if "auto_approval_tools" not in data or not isinstance(data.get("auto_approval_tools"), list):
251+
if "auto_approval_tools" not in data or not isinstance(
252+
data.get("auto_approval_tools"), list
253+
):
248254
data["auto_approval_tools"] = []
249255
return data
250256
except json.JSONDecodeError:
@@ -596,4 +602,6 @@ def write_auto_approval_tools(self, tool_name: str, add: bool = True) -> None:
596602
self.write_global_config_data(global_config)
597603
except Exception as e:
598604
action = "add" if add else "remove"
599-
logger.warning(f"Warning: Failed to {action} tool {tool_name} from auto-approval list: {e}")
605+
logger.warning(
606+
f"Warning: Failed to {action} tool {tool_name} from auto-approval list: {e}"
607+
)
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
from .console_ui import ConsoleUI
2+
from .display_handlers import DisplayHandlers
3+
from .tool_display import ToolDisplayHandlers
4+
from .input_handler import InputHandler
5+
from .ui_effects import UIEffects
6+
from .confirmation_handler import ConfirmationHandler
7+
from .conversation_handler import ConversationHandler
28

39
__all__ = [
410
"ConsoleUI",
5-
]
6-
11+
"DisplayHandlers",
12+
"ToolDisplayHandlers",
13+
"InputHandler",
14+
"UIEffects",
15+
"ConfirmationHandler",
16+
"ConversationHandler",
17+
]

AgentCrew/modules/console/completers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,4 @@ def get_path_completions(self, path):
259259
entries = os.listdir(directory)
260260
return entries
261261
except (FileNotFoundError, NotADirectoryError):
262-
return []
262+
return []
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"""
2+
Confirmation handlers for console UI.
3+
Handles tool confirmation requests and MCP prompt confirmations.
4+
"""
5+
6+
from rich.console import Console
7+
from rich.text import Text
8+
9+
from .constants import (
10+
RICH_STYLE_YELLOW,
11+
RICH_STYLE_GREEN,
12+
RICH_STYLE_RED,
13+
RICH_STYLE_GRAY,
14+
)
15+
16+
17+
class ConfirmationHandler:
18+
"""Handles confirmation dialogs for tools and MCP prompts."""
19+
20+
def __init__(self, console: Console, input_handler):
21+
"""Initialize the confirmation handler."""
22+
self.console = console
23+
self.input_handler = input_handler
24+
25+
def display_tool_confirmation_request(self, tool_info, message_handler):
26+
"""Display tool confirmation request and get user response."""
27+
tool_use = tool_info.copy()
28+
confirmation_id = tool_use.pop("confirmation_id")
29+
30+
self.console.print(
31+
Text(
32+
"\n🔧 Tool execution requires your permission:", style=RICH_STYLE_YELLOW
33+
)
34+
)
35+
tool_name = Text("Tool: ", style=RICH_STYLE_YELLOW)
36+
tool_name.append(tool_use["name"])
37+
self.console.print(tool_name)
38+
39+
# Display tool parameters
40+
if isinstance(tool_use["input"], dict):
41+
self.console.print(Text("Parameters:", style=RICH_STYLE_YELLOW))
42+
for key, value in tool_use["input"].items():
43+
param_text = Text(f" - {key}: ", style=RICH_STYLE_YELLOW)
44+
param_text.append(str(value))
45+
self.console.print(param_text)
46+
else:
47+
input_text = Text("Input: ", style=RICH_STYLE_YELLOW)
48+
input_text.append(str(tool_use["input"]))
49+
self.console.print(input_text)
50+
51+
# Get user response
52+
self.input_handler._stop_input_thread()
53+
while True:
54+
# Use Rich to print the prompt but still need to use input() for user interaction
55+
self.console.print(
56+
Text(
57+
"\nAllow this tool to run? [y]es/[n]o/[a]ll in this session/[f]orever (this and future sessions): ",
58+
style=RICH_STYLE_YELLOW,
59+
),
60+
end="",
61+
)
62+
try:
63+
response = input().lower()
64+
except KeyboardInterrupt:
65+
response = "no"
66+
67+
if response in ["y", "yes"]:
68+
message_handler.resolve_tool_confirmation(
69+
confirmation_id, {"action": "approve"}
70+
)
71+
break
72+
elif response in ["n", "no"]:
73+
message_handler.resolve_tool_confirmation(
74+
confirmation_id, {"action": "deny"}
75+
)
76+
break
77+
elif response in ["a", "all"]:
78+
message_handler.resolve_tool_confirmation(
79+
confirmation_id, {"action": "approve_all"}
80+
)
81+
approved_text = Text(
82+
f"✓ Approved all future calls to '{tool_use['name']}' for this session.",
83+
style=RICH_STYLE_YELLOW,
84+
)
85+
self.console.print(approved_text)
86+
break
87+
elif response in ["f", "forever"]:
88+
from AgentCrew.modules.config import ConfigManagement
89+
90+
config_manager = ConfigManagement()
91+
config_manager.write_auto_approval_tools(tool_use["name"], add=True)
92+
93+
message_handler.resolve_tool_confirmation(
94+
confirmation_id, {"action": "approve_all"}
95+
)
96+
saved_text = Text(
97+
f"✓ Tool '{tool_use['name']}' will be auto-approved forever.",
98+
style=RICH_STYLE_YELLOW,
99+
)
100+
self.console.print(saved_text)
101+
break
102+
else:
103+
self.console.print(
104+
Text("Please enter 'y', 'n', 'a', or 'f'.", style=RICH_STYLE_YELLOW)
105+
)
106+
self.input_handler._start_input_thread()
107+
108+
def display_mcp_prompt_confirmation(self, prompt_data, input_queue):
109+
"""Display MCP prompt confirmation request and get user response."""
110+
self.console.print(
111+
Text("\n🤖 MCP Tool wants to execute a prompt:", style=RICH_STYLE_YELLOW)
112+
)
113+
114+
# Display the prompt content
115+
if isinstance(prompt_data, dict):
116+
if "name" in prompt_data:
117+
prompt_name = Text("Prompt: ", style=RICH_STYLE_YELLOW)
118+
prompt_name.append(prompt_data["name"])
119+
self.console.print(prompt_name)
120+
121+
if "content" in prompt_data:
122+
self.console.print(Text("Content:", style=RICH_STYLE_YELLOW))
123+
# Display content with proper formatting
124+
content = str(prompt_data["content"])
125+
if len(content) > 1000:
126+
self.console.print(f" {content[:1000]}...")
127+
self.console.print(
128+
Text(
129+
f" (Content truncated, total length: {len(content)} characters)",
130+
style=RICH_STYLE_GRAY,
131+
)
132+
)
133+
else:
134+
self.console.print(f" {content}")
135+
136+
# Get user response
137+
self.input_handler._stop_input_thread()
138+
while True:
139+
self.console.print(
140+
Text(
141+
"\nAllow this prompt to be executed? [y]es/[n]o: ",
142+
style=RICH_STYLE_YELLOW,
143+
),
144+
end="",
145+
)
146+
response = input().lower()
147+
148+
if response in ["y", "yes"]:
149+
# User approved, put the prompt data in the input queue
150+
self.console.print(
151+
Text(
152+
"✓ MCP prompt approved and queued for execution.",
153+
style=RICH_STYLE_GREEN,
154+
)
155+
)
156+
input_queue.put(prompt_data["content"])
157+
break
158+
elif response in ["n", "no"]:
159+
# User denied, don't queue the prompt
160+
self.console.print(
161+
Text("❌ MCP prompt execution denied.", style=RICH_STYLE_RED)
162+
)
163+
break
164+
else:
165+
self.console.print(
166+
Text(
167+
"Please enter 'y' for yes or 'n' for no.",
168+
style=RICH_STYLE_YELLOW,
169+
)
170+
)
171+
172+
self.input_handler._start_input_thread()

0 commit comments

Comments
 (0)