agentcrew-ai 0.8.13__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. AgentCrew/__init__.py +1 -1
  2. AgentCrew/app.py +34 -633
  3. AgentCrew/main_docker.py +1 -30
  4. AgentCrew/modules/agents/local_agent.py +2 -2
  5. AgentCrew/modules/chat/message/command_processor.py +33 -8
  6. AgentCrew/modules/chat/message/handler.py +5 -1
  7. AgentCrew/modules/console/completers.py +1 -1
  8. AgentCrew/modules/console/console_ui.py +6 -11
  9. AgentCrew/modules/console/conversation_browser/__init__.py +9 -0
  10. AgentCrew/modules/console/conversation_browser/browser.py +84 -0
  11. AgentCrew/modules/console/conversation_browser/browser_input_handler.py +279 -0
  12. AgentCrew/modules/console/{conversation_browser.py → conversation_browser/browser_ui.py} +249 -163
  13. AgentCrew/modules/console/conversation_handler.py +34 -1
  14. AgentCrew/modules/console/display_handlers.py +123 -7
  15. AgentCrew/modules/gui/components/command_handler.py +137 -29
  16. AgentCrew/modules/gui/themes/README.md +30 -14
  17. AgentCrew/modules/gui/themes/__init__.py +2 -1
  18. AgentCrew/modules/gui/themes/atom_light.yaml +1287 -0
  19. AgentCrew/modules/gui/themes/catppuccin.yaml +1276 -0
  20. AgentCrew/modules/gui/themes/dracula.yaml +1262 -0
  21. AgentCrew/modules/gui/themes/nord.yaml +1267 -0
  22. AgentCrew/modules/gui/themes/saigontech.yaml +1268 -0
  23. AgentCrew/modules/gui/themes/style_provider.py +76 -264
  24. AgentCrew/modules/gui/themes/theme_loader.py +379 -0
  25. AgentCrew/modules/gui/themes/unicorn.yaml +1276 -0
  26. AgentCrew/modules/gui/widgets/configs/global_settings.py +3 -4
  27. AgentCrew/modules/llm/constants.py +13 -4
  28. AgentCrew/setup.py +470 -0
  29. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.0.dist-info}/METADATA +1 -1
  30. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.0.dist-info}/RECORD +34 -29
  31. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.0.dist-info}/WHEEL +1 -1
  32. AgentCrew/modules/gui/themes/atom_light.py +0 -1365
  33. AgentCrew/modules/gui/themes/catppuccin.py +0 -1404
  34. AgentCrew/modules/gui/themes/dracula.py +0 -1372
  35. AgentCrew/modules/gui/themes/nord.py +0 -1365
  36. AgentCrew/modules/gui/themes/saigontech.py +0 -1359
  37. AgentCrew/modules/gui/themes/unicorn.py +0 -1372
  38. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.0.dist-info}/entry_points.txt +0 -0
  39. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.0.dist-info}/licenses/LICENSE +0 -0
  40. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.0.dist-info}/top_level.txt +0 -0
AgentCrew/main_docker.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import click
2
2
  import os
3
3
  import sys
4
+ from AgentCrew.app import common_options
4
5
 
5
6
 
6
7
  def _custom_unraisable_hook(unraisable):
@@ -48,36 +49,6 @@ def cli():
48
49
  )
49
50
 
50
51
 
51
- def common_options(func):
52
- import functools
53
-
54
- @click.option(
55
- "--provider",
56
- type=click.Choice(PROVIDER_LIST),
57
- default=None,
58
- help="LLM provider to use (claude, groq, openai, google, github_copilot, or deepinfra)",
59
- )
60
- @click.option(
61
- "--agent-config", default=None, help="Path/URL to the agent configuration file."
62
- )
63
- @click.option(
64
- "--mcp-config", default=None, help="Path to the mcp servers configuration file."
65
- )
66
- @click.option(
67
- "--memory-llm",
68
- type=click.Choice(
69
- ["claude", "groq", "openai", "google", "deepinfra", "github_copilot"]
70
- ),
71
- default=None,
72
- help="LLM Model use for analyzing and processing memory",
73
- )
74
- @functools.wraps(func)
75
- def wrapper(*args, **kwargs):
76
- return func(*args, **kwargs)
77
-
78
- return wrapper
79
-
80
-
81
52
  def cli_prod():
82
53
  if sys.argv[1] == "--version":
83
54
  click.echo(f"AgentCrew version: {get_current_version()}")
