agentcrew-ai 0.8.13__py3-none-any.whl → 0.9.1__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 (55) hide show
  1. AgentCrew/__init__.py +1 -1
  2. AgentCrew/app.py +46 -634
  3. AgentCrew/main_docker.py +1 -30
  4. AgentCrew/modules/a2a/common/client/card_resolver.py +27 -8
  5. AgentCrew/modules/a2a/server.py +5 -0
  6. AgentCrew/modules/a2a/task_manager.py +1 -0
  7. AgentCrew/modules/agents/local_agent.py +2 -2
  8. AgentCrew/modules/chat/message/command_processor.py +33 -8
  9. AgentCrew/modules/chat/message/conversation.py +18 -1
  10. AgentCrew/modules/chat/message/handler.py +5 -1
  11. AgentCrew/modules/code_analysis/service.py +50 -7
  12. AgentCrew/modules/code_analysis/tool.py +9 -8
  13. AgentCrew/modules/console/completers.py +5 -1
  14. AgentCrew/modules/console/console_ui.py +23 -11
  15. AgentCrew/modules/console/conversation_browser/__init__.py +9 -0
  16. AgentCrew/modules/console/conversation_browser/browser.py +84 -0
  17. AgentCrew/modules/console/conversation_browser/browser_input_handler.py +279 -0
  18. AgentCrew/modules/console/{conversation_browser.py → conversation_browser/browser_ui.py} +249 -163
  19. AgentCrew/modules/console/conversation_handler.py +34 -1
  20. AgentCrew/modules/console/display_handlers.py +127 -7
  21. AgentCrew/modules/console/visual_mode/__init__.py +5 -0
  22. AgentCrew/modules/console/visual_mode/viewer.py +41 -0
  23. AgentCrew/modules/console/visual_mode/viewer_input_handler.py +315 -0
  24. AgentCrew/modules/console/visual_mode/viewer_ui.py +608 -0
  25. AgentCrew/modules/gui/components/command_handler.py +137 -29
  26. AgentCrew/modules/gui/components/menu_components.py +8 -7
  27. AgentCrew/modules/gui/themes/README.md +30 -14
  28. AgentCrew/modules/gui/themes/__init__.py +2 -1
  29. AgentCrew/modules/gui/themes/atom_light.yaml +1287 -0
  30. AgentCrew/modules/gui/themes/catppuccin.yaml +1276 -0
  31. AgentCrew/modules/gui/themes/dracula.yaml +1262 -0
  32. AgentCrew/modules/gui/themes/nord.yaml +1267 -0
  33. AgentCrew/modules/gui/themes/saigontech.yaml +1268 -0
  34. AgentCrew/modules/gui/themes/style_provider.py +78 -264
  35. AgentCrew/modules/gui/themes/theme_loader.py +379 -0
  36. AgentCrew/modules/gui/themes/unicorn.yaml +1276 -0
  37. AgentCrew/modules/gui/widgets/configs/global_settings.py +4 -4
  38. AgentCrew/modules/gui/widgets/history_sidebar.py +6 -1
  39. AgentCrew/modules/llm/constants.py +28 -9
  40. AgentCrew/modules/mcpclient/service.py +0 -1
  41. AgentCrew/modules/memory/base_service.py +13 -0
  42. AgentCrew/modules/memory/chroma_service.py +50 -0
  43. AgentCrew/setup.py +470 -0
  44. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/METADATA +1 -1
  45. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/RECORD +49 -40
  46. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/WHEEL +1 -1
  47. AgentCrew/modules/gui/themes/atom_light.py +0 -1365
  48. AgentCrew/modules/gui/themes/catppuccin.py +0 -1404
  49. AgentCrew/modules/gui/themes/dracula.py +0 -1372
  50. AgentCrew/modules/gui/themes/nord.py +0 -1365
  51. AgentCrew/modules/gui/themes/saigontech.py +0 -1359
  52. AgentCrew/modules/gui/themes/unicorn.py +0 -1372
  53. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/entry_points.txt +0 -0
  54. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/licenses/LICENSE +0 -0
  55. {agentcrew_ai-0.8.13.dist-info → agentcrew_ai-0.9.1.dist-info}/top_level.txt +0 -0
@@ -122,12 +122,125 @@ class DisplayHandlers:
122
122
  )
123
123
 
124
124
  def display_debug_info(self, debug_info):
125
- """Display debug information."""
126
- self.console.print(Text("Current messages:", style=RICH_STYLE_YELLOW))
125
+ """Display debug information with formatting and truncation.
126
+
127
+ Args:
128
+ debug_info: Either a dict with 'type' and 'messages' keys (new format)
129
+ or a raw list of messages (legacy format)
130
+ """
131
+ if (
132
+ isinstance(debug_info, dict)
133
+ and "type" in debug_info
134
+ and "messages" in debug_info
135
+ ):
136
+ # New format with type and messages
137
+ msg_type = debug_info["type"]
138
+ messages = debug_info["messages"]
139
+ title = "Agent Messages" if msg_type == "agent" else "Chat Messages"
140
+ else:
141
+ # Legacy format - just raw messages
142
+ title = "Messages"
143
+ messages = debug_info
144
+
145
+ self.console.print(
146
+ Text(f"\n{title} ({len(messages)} messages):", style=RICH_STYLE_YELLOW)
147
+ )
148
+
149
+ formatted = self._format_messages_for_debug(messages)
127
150
  try:
128
- self.console.print(json.dumps(debug_info, indent=2))
151
+ self.console.print(json.dumps(formatted, indent=2))
129
152
  except Exception:
130
- self.console.print(debug_info)
153
+ self.console.print(str(formatted))
154
+
155
+ def _format_messages_for_debug(
156
+ self, messages, max_content_length: int = 200
157
+ ) -> list:
158
+ """Format messages for debug display with truncated content.
159
+
160
+ Args:
161
+ messages: List of message dictionaries
162
+ max_content_length: Maximum length for message content
163
+
164
+ Returns:
165
+ List of formatted message dictionaries
166
+ """
167
+ formatted = []
168
+
169
+ for i, msg in enumerate(messages):
170
+ formatted_msg = {"#": i, "content": ""}
171
+
172
+ # Copy basic fields
173
+ if "role" in msg:
174
+ formatted_msg["role"] = msg["role"]
175
+ if "agent" in msg:
176
+ formatted_msg["agent"] = msg["agent"]
177
+
178
+ # Truncate content
179
+ content = msg.get("content", "")
180
+ formatted_msg["content"] = self._truncate_content(
181
+ content, max_content_length
182
+ )
183
+
184
+ # Include tool_use/tool_result indicators if present
185
+ if isinstance(content, list):
186
+ content_types = []
187
+ for item in content:
188
+ if isinstance(item, dict):
189
+ item_type = item.get("type", "unknown")
190
+ if item_type == "tool_use":
191
+ tool_name = item.get("name", "unknown")
192
+ content_types.append(f"tool_use:{tool_name}")
193
+ elif item_type == "tool_result":
194
+ content_types.append("tool_result")
195
+ elif item_type not in ("text",):
196
+ content_types.append(item_type)
197
+ if content_types:
198
+ formatted_msg["content_types"] = content_types
199
+
200
+ formatted.append(formatted_msg)
201
+
202
+ return formatted
203
+
204
+ def _truncate_content(self, content, max_length: int = 200) -> str:
205
+ """Truncate content to max_length with ellipsis.
206
+
207
+ Args:
208
+ content: Message content (can be string, list, or dict)
209
+ max_length: Maximum length for the output
210
+
211
+ Returns:
212
+ Truncated string representation
213
+ """
214
+ if isinstance(content, str):
215
+ text = content
216
+ elif isinstance(content, list):
217
+ # Extract text from content blocks
218
+ text_parts = []
219
+ for item in content:
220
+ if isinstance(item, dict):
221
+ if item.get("type") == "text":
222
+ text_parts.append(item.get("text", ""))
223
+ elif item.get("type") == "tool_use":
224
+ text_parts.append(f"[tool:{item.get('name', 'unknown')}]")
225
+ elif item.get("type") == "tool_result":
226
+ result = item.get("content", "")
227
+ if isinstance(result, str):
228
+ text_parts.append(f"[result:{result[:50]}...]")
229
+ else:
230
+ text_parts.append("[result:...]")
231
+ elif isinstance(item, str):
232
+ text_parts.append(item)
233
+ text = " | ".join(text_parts)
234
+ else:
235
+ text = str(content)
236
+
237
+ # Clean up whitespace
238
+ text = " ".join(text.split())
239
+
240
+ if len(text) <= max_length:
241
+ return text
242
+
243
+ return text[: max_length - 3] + "..."
131
244
 
