agentcrew-ai 0.8.12__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 (66) hide show
  1. AgentCrew/__init__.py +1 -1
  2. AgentCrew/app.py +34 -633
  3. AgentCrew/main.py +55 -3
  4. AgentCrew/main_docker.py +1 -30
  5. AgentCrew/modules/agents/local_agent.py +26 -1
  6. AgentCrew/modules/chat/message/command_processor.py +33 -8
  7. AgentCrew/modules/chat/message/handler.py +5 -1
  8. AgentCrew/modules/code_analysis/__init__.py +8 -0
  9. AgentCrew/modules/code_analysis/parsers/__init__.py +67 -0
  10. AgentCrew/modules/code_analysis/parsers/base.py +93 -0
  11. AgentCrew/modules/code_analysis/parsers/cpp_parser.py +127 -0
  12. AgentCrew/modules/code_analysis/parsers/csharp_parser.py +162 -0
  13. AgentCrew/modules/code_analysis/parsers/generic_parser.py +63 -0
  14. AgentCrew/modules/code_analysis/parsers/go_parser.py +154 -0
  15. AgentCrew/modules/code_analysis/parsers/java_parser.py +103 -0
  16. AgentCrew/modules/code_analysis/parsers/javascript_parser.py +268 -0
  17. AgentCrew/modules/code_analysis/parsers/kotlin_parser.py +84 -0
  18. AgentCrew/modules/code_analysis/parsers/php_parser.py +107 -0
  19. AgentCrew/modules/code_analysis/parsers/python_parser.py +60 -0
  20. AgentCrew/modules/code_analysis/parsers/ruby_parser.py +46 -0
  21. AgentCrew/modules/code_analysis/parsers/rust_parser.py +72 -0
  22. AgentCrew/modules/code_analysis/service.py +231 -897
  23. AgentCrew/modules/command_execution/constants.py +2 -2
  24. AgentCrew/modules/console/completers.py +1 -1
  25. AgentCrew/modules/console/confirmation_handler.py +4 -4
  26. AgentCrew/modules/console/console_ui.py +17 -3
  27. AgentCrew/modules/console/conversation_browser/__init__.py +9 -0
  28. AgentCrew/modules/console/conversation_browser/browser.py +84 -0
  29. AgentCrew/modules/console/conversation_browser/browser_input_handler.py +279 -0
  30. AgentCrew/modules/console/conversation_browser/browser_ui.py +643 -0
  31. AgentCrew/modules/console/conversation_handler.py +34 -1
  32. AgentCrew/modules/console/diff_display.py +22 -51
  33. AgentCrew/modules/console/display_handlers.py +142 -26
  34. AgentCrew/modules/console/tool_display.py +4 -6
  35. AgentCrew/modules/file_editing/service.py +8 -8
  36. AgentCrew/modules/file_editing/tool.py +65 -67
  37. AgentCrew/modules/gui/components/command_handler.py +137 -29
  38. AgentCrew/modules/gui/components/tool_handlers.py +0 -2
  39. AgentCrew/modules/gui/themes/README.md +30 -14
  40. AgentCrew/modules/gui/themes/__init__.py +2 -1
  41. AgentCrew/modules/gui/themes/atom_light.yaml +1287 -0
  42. AgentCrew/modules/gui/themes/catppuccin.yaml +1276 -0
  43. AgentCrew/modules/gui/themes/dracula.yaml +1262 -0
  44. AgentCrew/modules/gui/themes/nord.yaml +1267 -0
  45. AgentCrew/modules/gui/themes/saigontech.yaml +1268 -0
  46. AgentCrew/modules/gui/themes/style_provider.py +76 -264
  47. AgentCrew/modules/gui/themes/theme_loader.py +379 -0
  48. AgentCrew/modules/gui/themes/unicorn.yaml +1276 -0
  49. AgentCrew/modules/gui/widgets/configs/global_settings.py +3 -4
  50. AgentCrew/modules/gui/widgets/diff_widget.py +30 -61
  51. AgentCrew/modules/llm/constants.py +18 -9
  52. AgentCrew/modules/memory/context_persistent.py +1 -0
  53. AgentCrew/modules/memory/tool.py +1 -1
  54. AgentCrew/setup.py +470 -0
  55. {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/METADATA +1 -1
  56. {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/RECORD +60 -41
  57. {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/WHEEL +1 -1
  58. AgentCrew/modules/gui/themes/atom_light.py +0 -1365
  59. AgentCrew/modules/gui/themes/catppuccin.py +0 -1404
  60. AgentCrew/modules/gui/themes/dracula.py +0 -1372
  61. AgentCrew/modules/gui/themes/nord.py +0 -1365
  62. AgentCrew/modules/gui/themes/saigontech.py +0 -1359
  63. AgentCrew/modules/gui/themes/unicorn.py +0 -1372
  64. {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/entry_points.txt +0 -0
  65. {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/licenses/LICENSE +0 -0
  66. {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ Provides split view diff display with syntax highlighting.
4
4
  """
5
5
 
6
6
  import difflib
7
+ from typing import List, Dict
7
8
  from rich.text import Text
8
9
  from rich.table import Table
9
10
  from rich.box import SIMPLE_HEAD
@@ -16,67 +17,37 @@ class DiffDisplay:
16
17
  """Helper class for creating split diff views."""
17
18
 
18
19
  @staticmethod
19
- def has_search_replace_blocks(text: str) -> bool:
20
- """Check if text contains search/replace blocks."""
21
- return (
22
- "<<<<<<< SEARCH" in text and "=======" in text and ">>>>>>> REPLACE" in text
20
+ def has_search_replace_blocks(blocks: List[Dict]) -> bool:
21
+ """Check if input is a valid list of search/replace blocks."""
22
+ if not isinstance(blocks, list):
23
+ return False
24
+ return len(blocks) > 0 and all(
25
+ isinstance(b, dict) and "search" in b and "replace" in b for b in blocks
23
26
  )
24
27
 
25
28
  @staticmethod
26
- def parse_search_replace_blocks(blocks_text: str) -> list:
29
+ def parse_search_replace_blocks(blocks: List[Dict]) -> List[Dict]:
27
30
  """
28
- Parse search/replace blocks from text.
31
+ Parse search/replace blocks from list format.
29
32
 
30
33
  Args:
31
- blocks_text: Text containing search/replace blocks
34
+ blocks: List of dicts with 'search' and 'replace' keys
32
35
 
33
36
  Returns:
34
37
  List of dicts with 'index', 'search', and 'replace' keys
35
38
  """
36
- blocks = []
37
- lines = blocks_text.split("\n")
38
- i = 0
39
- block_index = 0
40
-
41
- while i < len(lines):
42
- if lines[i].strip() == "<<<<<<< SEARCH":
43
- search_lines = []
44
- i += 1
45
-
46
- while i < len(lines) and lines[i].strip() != "=======":
47
- search_lines.append(lines[i])
48
- i += 1
49
-
50
- if i >= len(lines):
51
- break
52
-
53
- i += 1
54
- replace_lines = []
55
-
56
- while (
57
- i < len(lines)
58
- and lines[i].strip() != ">>>>>>> REPLACE"
59
- and lines[i].strip() != "======="
60
- ):
61
- replace_lines.append(lines[i])
62
- i += 1
63
-
64
- if i >= len(lines):
65
- break
66
-
67
- blocks.append(
68
- {
69
- "index": block_index,
70
- "search": "\n".join(search_lines),
71
- "replace": "\n".join(replace_lines),
72
- }
73
- )
74
- block_index += 1
75
- i += 1
76
- else:
77
- i += 1
78
-
79
- return blocks
39
+ if not isinstance(blocks, list):
40
+ return []
41
+
42
+ return [
43
+ {
44
+ "index": i,
45
+ "search": block.get("search", ""),
46
+ "replace": block.get("replace", ""),
47
+ }
48
+ for i, block in enumerate(blocks)
49
+ if isinstance(block, dict)
50
+ ]
80
51
 
81
52
  @staticmethod
82
53
  def create_split_diff_table(
@@ -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."""
@@ -157,34 +270,37 @@ class DisplayHandlers:
157
270
  f" - {agent_name}{current}: {agent_data['description']}"
158
271
  )
159
272
 
160
- def display_conversations(self, conversations: List[Dict[str, Any]]):
161
- """Display available conversations."""
273
+ def display_conversations(
274
+ self,
275
+ conversations: List[Dict[str, Any]],
276
+ get_history_callback=None,
277
+ delete_callback=None,
278
+ ):
279
+ """Display available conversations using interactive browser.
280
+
281
+ Args:
282
+ conversations: List of conversation metadata
283
+ get_history_callback: Optional callback to fetch full conversation history
284
+ delete_callback: Optional callback to delete conversations by IDs
285
+
286
+ Returns:
287
+ Selected conversation ID or None if cancelled
288
+ """
162
289
  if not conversations:
163
290
  self.console.print(
164
291
  Text("No saved conversations found.", style=RICH_STYLE_YELLOW)
165
292
  )
166
- return
167
-
168
- self.console.print(Text("Available conversations:", style=RICH_STYLE_YELLOW))
169
- for i, convo in enumerate(conversations[:30], 1):
170
- # Format timestamp for better readability
171
- timestamp = convo.get("timestamp", "Unknown")
172
- if isinstance(timestamp, (int, float)):
173
- timestamp = datetime.fromtimestamp(timestamp).strftime(
174
- "%Y-%m-%d %H:%M:%S"
175
- )
176
-
177
- title = convo.get("title", "Untitled")
178
- convo_id = convo.get("id", "unknown")
293
+ return None
179
294
 
180
- # Display conversation with index for easy selection
181
- self.console.print(f" {i}. {title} [{convo_id}]")
182
- self.console.print(f" Created: {timestamp}")
295
+ from .conversation_browser.browser import ConversationBrowser
183
296
 
184
- # Show a preview if available
185
- if "preview" in convo:
186
- self.console.print(f" Preview: {convo['preview']}")
187
- self.console.print("")
297
+ browser = ConversationBrowser(
298
+ console=self.console,
299
+ get_conversation_history=get_history_callback,
300
+ on_delete=delete_callback,
301
+ )
302
+ browser.set_conversations(conversations)
303
+ return browser.show()
188
304
 
189
305
  def display_consolidation_result(self, result: Dict[str, Any]):
190
306
  """Display information about a consolidation operation."""
@@ -109,9 +109,7 @@ class ToolDisplayHandlers:
109
109
  )
110
110
  )
111
111
 
112
- def _display_write_or_edit_file_use(
113
- self, tool_use: Dict, file_path: str, blocks_text: str
114
- ):
112
+ def _display_write_or_edit_file_use(self, tool_use: Dict, file_path: str, blocks):
115
113
  """Display write_or_edit_file tool with split diff view."""
116
114
  tool_icon = self.get_tool_icon(tool_use["name"])
117
115
 
@@ -121,10 +119,10 @@ class ToolDisplayHandlers:
121
119
 
122
120
  self.console.print(Panel(header, box=HORIZONTALS, title_align="left"))
123
121
 
124
- blocks = DiffDisplay.parse_search_replace_blocks(blocks_text)
122
+ parsed_blocks = DiffDisplay.parse_search_replace_blocks(blocks)
125
123
 
126
- if blocks:
127
- for block in blocks:
124
+ if parsed_blocks:
125
+ for block in parsed_blocks:
128
126
  diff_table = DiffDisplay.create_split_diff_table(
129
127
  block["search"], block["replace"], max_width=self.console.width - 4
130
128
  )
@@ -41,21 +41,21 @@ class FileEditingService:
41
41
  def write_or_edit_file(
42
42
  self,
43
43
  file_path: str,
44
- percentage_to_change: float,
45
44
  text_or_search_replace_blocks: str,
45
+ is_search_replace: bool = False,
46
46
  agent_name: Optional[str] = None,
47
47
  ) -> Dict[str, Any]:
48
48
  """
49
49
  Main entry point for file editing.
50
50
 
51
51
  Decision logic:
52
- - percentage > 50: Full file write
53
- - percentage <= 50: Search/replace blocks
52
+ - is_search_replace=False: Full file write
53
+ - is_search_replace=True: Search/replace blocks
54
54
 
55
55
  Args:
56
56
  file_path: Path to file (absolute or relative, ~ supported)
57
- percentage_to_change: Percentage of lines changing (0-100)
58
57
  text_or_search_replace_blocks: Full content or search/replace blocks
58
+ is_search_replace: True for search/replace mode, False for full content
59
59
  agent_name: Optional agent name for permission checks
60
60
 
61
61
  Returns:
@@ -94,12 +94,12 @@ class FileEditingService:
94
94
  }
95
95
 
96
96
  try:
97
- if percentage_to_change > 50:
98
- result = self._write_full_file(file_path, text_or_search_replace_blocks)
99
- else:
97
+ if is_search_replace:
100
98
  result = self._apply_search_replace(
101
99
  file_path, text_or_search_replace_blocks
102
100
  )
101
+ else:
102
+ result = self._write_full_file(file_path, text_or_search_replace_blocks)
103
103
 
104
104
  if result["status"] != "success":
105
105
  return result
@@ -166,7 +166,7 @@ class FileEditingService:
166
166
  return {
167
167
  "status": "error",
168
168
  "error": f"File not found: {file_path}",
169
- "suggestion": "Use percentage_to_change > 50 to create new files",
169
+ "suggestion": "Use full content mode (string) to create new files",
170
170
  }
171
171
 
172
172
  try:
@@ -4,39 +4,42 @@ File editing tool definitions and handlers for AgentCrew.
4
4
  Provides file_write_or_edit tool for intelligent file editing with search/replace blocks.
5
5
  """
6
6
 
7
- from typing import Dict, Any, Callable, Optional
7
+ from typing import Dict, Any, Callable, Optional, List
8
8
  from .service import FileEditingService
9
9
 
10
10
 
11
- def get_file_write_or_edit_tool_definition(provider="claude") -> Dict[str, Any]:
12
- """
13
- Get tool definition for file editing.
11
+ def convert_blocks_to_string(blocks: List[Dict[str, str]]) -> str:
12
+ result_parts = []
13
+ for block in blocks:
14
+ search_text = block.get("search", "")
15
+ replace_text = block.get("replace", "")
16
+ block_str = (
17
+ f"<<<<<<< SEARCH\n{search_text}\n=======\n{replace_text}\n>>>>>>> REPLACE"
18
+ )
19
+ result_parts.append(block_str)
20
+ return "\n".join(result_parts)
21
+
14
22
 
15
- Args:
16
- provider: LLM provider name ("claude", "openai", "groq", "google")
23
+ def is_full_content_mode(blocks: List[Dict[str, str]]) -> bool:
24
+ if len(blocks) == 1:
25
+ block = blocks[0]
26
+ search_text = block.get("search", "")
27
+ return search_text == ""
28
+ return False
17
29
 
18
- Returns:
19
- Provider-specific tool definition
20
- """
21
- tool_description = """Write/edit files via search/replace blocks or full content.
22
30
 
23
- LOGIC: percentage_to_change >50 = full content | ≤50 = search/replace
31
+ def get_file_write_or_edit_tool_definition(provider="claude") -> Dict[str, Any]:
32
+ tool_description = """Write/edit files via search/replace blocks.
24
33
 
25
- SEARCH/REPLACE BLOCK FORMAT:
26
- <<<<<<< SEARCH
27
- [exact content to find]
28
- =======
29
- [replacement content]
30
- >>>>>>> REPLACE
34
+ FORMAT: Array of {"search": "...", "replace": "..."} objects
35
+ - Empty search + replace with content = write full file content
36
+ - Non-empty search + replace = search/replace operation
37
+ - Non-empty search + empty replace = delete matched content
31
38
 
32
39
  RULES:
33
40
  1. SEARCH must match exactly (character-perfect)
34
41
  2. Include changing lines +0-3 context
35
- 3. Multiple blocks in one call OK
36
- 4. Preserve whitespace/indentation
37
- 5. Empty REPLACE = delete
38
-
39
- EXAMPLES: Add import (existing+new) | Delete (full→empty) | Modify (signature+changes)
42
+ 3. Preserve whitespace/indentation
40
43
 
41
44
  Auto syntax check (30+ langs) with rollback on error
42
45
  """
@@ -46,19 +49,28 @@ Auto syntax check (30+ langs) with rollback on error
46
49
  "type": "string",
47
50
  "description": "Path (absolute/relative). Use ~ for home. Ex: './src/main.py'",
48
51
  },
49
- "percentage_to_change": {
50
- "type": "number",
51
- "description": "% lines changing (0-100). >50=full, ≤50=blocks",
52
- },
53
52
  "text_or_search_replace_blocks": {
54
- "type": "string",
55
- "description": "Full content (>50%) OR search/replace blocks (≤50%)",
53
+ "type": "array",
54
+ "items": {
55
+ "type": "object",
56
+ "properties": {
57
+ "search": {
58
+ "type": "string",
59
+ "description": "Exact content to find. Empty string means full file write mode.",
60
+ },
61
+ "replace": {
62
+ "type": "string",
63
+ "description": "Replacement content (empty string to delete)",
64
+ },
65
+ },
66
+ "required": ["search", "replace"],
67
+ },
68
+ "description": 'Array of search/replace blocks. For full file write: [{"search": "", "replace": "full content"}]. For edits: [{"search": "exact match", "replace": "replacement"}]',
56
69
  },
57
70
  }
58
71
 
59
72
  tool_required = [
60
73
  "file_path",
61
- "percentage_to_change",
62
74
  "text_or_search_replace_blocks",
63
75
  ]
64
76
 
@@ -72,7 +84,7 @@ Auto syntax check (30+ langs) with rollback on error
72
84
  "required": tool_required,
73
85
  },
74
86
  }
75
- else: # provider in ["openai", "google", "groq"] or other OpenAI-compatible
87
+ else:
76
88
  return {
77
89
  "type": "function",
78
90
  "function": {
@@ -90,44 +102,37 @@ Auto syntax check (30+ langs) with rollback on error
90
102
  def get_file_write_or_edit_tool_handler(
91
103
  file_editing_service: FileEditingService,
92
104
  ) -> Callable:
93
- """
94
- Get the handler function for the file editing tool.
95
-
96
- Args:
97
- file_editing_service: FileEditingService instance
98
-
99
- Returns:
100
- Handler function
101
- """
102
-
103
105
  def handle_file_write_or_edit(**params) -> str:
104
- """
105
- Tool execution handler.
106
-
107
- Args:
108
- **params: Tool parameters (file_path, percentage_to_change, text_or_search_replace_blocks)
109
-
110
- Returns:
111
- Success or error message
112
- """
113
106
  file_path = params.get("file_path")
114
- percentage_to_change = params.get("percentage_to_change")
115
- text_or_search_replace_blocks = params.get("text_or_search_replace_blocks")
107
+ blocks = params.get("text_or_search_replace_blocks")
116
108
 
117
109
  if not file_path:
118
110
  raise ValueError("Error: No file path provided.")
119
111
 
120
- if percentage_to_change is None:
121
- raise ValueError("Error: No percentage_to_change provided.")
112
+ if blocks is None:
113
+ raise ValueError("Error: No search/replace blocks provided.")
122
114
 
123
- if not text_or_search_replace_blocks:
124
- raise ValueError("Error: No content or search/replace blocks provided.")
115
+ if not isinstance(blocks, list):
116
+ raise ValueError(
117
+ "Error: text_or_search_replace_blocks must be an array of search/replace objects."
118
+ )
125
119
 
126
- result = file_editing_service.write_or_edit_file(
127
- file_path=file_path,
128
- percentage_to_change=float(percentage_to_change),
129
- text_or_search_replace_blocks=text_or_search_replace_blocks,
130
- )
120
+ full_content_mode = is_full_content_mode(blocks)
121
+
122
+ if full_content_mode:
123
+ content = blocks[0].get("replace", "")
124
+ result = file_editing_service.write_or_edit_file(
125
+ file_path=file_path,
126
+ is_search_replace=False,
127
+ text_or_search_replace_blocks=content,
128
+ )
129
+ else:
130
+ blocks_string = convert_blocks_to_string(blocks)
131
+ result = file_editing_service.write_or_edit_file(
132
+ file_path=file_path,
133
+ is_search_replace=True,
134
+ text_or_search_replace_blocks=blocks_string,
135
+ )
131
136
 
132
137
  if result["status"] == "success":
133
138
  parts = [f"{result['file_path']}"]
@@ -171,13 +176,6 @@ def get_file_write_or_edit_tool_handler(
171
176
 
172
177
 
173
178
  def register(service_instance: Optional[FileEditingService] = None, agent=None):
174
- """
175
- Register file editing tools with AgentCrew tool registry.
176
-
177
- Args:
178
- service_instance: Optional FileEditingService instance
179
- agent: Optional agent to register with directly
180
- """
181
179
  from AgentCrew.modules.tools.registration import register_tool
182
180
 
183
181
  if service_instance is None: