agentcrew-ai 0.8.12__py3-none-any.whl → 0.8.13__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.
- AgentCrew/__init__.py +1 -1
- AgentCrew/main.py +55 -3
- AgentCrew/modules/agents/local_agent.py +25 -0
- AgentCrew/modules/code_analysis/__init__.py +8 -0
- AgentCrew/modules/code_analysis/parsers/__init__.py +67 -0
- AgentCrew/modules/code_analysis/parsers/base.py +93 -0
- AgentCrew/modules/code_analysis/parsers/cpp_parser.py +127 -0
- AgentCrew/modules/code_analysis/parsers/csharp_parser.py +162 -0
- AgentCrew/modules/code_analysis/parsers/generic_parser.py +63 -0
- AgentCrew/modules/code_analysis/parsers/go_parser.py +154 -0
- AgentCrew/modules/code_analysis/parsers/java_parser.py +103 -0
- AgentCrew/modules/code_analysis/parsers/javascript_parser.py +268 -0
- AgentCrew/modules/code_analysis/parsers/kotlin_parser.py +84 -0
- AgentCrew/modules/code_analysis/parsers/php_parser.py +107 -0
- AgentCrew/modules/code_analysis/parsers/python_parser.py +60 -0
- AgentCrew/modules/code_analysis/parsers/ruby_parser.py +46 -0
- AgentCrew/modules/code_analysis/parsers/rust_parser.py +72 -0
- AgentCrew/modules/code_analysis/service.py +231 -897
- AgentCrew/modules/command_execution/constants.py +2 -2
- AgentCrew/modules/console/confirmation_handler.py +4 -4
- AgentCrew/modules/console/console_ui.py +20 -1
- AgentCrew/modules/console/conversation_browser.py +557 -0
- AgentCrew/modules/console/diff_display.py +22 -51
- AgentCrew/modules/console/display_handlers.py +22 -22
- AgentCrew/modules/console/tool_display.py +4 -6
- AgentCrew/modules/file_editing/service.py +8 -8
- AgentCrew/modules/file_editing/tool.py +65 -67
- AgentCrew/modules/gui/components/tool_handlers.py +0 -2
- AgentCrew/modules/gui/widgets/diff_widget.py +30 -61
- AgentCrew/modules/llm/constants.py +5 -5
- AgentCrew/modules/memory/context_persistent.py +1 -0
- AgentCrew/modules/memory/tool.py +1 -1
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.8.13.dist-info}/METADATA +1 -1
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.8.13.dist-info}/RECORD +38 -24
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.8.13.dist-info}/WHEEL +1 -1
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.8.13.dist-info}/entry_points.txt +0 -0
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.8.13.dist-info}/licenses/LICENSE +0 -0
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.8.13.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(
|
|
20
|
-
"""Check if
|
|
21
|
-
|
|
22
|
-
|
|
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(
|
|
29
|
+
def parse_search_replace_blocks(blocks: List[Dict]) -> List[Dict]:
|
|
27
30
|
"""
|
|
28
|
-
Parse search/replace blocks from
|
|
31
|
+
Parse search/replace blocks from list format.
|
|
29
32
|
|
|
30
33
|
Args:
|
|
31
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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(
|
|
@@ -157,34 +157,34 @@ class DisplayHandlers:
|
|
|
157
157
|
f" - {agent_name}{current}: {agent_data['description']}"
|
|
158
158
|
)
|
|
159
159
|
|
|
160
|
-
def display_conversations(
|
|
161
|
-
|
|
160
|
+
def display_conversations(
|
|
161
|
+
self,
|
|
162
|
+
conversations: List[Dict[str, Any]],
|
|
163
|
+
get_history_callback=None,
|
|
164
|
+
):
|
|
165
|
+
"""Display available conversations using interactive browser.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
conversations: List of conversation metadata
|
|
169
|
+
get_history_callback: Optional callback to fetch full conversation history
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Selected conversation ID or None if cancelled
|
|
173
|
+
"""
|
|
162
174
|
if not conversations:
|
|
163
175
|
self.console.print(
|
|
164
176
|
Text("No saved conversations found.", style=RICH_STYLE_YELLOW)
|
|
165
177
|
)
|
|
166
|
-
return
|
|
178
|
+
return None
|
|
167
179
|
|
|
168
|
-
|
|
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
|
-
)
|
|
180
|
+
from .conversation_browser import ConversationBrowser
|
|
176
181
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# Show a preview if available
|
|
185
|
-
if "preview" in convo:
|
|
186
|
-
self.console.print(f" Preview: {convo['preview']}")
|
|
187
|
-
self.console.print("")
|
|
182
|
+
browser = ConversationBrowser(
|
|
183
|
+
console=self.console,
|
|
184
|
+
get_conversation_history=get_history_callback,
|
|
185
|
+
)
|
|
186
|
+
browser.set_conversations(conversations)
|
|
187
|
+
return browser.show()
|
|
188
188
|
|
|
189
189
|
def display_consolidation_result(self, result: Dict[str, Any]):
|
|
190
190
|
"""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
|
-
|
|
122
|
+
parsed_blocks = DiffDisplay.parse_search_replace_blocks(blocks)
|
|
125
123
|
|
|
126
|
-
if
|
|
127
|
-
for block in
|
|
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
|
-
-
|
|
53
|
-
-
|
|
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
|
|
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
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
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": "
|
|
55
|
-
"
|
|
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:
|
|
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
|
-
|
|
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
|
|
121
|
-
raise ValueError("Error: No
|
|
112
|
+
if blocks is None:
|
|
113
|
+
raise ValueError("Error: No search/replace blocks provided.")
|
|
122
114
|
|
|
123
|
-
if not
|
|
124
|
-
raise ValueError(
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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:
|
|
@@ -302,7 +302,6 @@ class ToolEventHandler:
|
|
|
302
302
|
tool_input = tool_use.get("input", {})
|
|
303
303
|
file_path = tool_input.get("file_path", "")
|
|
304
304
|
text_or_blocks = tool_input.get("text_or_search_replace_blocks", "")
|
|
305
|
-
percentage = tool_input.get("percentage_to_change", 0)
|
|
306
305
|
|
|
307
306
|
has_diff = DiffWidget.has_search_replace_blocks(text_or_blocks)
|
|
308
307
|
|
|
@@ -321,7 +320,6 @@ class ToolEventHandler:
|
|
|
321
320
|
layout.addWidget(header_label)
|
|
322
321
|
|
|
323
322
|
info_label = QLabel(
|
|
324
|
-
f"Change percentage: {percentage}% | "
|
|
325
323
|
f"Mode: {'Search/Replace Blocks' if has_diff else 'Full Content'}"
|
|
326
324
|
)
|
|
327
325
|
info_label.setStyleSheet(
|
|
@@ -17,10 +17,6 @@ from PySide6.QtCore import Qt
|
|
|
17
17
|
class DiffWidget(QWidget):
|
|
18
18
|
"""Widget to display split diff view for file changes."""
|
|
19
19
|
|
|
20
|
-
SEARCH_DELIMITER = "<<<<<<< SEARCH"
|
|
21
|
-
MIDDLE_DELIMITER = "======="
|
|
22
|
-
REPLACE_DELIMITER = ">>>>>>> REPLACE"
|
|
23
|
-
|
|
24
20
|
def __init__(self, parent=None, style_provider=None):
|
|
25
21
|
super().__init__(parent)
|
|
26
22
|
self._style_provider = style_provider
|
|
@@ -62,71 +58,44 @@ class DiffWidget(QWidget):
|
|
|
62
58
|
self.main_layout.setSpacing(8)
|
|
63
59
|
|
|
64
60
|
@staticmethod
|
|
65
|
-
def has_search_replace_blocks(
|
|
66
|
-
"""Check if
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
def has_search_replace_blocks(blocks: List[Dict]) -> bool:
|
|
62
|
+
"""Check if input is a valid list of search/replace blocks."""
|
|
63
|
+
if not isinstance(blocks, list):
|
|
64
|
+
return False
|
|
65
|
+
return len(blocks) > 0 and all(
|
|
66
|
+
isinstance(b, dict) and "search" in b and "replace" in b for b in blocks
|
|
69
67
|
)
|
|
70
68
|
|
|
71
69
|
@staticmethod
|
|
72
|
-
def parse_search_replace_blocks(
|
|
70
|
+
def parse_search_replace_blocks(blocks: List[Dict]) -> List[Dict]:
|
|
73
71
|
"""
|
|
74
|
-
Parse search/replace blocks from
|
|
72
|
+
Parse search/replace blocks from list format.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
blocks: List of dicts with 'search' and 'replace' keys
|
|
75
76
|
|
|
76
77
|
Returns:
|
|
77
78
|
List of dicts with 'index', 'search', and 'replace' keys
|
|
78
79
|
"""
|
|
79
|
-
blocks
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if i >= len(lines):
|
|
94
|
-
break
|
|
95
|
-
|
|
96
|
-
i += 1
|
|
97
|
-
replace_lines = []
|
|
98
|
-
|
|
99
|
-
while (
|
|
100
|
-
i < len(lines)
|
|
101
|
-
and lines[i].strip() != ">>>>>>> REPLACE"
|
|
102
|
-
and lines[i].strip() != "======="
|
|
103
|
-
):
|
|
104
|
-
replace_lines.append(lines[i])
|
|
105
|
-
i += 1
|
|
106
|
-
|
|
107
|
-
if i >= len(lines):
|
|
108
|
-
break
|
|
109
|
-
|
|
110
|
-
blocks.append(
|
|
111
|
-
{
|
|
112
|
-
"index": block_index,
|
|
113
|
-
"search": "\n".join(search_lines),
|
|
114
|
-
"replace": "\n".join(replace_lines),
|
|
115
|
-
}
|
|
116
|
-
)
|
|
117
|
-
block_index += 1
|
|
118
|
-
i += 1
|
|
119
|
-
else:
|
|
120
|
-
i += 1
|
|
121
|
-
|
|
122
|
-
return blocks
|
|
123
|
-
|
|
124
|
-
def set_diff_content(self, blocks_text: str, file_path: str = ""):
|
|
80
|
+
if not isinstance(blocks, list):
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
"index": i,
|
|
86
|
+
"search": block.get("search", ""),
|
|
87
|
+
"replace": block.get("replace", ""),
|
|
88
|
+
}
|
|
89
|
+
for i, block in enumerate(blocks)
|
|
90
|
+
if isinstance(block, dict)
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
def set_diff_content(self, blocks: List[Dict], file_path: str = ""):
|
|
125
94
|
"""
|
|
126
95
|
Set the diff content to display.
|
|
127
96
|
|
|
128
97
|
Args:
|
|
129
|
-
|
|
98
|
+
blocks: List of search/replace block dicts
|
|
130
99
|
file_path: Optional file path to display in header
|
|
131
100
|
"""
|
|
132
101
|
self._clear_layout()
|
|
@@ -139,7 +108,7 @@ class DiffWidget(QWidget):
|
|
|
139
108
|
)
|
|
140
109
|
self.main_layout.addWidget(header)
|
|
141
110
|
|
|
142
|
-
blocks = self.parse_search_replace_blocks(
|
|
111
|
+
blocks = self.parse_search_replace_blocks(blocks)
|
|
143
112
|
|
|
144
113
|
if not blocks:
|
|
145
114
|
no_blocks_label = QLabel("No valid search/replace blocks found")
|
|
@@ -378,12 +347,12 @@ class CompactDiffWidget(QWidget):
|
|
|
378
347
|
self.main_layout.setContentsMargins(4, 4, 4, 4)
|
|
379
348
|
self.main_layout.setSpacing(4)
|
|
380
349
|
|
|
381
|
-
def set_diff_content(self,
|
|
350
|
+
def set_diff_content(self, blocks: List[Dict], file_path: str = ""):
|
|
382
351
|
"""Set the diff content to display in compact form."""
|
|
383
352
|
self._clear_layout()
|
|
384
353
|
colors = self._colors
|
|
385
354
|
|
|
386
|
-
blocks = DiffWidget.parse_search_replace_blocks(
|
|
355
|
+
blocks = DiffWidget.parse_search_replace_blocks(blocks)
|
|
387
356
|
|
|
388
357
|
if not blocks:
|
|
389
358
|
return
|
|
@@ -428,7 +397,7 @@ class CompactDiffWidget(QWidget):
|
|
|
428
397
|
layout.setContentsMargins(6, 6, 6, 6)
|
|
429
398
|
layout.setSpacing(1)
|
|
430
399
|
|
|
431
|
-
if
|
|
400
|
+
if block_num > 1:
|
|
432
401
|
block_header = QLabel(f"Block {block_num}")
|
|
433
402
|
block_header.setStyleSheet(
|
|
434
403
|
f"font-size: 10px; color: {colors['line_number_text']}; padding: 0 0 2px 0;"
|
|
@@ -325,14 +325,14 @@ _DEEPINFRA_MODELS = [
|
|
|
325
325
|
output_token_price_1m=0.6,
|
|
326
326
|
),
|
|
327
327
|
Model(
|
|
328
|
-
id="zai-org/GLM-4.
|
|
328
|
+
id="zai-org/GLM-4.7",
|
|
329
329
|
provider="deepinfra",
|
|
330
|
-
name="Zai GLM-4.
|
|
331
|
-
description="
|
|
330
|
+
name="Zai GLM-4.7",
|
|
331
|
+
description="GLM-4.7 is a state-of-the-art, multilingual Mixture-of-Experts (MoE) language model designed for complex reasoning, agentic coding, and tool use",
|
|
332
332
|
force_sample_params=SampleParam(temperature=1, top_p=0.95, top_k=40),
|
|
333
333
|
capabilities=["tool_use", "stream", "structured_output"],
|
|
334
|
-
input_token_price_1m=0.
|
|
335
|
-
output_token_price_1m=
|
|
334
|
+
input_token_price_1m=0.43,
|
|
335
|
+
output_token_price_1m=1.75,
|
|
336
336
|
),
|
|
337
337
|
Model(
|
|
338
338
|
id="Qwen/Qwen3-32B",
|
AgentCrew/modules/memory/tool.py
CHANGED
|
@@ -53,7 +53,7 @@ def get_memory_forget_tool_handler(memory_service: BaseMemoryService) -> Callabl
|
|
|
53
53
|
"""Optimized memory forgetting handler with concise feedback."""
|
|
54
54
|
|
|
55
55
|
def handle_memory_forget(**params) -> str:
|
|
56
|
-
ids = params.get("ids",
|
|
56
|
+
ids = params.get("ids", [])
|
|
57
57
|
|
|
58
58
|
# Use provided agent_name or fallback to current agent
|
|
59
59
|
current_agent = AgentManager.get_instance().get_current_agent()
|