132
245
  def display_models(self, models_by_provider: Dict):
133
246
  """Display available models grouped by provider."""
@@ -161,13 +274,15 @@ class DisplayHandlers:
161
274
  self,
162
275
  conversations: List[Dict[str, Any]],
163
276
  get_history_callback=None,
277
+ delete_callback=None,
164
278
  ):
165
279
  """Display available conversations using interactive browser.
166
-
280
+
167
281
  Args:
168
282
  conversations: List of conversation metadata
169
283
  get_history_callback: Optional callback to fetch full conversation history
170
-
284
+ delete_callback: Optional callback to delete conversations by IDs
285
+
171
286
  Returns:
172
287
  Selected conversation ID or None if cancelled
173
288
  """
@@ -177,11 +292,12 @@ class DisplayHandlers:
177
292
  )
178
293
  return None
179
294
 
180
- from .conversation_browser import ConversationBrowser
295
+ from .conversation_browser.browser import ConversationBrowser
181
296
 
182
297
  browser = ConversationBrowser(
183
298
  console=self.console,
184
299
  get_conversation_history=get_history_callback,
300
+ on_delete=delete_callback,
185
301
  )
186
302
  browser.set_conversations(conversations)
187
303
  return browser.show()
@@ -378,6 +494,10 @@ class DisplayHandlers:
378
494
  "Use '/unconsolidate' undo last consolidated.",
379
495
  style=RICH_STYLE_YELLOW,
380
496
  ),