@@ -672,7 +672,7 @@ Whenever condition on `when` clause in a **Behavior** matches, tailor your respo
672
672
  adaptive_messages["content"].append(
673
673
  {
674
674
  "type": "text",
675
- "text": f"Cwd structure:\n{dir_structure}",
675
+ "text": f"cwd `{os.getcwd()}` structure:\n{dir_structure}",
676
676
  }
677
677
  )
678
678
 
@@ -763,7 +763,7 @@ Whenever condition on `when` clause in a **Behavior** matches, tailor your respo
763
763
  continue
764
764
 
765
765
  if is_shrinkable and i < shrink_threshold:
766
- msg["content"] = "[REDACTED]"
766
+ msg["content"] = "[PRUNED]"
767
767
  continue
768
768
 
769
769
  # Check if content starts with [UNIQUE]
@@ -43,14 +43,8 @@ class CommandProcessor:
43
43
  return CommandResult(handled=True, clear_flag=True)
44
44
  elif user_input.lower().startswith("/copy"):
45
45
  return await self._handle_copy_command(user_input)
46
- elif user_input.lower() == "/debug":
47
- self.message_handler._notify(
48
- "debug_requested", self.message_handler.agent.clean_history
49
- )
50
- self.message_handler._notify(
51
- "debug_requested", self.message_handler.streamline_messages
52
- )
53
- return CommandResult(handled=True, clear_flag=True)
46
+ elif user_input.lower().startswith("/debug"):
47
+ return self._handle_debug_command(user_input)
54
48
  elif user_input.lower().startswith("/think "):
55
49
  try:
56
50
  budget = user_input[7:].strip()
@@ -759,6 +753,37 @@ class CommandProcessor:
759
753
  self.message_handler._notify("voice_recording_completed", None)
760
754
  return CommandResult(handled=True, clear_flag=True)
761
755
 
756
+ def _handle_debug_command(self, user_input: str) -> CommandResult:
757
+ """Handle /debug command with optional filtering.
758
+
759
+ Usage:
760
+ /debug - Show both agent and chat messages
761
+ /debug agent - Show only agent messages
762
+ /debug chat - Show only chat/streamline messages
763
+ """
764
+ parts = user_input.lower().split()
765
+ filter_type = parts[1] if len(parts) > 1 else None
766
+
767
+ if filter_type and filter_type not in ("agent", "chat"):
768
+ self.message_handler._notify(
769
+ "error", f"Invalid filter '{filter_type}'. Use 'agent' or 'chat'."
770
+ )
771
+ return CommandResult(handled=True, clear_flag=True)
772
+
773
+ if filter_type is None or filter_type == "agent":
774
+ self.message_handler._notify(
775
+ "debug_requested",
776
+ {"type": "agent", "messages": self.message_handler.agent.clean_history},
777
+ )
778
+
779
+ if filter_type is None or filter_type == "chat":
780
+ self.message_handler._notify(
781
+ "debug_requested",
782
+ {"type": "chat", "messages": self.message_handler.streamline_messages},
783
+ )
784
+
785
+ return CommandResult(handled=True, clear_flag=True)
786
+
762
787
  def _handle_toggle_transfer_command(self, user_input: str) -> CommandResult:
763
788
  """Handle /toggle_transfer command to toggle the enforce_transfer property of agent_manager."""
764
789
  try:
@@ -470,9 +470,13 @@ class MessageHandler(Observable):
470
470
  if isinstance(e, BadRequestError):
471
471
  if e.code == "model_max_prompt_tokens_exceeded":
472
472
  from AgentCrew.modules.agents import LocalAgent
473
+ from AgentCrew.modules.llm.model_registry import ModelRegistry
473
474
 
474
475
  if isinstance(self.agent, LocalAgent):
475
- self.agent.input_tokens_usage = 128_000
476
+ max_token = ModelRegistry.get_model_limit(
477
+ self.agent.get_model()
478
+ )
479
+ self.agent.input_tokens_usage = max_token
476
480
  return await self.get_assistant_response()
477
481
  if self.current_user_input:
478
482
  self.conversation_manager.store_conversation_turn(
@@ -174,7 +174,7 @@ class ChatCompleter(Completer):
174
174
  ),
175
175
  (
176
176
  "/debug",
177
- "Show debug information (agent history and streamline messages)",
177
+ "Show debug info (usage: /debug [agent|chat])",
178
178
  ),
179
179
  ("/think", "Set thinking budget (usage: /think <budget>)"),
180
180
  (
@@ -71,14 +71,6 @@ class ConsoleUI(Observer):
71
71
  self.conversation_handler = ConversationHandler(self)
72
72
  self.command_handlers = CommandHandlers(self)
73
73
 
74
- def _get_conversation_history(self, conversation_id: str):
75
- """Get conversation history for preview in browser."""
76
- if self.message_handler.persistent_service:
77
- return self.message_handler.persistent_service.get_conversation_history(
78
- conversation_id
79
- )
80
- return None
81
-
82
74
  def listen(self, event: str, data: Any = None):
83
75
  """
84
76
  Update method required by the Observer interface. Handles events from the MessageHandler.
@@ -223,8 +215,10 @@ class ConsoleUI(Observer):
223
215
  )
224
216
  elif event == "conversations_listed":
225
217
  self.display_handlers.display_conversations(
226
- data
227
- ) # data is list of conversation metadata
218
+ data,
219
+ get_history_callback=self.conversation_handler.get_conversation_history,
220
+ delete_callback=self.conversation_handler.delete_conversations,
221
+ )
228
222
  self.conversation_handler.update_cached_conversations(data)
229
223
  elif event == "conversation_loaded":
230
224
  loaded_text = Text("Loaded conversation: ", style=RICH_STYLE_YELLOW)
@@ -466,7 +460,8 @@ class ConsoleUI(Observer):
466
460
  try:
467
461
  selected_id = self.display_handlers.display_conversations(
468
462
  conversations,
469
- get_history_callback=self._get_conversation_history,
463
+ get_history_callback=self.conversation_handler.get_conversation_history,
464
+ delete_callback=self.conversation_handler.delete_conversations,
470
465
  )
471
466
  if selected_id:
472
467
  self.conversation_handler.handle_load_conversation(
@@ -0,0 +1,9 @@
1
+ from .browser import ConversationBrowser
2
+ from .browser_ui import ConversationBrowserUI
3
+ from .browser_input_handler import ConversationBrowserInputHandler
4
+
5
+ __all__ = [
6
+ "ConversationBrowser",
7
+ "ConversationBrowserUI",
8
+ "ConversationBrowserInputHandler",
9
+ ]
@@ -0,0 +1,84 @@
1
+ """Conversation browser with split-panel interface.
2
+
3
+ Provides Rich-based UI for listing and loading conversations with preview.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import List, Dict, Any, Optional, Callable
9
+
10
+ from rich.console import Console
11
+ from rich.text import Text
12
+
13
+ from ..constants import RICH_STYLE_YELLOW
14
+ from .browser_ui import ConversationBrowserUI
15
+ from .browser_input_handler import ConversationBrowserInputHandler
16
+
17
+
18
+ class ConversationBrowser:
19
+ """Interactive conversation browser with split-panel layout.
20
+
21
+ This class orchestrates the UI rendering and input handling components
22
+ to provide a complete interactive conversation browsing experience.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ console: Console,
28
+ get_conversation_history: Optional[
29
+ Callable[[str], List[Dict[str, Any]]]
30
+ ] = None,
31
+ on_delete: Optional[Callable[[List[str]], bool]] = None,
32
+ ):
33
+ """Initialize the conversation browser.
34
+
35
+ Args:
36
+ console: Rich console for rendering
37
+ get_conversation_history: Optional callback to fetch full conversation history
38
+ on_delete: Optional callback to delete conversations by IDs. Returns True if successful.
39
+ """
40
+ self._console = console
41
+ self._ui = ConversationBrowserUI(
42
+ console=console,
43
+ get_conversation_history=get_conversation_history,
44
+ )
45
+ self._input_handler = ConversationBrowserInputHandler(
46
+ ui=self._ui,
47
+ on_delete=on_delete,
48
+ )
49
+
50
+ def set_conversations(self, conversations: List[Dict[str, Any]]):
51
+ """Set the conversations list to browse."""
52
+ self._ui.set_conversations(conversations)
53
+
54
+ def get_selected_conversation_id(self) -> Optional[str]:
55
+ """Get the ID of the currently selected conversation."""
56
+ return self._ui.get_selected_conversation_id()
57
+
58
+ def get_selected_conversation_index(self) -> int:
59
+ """Get the 1-based index of the currently selected conversation."""
60
+ return self._ui.get_selected_conversation_index()
61
+
62
+ @property
63
+ def ui(self) -> ConversationBrowserUI:
64
+ """Access the UI component directly."""
65
+ return self._ui
66
+
67
+ @property
68
+ def input_handler(self) -> ConversationBrowserInputHandler:
69
+ """Access the input handler component directly."""
70
+ return self._input_handler
71
+
72
+ def show(self) -> Optional[str]:
73
+ """Show the interactive conversation browser.
74
+
75
+ Returns:
76
+ The ID of the selected conversation, or None if cancelled.
77
+ """
78
+ if not self._ui.conversations:
79
+ self._console.print(
80
+ Text("No conversations available.", style=RICH_STYLE_YELLOW)
81
+ )
82
+ return None
83
+
84
+ return self._input_handler.run()
@@ -0,0 +1,279 @@
1
+ """Conversation browser input handling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Optional, Callable, List
6
+
7
+ from prompt_toolkit import PromptSession
8
+ from prompt_toolkit.key_binding import KeyBindings
9
+ from prompt_toolkit.keys import Keys
10
+
11
+ from loguru import logger
12
+
13
+ if TYPE_CHECKING:
14
+ from .browser_ui import ConversationBrowserUI
15
+
16
+
17
+ class ConversationBrowserInputHandler:
18
+ """Handles keyboard input for the conversation browser."""
19
+
20
+ def __init__(
21
+ self,
22
+ ui: ConversationBrowserUI,
23
+ on_select: Optional[Callable[[str], None]] = None,
24
+ on_cancel: Optional[Callable[[], None]] = None,
25
+ on_delete: Optional[Callable[[List[str]], bool]] = None,
26
+ ):
27
+ self._ui = ui
28
+ self._running = False
29
+ self._g_pressed = False
30
+ self._d_pressed = False
31
+ self._selected_id: Optional[str] = None
32
+ self._on_select = on_select
33
+ self._on_cancel = on_cancel
34
+ self._on_delete = on_delete
35
+
36
+ def _create_key_bindings(self) -> KeyBindings:
37
+ """Create and configure key bindings for the browser."""
38
+ kb = KeyBindings()
39
+
40
+ @kb.add(Keys.Up)
41
+ @kb.add("k")
42
+ def _(event):
43
+ if self._ui.search_mode:
44
+ return
45
+ self._g_pressed = False
46
+ self._d_pressed = False
47
+ if self._ui.handle_navigation("up"):
48
+ self._ui.render()
49
+
50
+ @kb.add(Keys.Down)
51
+ @kb.add("j")
52
+ def _(event):
53
+ if self._ui.search_mode:
54
+ return
55
+ self._g_pressed = False
56
+ self._d_pressed = False
57
+ if self._ui.handle_navigation("down"):
58
+ self._ui.render()
59
+
60
+ @kb.add(Keys.ControlP)
61
+ def _(event):
62
+ if self._ui.search_mode:
63
+ return
64
+ self._g_pressed = False
65
+ self._d_pressed = False
66
+ if self._ui.handle_navigation("up"):
67
+ self._ui.render()
68
+
69
+ @kb.add(Keys.ControlN)
70
+ def _(event):
71
+ if self._ui.search_mode:
72
+ return
73
+ self._g_pressed = False
74
+ self._d_pressed = False
75
+ if self._ui.handle_navigation("down"):
76
+ self._ui.render()
77
+
78
+ @kb.add("g")
79
+ def _(event):
80
+ if self._ui.search_mode:
81
+ self._ui.append_search_char("g")
82
+ self._ui.render()
83
+ return
84
+ self._d_pressed = False
85
+ if self._g_pressed:
86
+ self._g_pressed = False
87
+ if self._ui.handle_navigation("top"):
88
+ self._ui.render()
89
+ else:
90
+ self._g_pressed = True
91
+
92
+ @kb.add("G")
93
+ def _(event):
94
+ if self._ui.search_mode:
95
+ self._ui.append_search_char("G")
96
+ self._ui.render()
97
+ return
98
+ self._g_pressed = False
99
+ self._d_pressed = False
100
+ if self._ui.handle_navigation("bottom"):
101
+ self._ui.render()
102
+
103
+ @kb.add(Keys.ControlU)
104
+ @kb.add(Keys.PageUp)
105
+ def _(event):
106
+ if self._ui.search_mode:
107
+ return
108
+ self._g_pressed = False
109
+ self._d_pressed = False
110
+ if self._ui.handle_navigation("page_up"):
111
+ self._ui.render()
112
+
113
+ @kb.add(Keys.ControlD)
114
+ @kb.add(Keys.PageDown)
115
+ def _(event):
116
+ if self._ui.search_mode:
117
+ return
118
+ self._g_pressed = False
119
+ self._d_pressed = False
120
+ if self._ui.handle_navigation("page_down"):
121
+ self._ui.render()
122
+
123
+ @kb.add("v")
124
+ def _(event):
125
+ if self._ui.search_mode:
126
+ self._ui.append_search_char("v")
127
+ self._ui.render()
128
+ return
129
+ self._g_pressed = False
130
+ self._d_pressed = False
131
+ if self._ui.toggle_selection():
132
+ self._ui.render()
133
+
134
+ @kb.add("d")
135
+ def _(event):
136
+ if self._ui.search_mode:
137
+ self._ui.append_search_char("d")
138
+ self._ui.render()
139
+ return
140
+ self._g_pressed = False
141
+ if self._d_pressed:
142
+ self._d_pressed = False
143
+ self._handle_delete()
144
+ else:
145
+ self._d_pressed = True
146
+
147
+ @kb.add("/")
148
+ def _(event):
149
+ if self._ui.search_mode:
150
+ self._ui.append_search_char("/")
151
+ self._ui.render()
152
+ return
153
+ self._g_pressed = False
154
+ self._d_pressed = False
155
+ self._ui.start_search_mode()
156
+ self._ui.render()
157
+
158
+ @kb.add(Keys.Backspace)
159
+ def _(event):
160
+ if self._ui.search_mode:
161
+ self._ui.backspace_search()
162
+ self._ui.render()
163
+
164
+ @kb.add(Keys.Enter)
165
+ @kb.add("l")
166
+ def _(event):
167
+ self._g_pressed = False
168
+ self._d_pressed = False
169
+ if self._ui.search_mode:
170
+ self._ui.exit_search_mode(clear_filter=False)
171
+ self._ui.render()
172
+ return
173
+ self._selected_id = self._ui.get_selected_conversation_id()
174
+ event.app.exit()
175
+
176
+ @kb.add(Keys.Escape)
177
+ def _(event):
178
+ self._g_pressed = False
179
+ self._d_pressed = False
180
+ if self._ui.search_mode:
181
+ self._ui.exit_search_mode(clear_filter=True)
182
+ self._ui.render()
183
+ return
184
+ event.app.exit()
185
+
186
+ @kb.add("q")
187
+ def _(event):
188
+ if self._ui.search_mode:
189
+ self._ui.append_search_char("q")
190
+ self._ui.render()
191
+ return
192
+ self._g_pressed = False
193
+ self._d_pressed = False
194
+ event.app.exit()
195
+
196
+ @kb.add(Keys.ControlC)
197
+ def _(event):
198
+ self._g_pressed = False
199
+ self._d_pressed = False
200
+ if self._ui.search_mode:
201
+ self._ui.exit_search_mode(clear_filter=True)
202
+ self._ui.render()
203
+ return
204
+ event.app.exit()
205
+
206
+ @kb.add(Keys.Any)
207
+ def _(event):
208
+ if self._ui.search_mode:
209
+ char = event.data
210
+ if char and char.isprintable():
211
+ self._ui.append_search_char(char)
212
+ self._ui.render()
213
+ return
214
+ self._g_pressed = False
215
+ self._d_pressed = False
216
+
217
+ return kb
218
+
219
+ def _handle_delete(self):
220
+ """Handle delete action for selected or current conversation."""
221
+ if not self._ui.conversations:
222
+ return
223
+
224
+ if self._ui.selected_items:
225
+ indices_to_delete = list(self._ui.selected_items)
226
+ ids_to_delete = self._ui.get_selected_conversation_ids()
227
+ else:
228
+ indices_to_delete = [self._ui.selected_index]
229
+ current_id = self._ui.get_selected_conversation_id()
230
+ ids_to_delete = [current_id] if current_id else []
231
+
232
+ if not ids_to_delete:
233
+ return
234
+
235
+ if self._on_delete:
236
+ success = self._on_delete(ids_to_delete)
237
+ if success:
238
+ self._ui.remove_conversations(indices_to_delete)
239
+ self._ui.render()
240
+ else:
241
+ self._ui.remove_conversations(indices_to_delete)
242
+ self._ui.render()
243
+
244
+ def run(self) -> Optional[str]:
245
+ """Run the input handler loop.
246
+
247
+ Returns:
248
+ The ID of the selected conversation, or None if cancelled.
249
+ """
250
+ self._running = True
251
+ self._g_pressed = False
252
+ self._d_pressed = False
253
+ self._selected_id = None
254
+
255
+ self._ui.start_live()
256
+
257
+ kb = self._create_key_bindings()
258
+
259
+ try:
260
+ session = PromptSession(key_bindings=kb)
261
+ session.prompt("")
262
+ except (KeyboardInterrupt, EOFError):
263
+ pass
264
+ except Exception as e:
265
+ logger.error(f"Error in conversation browser input handler: {e}")
266
+ finally:
267
+ self._ui.stop_live()
268
+
269
+ self._running = False
270
+ return self._selected_id
271
+
272
+ @property
273
+ def is_running(self) -> bool:
274
+ """Check if the input handler is currently running."""
275
+ return self._running
276
+
277
+ def stop(self):
278
+ """Stop the input handler."""
279
+ self._running = False