497
+ Text(
498
+ "Use '/visual' to view raw message content with vim-like navigation and copy.",
499
+ style=RICH_STYLE_YELLOW,
500
+ ),
381
501
  Text(
382
502
  "Tool calls require confirmation before execution.",
383
503
  style=RICH_STYLE_BLUE,
@@ -0,0 +1,5 @@
1
+ """Visual mode module for viewing raw message content."""
2
+
3
+ from .viewer import VisualModeViewer
4
+
5
+ __all__ = ["VisualModeViewer"]
@@ -0,0 +1,41 @@
1
+ """Visual mode viewer for raw message content."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, List, Dict, Any, Optional, Callable
6
+
7
+ from rich.console import Console
8
+ from rich.text import Text
9
+
10
+ from .viewer_ui import VisualModeUI
11
+ from .viewer_input_handler import VisualModeInputHandler
12
+ from ..constants import RICH_STYLE_YELLOW
13
+
14
+ if TYPE_CHECKING:
15
+ pass
16
+
17
+
18
+ class VisualModeViewer:
19
+ """Main class for visual mode viewing of raw message content."""
20
+
21
+ def __init__(
22
+ self,
23
+ console: Console,
24
+ on_copy: Optional[Callable[[str], None]] = None,
25
+ ):
26
+ self._console = console
27
+ self._on_copy = on_copy
28
+ self._ui = VisualModeUI(console)
29
+ self._input_handler = VisualModeInputHandler(self._ui, on_copy=on_copy)
30
+
31
+ def set_messages(self, messages: List[Dict[str, Any]]):
32
+ self._ui.set_messages(messages)
33
+
34
+ def show(self):
35
+ if not self._ui._messages:
36
+ self._console.print(
37
+ Text("No messages to display in visual mode.", style=RICH_STYLE_YELLOW)
38
+ )
39
+ return
40
+
41
+ self._input_handler.run()
@@ -0,0 +1,315 @@
1
+ """Input handler for visual mode viewer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Optional, Callable
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 .viewer_ui import VisualModeUI
15
+
16
+
17
+ class VisualModeInputHandler:
18
+ """Handles keyboard input for the visual mode viewer."""
19
+
20
+ def __init__(
21
+ self,
22
+ ui: VisualModeUI,
23
+ on_copy: Optional[Callable[[str], None]] = None,
24
+ ):
25
+ self._ui = ui
26
+ self._running = False
27
+ self._g_pressed = False
28
+ self._on_copy = on_copy
29
+
30
+ def _create_key_bindings(self) -> KeyBindings:
31
+ kb = KeyBindings()
32
+
33
+ @kb.add(Keys.Up)
34
+ @kb.add("k")
35
+ def _(event):
36
+ if self._ui._search_mode:
37
+ return
38
+ self._g_pressed = False
39
+ if self._ui.move_cursor("up"):
40
+ self._ui.render()
41
+
42
+ @kb.add(Keys.Down)
43
+ @kb.add("j")
44
+ def _(event):
45
+ if self._ui._search_mode:
46
+ return
47
+ self._g_pressed = False
48
+ if self._ui.move_cursor("down"):
49
+ self._ui.render()
50
+
51
+ @kb.add(Keys.Left)
52
+ @kb.add("h")
53
+ def _(event):
54
+ if self._ui._search_mode:
55
+ return
56
+ self._g_pressed = False
57
+ if self._ui.move_cursor("left"):
58
+ self._ui.render()
59
+
60
+ @kb.add(Keys.Right)
61
+ @kb.add("l")
62
+ def _(event):
63
+ if self._ui._search_mode:
64
+ return
65
+ self._g_pressed = False
66
+ if self._ui.move_cursor("right"):
67
+ self._ui.render()
68
+
69
+ @kb.add("w")
70
+ def _(event):
71
+ if self._ui._search_mode:
72
+ self._ui.append_search_char("w")
73
+ self._ui.render()
74
+ return
75
+ self._g_pressed = False
76
+ if self._ui.move_cursor("word_forward"):
77
+ self._ui.render()
78
+
79
+ @kb.add("b")
80
+ def _(event):
81
+ if self._ui._search_mode:
82
+ self._ui.append_search_char("b")
83
+ self._ui.render()
84
+ return
85
+ self._g_pressed = False
86
+ if self._ui.move_cursor("word_backward"):
87
+ self._ui.render()
88
+
89
+ @kb.add("0")
90
+ def _(event):
91
+ if self._ui._search_mode:
92
+ self._ui.append_search_char("0")
93
+ self._ui.render()
94
+ return
95
+ self._g_pressed = False
96
+ if self._ui.move_cursor("line_start"):
97
+ self._ui.render()
98
+
99
+ @kb.add("$")
100
+ def _(event):
101
+ if self._ui._search_mode:
102
+ self._ui.append_search_char("$")
103
+ self._ui.render()
104
+ return
105
+ self._g_pressed = False
106
+ if self._ui.move_cursor("line_end"):
107
+ self._ui.render()
108
+
109
+ @kb.add("g")
110
+ def _(event):
111
+ if self._ui._search_mode:
112
+ self._ui.append_search_char("g")
113
+ self._ui.render()
114
+ return
115
+ if self._g_pressed:
116
+ self._g_pressed = False
117
+ if self._ui.move_cursor("top"):
118
+ self._ui.render()
119
+ else:
120
+ self._g_pressed = True
121
+
122
+ @kb.add("G")
123
+ def _(event):
124
+ if self._ui._search_mode:
125
+ self._ui.append_search_char("G")
126
+ self._ui.render()
127
+ return
128
+ self._g_pressed = False
129
+ if self._ui.move_cursor("bottom"):
130
+ self._ui.render()
131
+
132
+ @kb.add(Keys.ControlU)
133
+ def _(event):
134
+ if self._ui._search_mode:
135
+ return
136
+ self._g_pressed = False
137
+ if self._ui.move_cursor("half_up"):
138
+ self._ui.render()
139
+
140
+ @kb.add(Keys.ControlD)
141
+ def _(event):
142
+ if self._ui._search_mode:
143
+ return
144
+ self._g_pressed = False
145
+ if self._ui.move_cursor("half_down"):
146
+ self._ui.render()
147
+
148
+ @kb.add(Keys.PageUp)
149
+ def _(event):
150
+ if self._ui._search_mode:
151
+ return
152
+ self._g_pressed = False
153
+ if self._ui.move_cursor("page_up"):
154
+ self._ui.render()
155
+
156
+ @kb.add(Keys.PageDown)
157
+ def _(event):
158
+ if self._ui._search_mode:
159
+ return
160
+ self._g_pressed = False
161
+ if self._ui.move_cursor("page_down"):
162
+ self._ui.render()
163
+
164
+ @kb.add("v")
165
+ def _(event):
166
+ if self._ui._search_mode:
167
+ self._ui.append_search_char("v")
168
+ self._ui.render()
169
+ return
170
+ self._g_pressed = False
171
+ self._ui.toggle_visual_mode()
172
+ self._ui.render()
173
+
174
+ @kb.add("y")
175
+ def _(event):
176
+ if self._ui._search_mode:
177
+ self._ui.append_search_char("y")
178
+ self._ui.render()
179
+ return
180
+ self._g_pressed = False
181
+ text = self._ui.get_selected_text()
182
+ if text and self._on_copy:
183
+ self._on_copy(text)
184
+ if self._ui._visual_mode:
185
+ self._ui.toggle_visual_mode()
186
+ self._ui.render()
187
+
188
+ @kb.add("Y")
189
+ def _(event):
190
+ if self._ui._search_mode:
191
+ self._ui.append_search_char("Y")
192
+ self._ui.render()
193
+ return
194
+ self._g_pressed = False
195
+ if 0 <= self._ui._cursor_line < len(self._ui._lines):
196
+ line_text = self._ui._lines[self._ui._cursor_line][0]
197
+ if self._on_copy:
198
+ self._on_copy(line_text)
199
+
200
+ @kb.add("/")
201
+ def _(event):
202
+ if self._ui._search_mode:
203
+ self._ui.append_search_char("/")
204
+ self._ui.render()
205
+ return
206
+ self._g_pressed = False
207
+ self._ui.start_search_mode()
208
+ self._ui.render()
209
+
210
+ @kb.add("n")
211
+ def _(event):
212
+ if self._ui._search_mode:
213
+ self._ui.append_search_char("n")
214
+ self._ui.render()
215
+ return
216
+ self._g_pressed = False
217
+ self._ui.next_search_match()
218
+ self._ui.render()
219
+
220
+ @kb.add("N")
221
+ def _(event):
222
+ if self._ui._search_mode:
223
+ self._ui.append_search_char("N")
224
+ self._ui.render()
225
+ return
226
+ self._g_pressed = False
227
+ self._ui.prev_search_match()
228
+ self._ui.render()
229
+
230
+ @kb.add(Keys.Enter)
231
+ def _(event):
232
+ if self._ui._search_mode:
233
+ self._ui.exit_search_mode(clear_results=False)
234
+ self._ui.render()
235
+
236
+ @kb.add(Keys.Backspace)
237
+ def _(event):
238
+ if self._ui._search_mode:
239
+ self._ui.backspace_search()
240
+ self._ui.render()
241
+
242
+ @kb.add(Keys.Escape)
243
+ def _(event):
244
+ self._g_pressed = False
245
+ if self._ui._visual_mode:
246
+ self._ui.toggle_visual_mode()
247
+ self._ui.render()
248
+ elif self._ui._search_mode:
249
+ self._ui.exit_search_mode(clear_results=True)
250
+ self._ui.render()
251
+ else:
252
+ event.app.exit()
253
+
254
+ @kb.add("q")
255
+ def _(event):
256
+ if self._ui._search_mode:
257
+ self._ui.append_search_char("q")
258
+ self._ui.render()
259
+ return
260
+ self._g_pressed = False
261
+ event.app.exit()
262
+
263
+ @kb.add(Keys.ControlC)
264
+ def _(event):
265
+ self._g_pressed = False
266
+ if self._ui._visual_mode:
267
+ text = self._ui.get_selected_text()
268
+ if text and self._on_copy:
269
+ self._on_copy(text)
270
+ self._ui.toggle_visual_mode()
271
+ self._ui.render()
272
+ elif self._ui._search_mode:
273
+ self._ui.exit_search_mode(clear_results=True)
274
+ self._ui.render()
275
+ else:
276
+ event.app.exit()
277
+
278
+ @kb.add(Keys.Any)
279
+ def _(event):
280
+ if self._ui._search_mode:
281
+ char = event.data
282
+ if char and char.isprintable():
283
+ self._ui.append_search_char(char)
284
+ self._ui.render()
285
+ return
286
+ self._g_pressed = False
287
+
288
+ return kb
289
+
290
+ def run(self):
291
+ self._running = True
292
+ self._g_pressed = False
293
+
294
+ self._ui.start_live()
295
+
296
+ kb = self._create_key_bindings()
297
+
298
+ try:
299
+ session = PromptSession(key_bindings=kb)
300
+ session.prompt("")
301
+ except (KeyboardInterrupt, EOFError):
302
+ pass
303
+ except Exception as e:
304
+ logger.error(f"Error in visual mode input handler: {e}")
305
+ finally:
306
+ self._ui.stop_live()
307
+
308
+ self._running = False
309
+
310
+ @property
311
+ def is_running(self) -> bool:
312
+ return self._running
313
+
314
+ def stop(self):
315
+ self._running = False