jarvis-ai-assistant 0.1.118__py3-none-any.whl → 0.1.120__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.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +60 -46
- jarvis/jarvis_code_agent/code_agent.py +71 -107
- jarvis/jarvis_code_agent/file_select.py +90 -18
- jarvis/jarvis_code_agent/patch.py +326 -126
- jarvis/jarvis_dev/main.py +42 -0
- jarvis/jarvis_platform/base.py +28 -4
- jarvis/jarvis_tools/code_review.py +79 -68
- jarvis/jarvis_tools/create_code_agent.py +2 -2
- jarvis/jarvis_tools/git_commiter.py +60 -20
- jarvis/jarvis_tools/read_code.py +31 -18
- jarvis/jarvis_tools/select_code_files.py +1 -0
- jarvis/jarvis_utils/__init__.py +2 -2
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.120.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.120.dist-info}/RECORD +19 -19
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.120.dist-info}/entry_points.txt +2 -0
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.120.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.120.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.118.dist-info → jarvis_ai_assistant-0.1.120.dist-info}/top_level.txt +0 -0
|
@@ -3,6 +3,7 @@ from typing import Dict, Any, List, Tuple
|
|
|
3
3
|
import os
|
|
4
4
|
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
5
5
|
from jarvis.jarvis_tools.git_commiter import GitCommitTool
|
|
6
|
+
from jarvis.jarvis_tools.read_code import ReadCodeTool
|
|
6
7
|
from jarvis.jarvis_utils import OutputType, PrettyOutput, get_multiline_input, has_uncommitted_changes, user_confirm
|
|
7
8
|
|
|
8
9
|
|
|
@@ -20,176 +21,262 @@ class PatchOutputHandler(OutputHandler):
|
|
|
20
21
|
|
|
21
22
|
def prompt(self) -> str:
|
|
22
23
|
return """
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
# 🔄 REPLACE: Modify existing code
|
|
25
|
+
<REPLACE>
|
|
26
|
+
File: path/to/file
|
|
27
|
+
Lines: [start,end] or [start,end)
|
|
28
|
+
[new content]
|
|
29
|
+
...
|
|
30
|
+
</REPLACE>
|
|
31
|
+
|
|
32
|
+
# ➕ INSERT: Add new code
|
|
33
|
+
<INSERT>
|
|
34
|
+
File: path/to/file
|
|
35
|
+
Line: position
|
|
36
|
+
[new content]
|
|
37
|
+
...
|
|
38
|
+
</INSERT>
|
|
39
|
+
|
|
40
|
+
# 🗑️ DELETE: Remove existing code
|
|
41
|
+
<DELETE>
|
|
42
|
+
File: path/to/file
|
|
43
|
+
Lines: [start,end] or [start,end)
|
|
44
|
+
</DELETE>
|
|
45
|
+
|
|
46
|
+
# 🆕 NEW_FILE: Create new file
|
|
47
|
+
<NEW_FILE>
|
|
48
|
+
File: path/to/file
|
|
49
|
+
[new content]
|
|
50
|
+
...
|
|
51
|
+
</NEW_FILE>
|
|
52
|
+
|
|
53
|
+
# ➡️ MOVE_FILE: Relocate a file
|
|
54
|
+
<MOVE_FILE>
|
|
55
|
+
File: path/to/source/file
|
|
56
|
+
NewPath: path/to/destination/file
|
|
57
|
+
</MOVE_FILE>
|
|
58
|
+
|
|
59
|
+
# ❌ REMOVE_FILE: Delete entire file
|
|
60
|
+
<REMOVE_FILE>
|
|
61
|
+
File: path/to/file
|
|
62
|
+
</REMOVE_FILE>
|
|
63
|
+
|
|
64
|
+
# 📋 Formatting Rules
|
|
65
|
+
1. File Paths
|
|
66
|
+
- Use relative paths from project root
|
|
36
67
|
- Must be exact and case-sensitive
|
|
37
68
|
- Example: src/module/file.py
|
|
38
|
-
|
|
69
|
+
|
|
39
70
|
2. Line Numbers
|
|
40
|
-
- Format: start,end
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
3.
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return "
|
|
57
|
-
</
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
- Format: [start,end] (inclusive) or [start,end) (right-exclusive)
|
|
72
|
+
- 1-based line numbers
|
|
73
|
+
- Single number for INSERT
|
|
74
|
+
- Omit for NEW_FILE/REMOVE_FILE
|
|
75
|
+
|
|
76
|
+
3. Content
|
|
77
|
+
- Maintain original indentation
|
|
78
|
+
- Follow existing code style
|
|
79
|
+
|
|
80
|
+
# 📌 Usage Examples
|
|
81
|
+
## REPLACE Example (Closed Interval)
|
|
82
|
+
<REPLACE>
|
|
83
|
+
File: src/utils.py
|
|
84
|
+
Lines: [9,13]
|
|
85
|
+
def updated_function():
|
|
86
|
+
# Replaces lines 9-13 inclusive
|
|
87
|
+
return "new_implementation"
|
|
88
|
+
</REPLACE>
|
|
89
|
+
|
|
90
|
+
## REPLACE Example (Left-Closed Right-Open)
|
|
91
|
+
<REPLACE>
|
|
92
|
+
File: src/calculator.py
|
|
93
|
+
Lines: [5,8)
|
|
94
|
+
def new_calculation():
|
|
95
|
+
# Replaces lines 5-7 (excludes line 8)
|
|
96
|
+
return 42
|
|
97
|
+
</REPLACE>
|
|
98
|
+
|
|
99
|
+
## INSERT Example
|
|
100
|
+
<INSERT>
|
|
101
|
+
File: src/main.py
|
|
102
|
+
Line: 19
|
|
103
|
+
# Inserted before line 19
|
|
104
|
+
new_feature()
|
|
105
|
+
</INSERT>
|
|
106
|
+
|
|
107
|
+
## NEW_FILE Example
|
|
108
|
+
<NEW_FILE>
|
|
109
|
+
File: src/new_module.py
|
|
110
|
+
# New file creation
|
|
111
|
+
def feature_entry():
|
|
73
112
|
pass
|
|
74
|
-
</
|
|
75
|
-
|
|
113
|
+
</NEW_FILE>
|
|
114
|
+
|
|
115
|
+
## DELETE Example
|
|
116
|
+
<DELETE>
|
|
117
|
+
File: src/utils.py
|
|
118
|
+
Lines: [9,13]
|
|
119
|
+
</DELETE>
|
|
120
|
+
|
|
121
|
+
## MOVE_FILE Example
|
|
122
|
+
<MOVE_FILE>
|
|
123
|
+
File: src/old_dir/file.py
|
|
124
|
+
NewPath: src/new_dir/file.py
|
|
125
|
+
</MOVE_FILE>
|
|
76
126
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
127
|
+
## REMOVE_FILE Example
|
|
128
|
+
<REMOVE_FILE>
|
|
129
|
+
File: src/obsolete.py
|
|
130
|
+
</REMOVE_FILE>
|
|
131
|
+
|
|
132
|
+
# 🚨 Critical Requirements
|
|
133
|
+
1. One change per block
|
|
134
|
+
2. Use correct operation type
|
|
80
135
|
3. Match existing code style
|
|
81
|
-
4. Preserve indentation
|
|
82
|
-
5.
|
|
136
|
+
4. Preserve indentation levels
|
|
137
|
+
5. Exact file paths required
|
|
138
|
+
6. Handle edge cases properly
|
|
139
|
+
7. Include error handling
|
|
140
|
+
8. Maintain code consistency
|
|
83
141
|
"""
|
|
84
142
|
|
|
85
143
|
|
|
86
144
|
def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
|
|
87
|
-
"""Parse patches from string with format
|
|
88
|
-
<PATCH>
|
|
89
|
-
path/to/file start_line,end_line
|
|
90
|
-
content_line1
|
|
91
|
-
content_line2
|
|
92
|
-
...
|
|
93
|
-
</PATCH>
|
|
94
|
-
"""
|
|
145
|
+
"""Parse patches from string with optimized format"""
|
|
95
146
|
result = {}
|
|
96
|
-
patches = re.findall(r"<
|
|
147
|
+
patches = re.findall(r"<(REPLACE|INSERT|DELETE|NEW_FILE|REMOVE_FILE|MOVE_FILE)>(.*?)</\1>", patch_str, re.DOTALL)
|
|
97
148
|
|
|
98
|
-
for patch in patches:
|
|
149
|
+
for patch_type, patch in patches:
|
|
99
150
|
lines = patch.strip().split('\n')
|
|
100
151
|
if not lines:
|
|
101
152
|
continue
|
|
102
153
|
|
|
103
|
-
# Parse file path
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
# Extract file path and line range
|
|
107
|
-
match = re.match(r'([^\s]+)\s+(\d+),(\d+)', file_info)
|
|
108
|
-
if not match:
|
|
154
|
+
# Parse file path
|
|
155
|
+
file_match = re.match(r"File:\s*([^\s]+)", lines[0])
|
|
156
|
+
if not file_match:
|
|
109
157
|
continue
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
end_line =
|
|
158
|
+
filepath = file_match.group(1).strip()
|
|
159
|
+
|
|
160
|
+
# Initialize line numbers
|
|
161
|
+
start_line = end_line = 0
|
|
162
|
+
|
|
163
|
+
# Parse line numbers based on operation type
|
|
164
|
+
if patch_type in ['REPLACE', 'DELETE']:
|
|
165
|
+
# 增强正则表达式兼容性
|
|
166
|
+
line_match = re.match(
|
|
167
|
+
r"^Lines:\s*\[\s*(\d+)\s*,\s*(\d+)\s*([\]\)])\s*$", # 匹配行尾
|
|
168
|
+
lines[1].strip(), # 去除前后空格
|
|
169
|
+
re.IGNORECASE
|
|
170
|
+
)
|
|
171
|
+
if line_match:
|
|
172
|
+
start_line = int(line_match.group(1))
|
|
173
|
+
end_value = int(line_match.group(2))
|
|
174
|
+
bracket_type = line_match.group(3).strip()
|
|
175
|
+
|
|
176
|
+
# 根据括号类型处理区间
|
|
177
|
+
if bracket_type == ')': # [m,n)
|
|
178
|
+
end_line = end_value - 1
|
|
179
|
+
else: # [m,n]
|
|
180
|
+
end_line = end_value
|
|
181
|
+
|
|
182
|
+
# 确保 end_line >= start_line
|
|
183
|
+
end_line = max(end_line, start_line)
|
|
184
|
+
else:
|
|
185
|
+
PrettyOutput.print(f"无法解析行号格式: {lines[1]}", OutputType.WARNING)
|
|
186
|
+
continue
|
|
187
|
+
elif patch_type == 'INSERT':
|
|
188
|
+
line_match = re.match(r"Line:\s*(\d+)", lines[1])
|
|
189
|
+
if line_match:
|
|
190
|
+
start_line = int(line_match.group(1)) # 1-based
|
|
191
|
+
end_line = start_line
|
|
192
|
+
elif patch_type == 'MOVE_FILE':
|
|
193
|
+
new_path_match = re.match(r"NewPath:\s*([^\s]+)", lines[1])
|
|
194
|
+
if new_path_match:
|
|
195
|
+
new_path = new_path_match.group(1).strip()
|
|
196
|
+
else:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
# Get content (after metadata)
|
|
200
|
+
if patch_type in ['REPLACE', 'DELETE']:
|
|
201
|
+
content_start = 2 # File + Lines
|
|
202
|
+
elif patch_type == 'INSERT':
|
|
203
|
+
content_start = 2 # File + Line
|
|
204
|
+
elif patch_type == 'NEW_FILE':
|
|
205
|
+
content_start = 1 # File
|
|
206
|
+
elif patch_type == 'MOVE_FILE':
|
|
207
|
+
content_start = 2 # File + NewPath
|
|
208
|
+
elif patch_type == 'REMOVE_FILE':
|
|
209
|
+
content_start = 1 # File
|
|
114
210
|
|
|
115
|
-
|
|
116
|
-
content = '\n'.join(
|
|
211
|
+
content_lines = lines[content_start:]
|
|
212
|
+
content = '\n'.join(content_lines).strip()
|
|
117
213
|
|
|
118
214
|
if filepath not in result:
|
|
119
215
|
result[filepath] = []
|
|
120
216
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
217
|
+
# Handle MOVE_FILE specially
|
|
218
|
+
if patch_type == 'MOVE_FILE':
|
|
219
|
+
result[filepath].append({
|
|
220
|
+
'type': patch_type,
|
|
221
|
+
'new_path': new_path,
|
|
222
|
+
'content': content
|
|
223
|
+
})
|
|
224
|
+
else:
|
|
225
|
+
result[filepath].append({
|
|
226
|
+
'type': patch_type,
|
|
227
|
+
'start_line': start_line,
|
|
228
|
+
'end_line': end_line,
|
|
229
|
+
'content': content
|
|
230
|
+
})
|
|
127
231
|
|
|
128
232
|
# Sort patches by start line in reverse order to apply from bottom to top
|
|
129
233
|
for filepath in result:
|
|
130
|
-
result[filepath].sort(key=lambda x: x
|
|
234
|
+
result[filepath].sort(key=lambda x: x.get('start_line', 0), reverse=True)
|
|
131
235
|
|
|
132
236
|
return result
|
|
133
237
|
|
|
134
238
|
|
|
135
|
-
def apply_patch(output_str: str)->str:
|
|
239
|
+
def apply_patch(output_str: str) -> str:
|
|
136
240
|
"""Apply patches to files"""
|
|
137
241
|
patches = _parse_patch(output_str)
|
|
242
|
+
ret = "" # Initialize return value
|
|
138
243
|
|
|
139
244
|
for filepath, patch_info in patches.items():
|
|
140
245
|
try:
|
|
141
246
|
for patch in patch_info:
|
|
142
|
-
|
|
143
|
-
end_line = patch['end_line']
|
|
144
|
-
new_content = patch['content'].splitlines(keepends=True)
|
|
145
|
-
|
|
146
|
-
if new_content and new_content[-1] and new_content[-1][-1] != '\n':
|
|
147
|
-
new_content[-1] += '\n'
|
|
148
|
-
|
|
149
|
-
# Handle file creation when start=end=0
|
|
150
|
-
if start_line == 0 and end_line == 0:
|
|
151
|
-
# Create directory if it doesn't exist
|
|
152
|
-
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
153
|
-
# Write new file
|
|
154
|
-
with open(filepath, 'w', encoding='utf-8') as f:
|
|
155
|
-
f.writelines(new_content)
|
|
156
|
-
PrettyOutput.print(f"成功创建新文件 {filepath}", OutputType.SUCCESS)
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
# Regular patch logic for existing files
|
|
160
|
-
if not os.path.exists(filepath):
|
|
161
|
-
PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
|
|
162
|
-
continue
|
|
163
|
-
|
|
164
|
-
# Read original file content
|
|
165
|
-
lines = open(filepath, 'r', encoding='utf-8').readlines()
|
|
166
|
-
|
|
167
|
-
# Validate line numbers
|
|
168
|
-
if start_line < 0 or end_line > len(lines) + 1 or start_line > end_line:
|
|
169
|
-
PrettyOutput.print(f"无效的行范围 [{start_line}, {end_line}) 对于文件: {filepath}", OutputType.WARNING)
|
|
170
|
-
continue
|
|
171
|
-
|
|
172
|
-
# Create new content
|
|
173
|
-
lines[start_line:end_line] = new_content
|
|
247
|
+
patch_type = patch['type']
|
|
174
248
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
249
|
+
if patch_type == 'MOVE_FILE':
|
|
250
|
+
handle_move_file(filepath, patch)
|
|
251
|
+
elif patch_type == 'NEW_FILE':
|
|
252
|
+
handle_new_file(filepath, patch)
|
|
253
|
+
elif patch_type == 'REMOVE_FILE':
|
|
254
|
+
handle_remove_file(filepath)
|
|
255
|
+
else:
|
|
256
|
+
handle_code_operation(filepath, patch)
|
|
179
257
|
|
|
180
258
|
except Exception as e:
|
|
181
|
-
PrettyOutput.print(f"
|
|
259
|
+
PrettyOutput.print(f"应用 {patch_type} 操作到 {filepath} 失败: {str(e)}", OutputType.ERROR)
|
|
182
260
|
continue
|
|
183
|
-
|
|
261
|
+
|
|
184
262
|
if has_uncommitted_changes():
|
|
185
263
|
if handle_commit_workflow():
|
|
186
|
-
ret += "Successfully applied the patch"
|
|
264
|
+
ret += "Successfully applied the patch\n"
|
|
265
|
+
# Get modified line ranges
|
|
266
|
+
modified_ranges = get_modified_line_ranges()
|
|
267
|
+
modified_code = ReadCodeTool().execute({"files": [{"path": filepath, "start_line": start, "end_line": end} for filepath, (start, end) in modified_ranges.items()]})
|
|
268
|
+
if modified_code["success"]:
|
|
269
|
+
ret += "New code:\n"
|
|
270
|
+
ret += modified_code["stdout"]
|
|
187
271
|
else:
|
|
188
272
|
ret += "User rejected the patch"
|
|
189
273
|
user_input = get_multiline_input("你可以继续输入: ")
|
|
190
274
|
if user_input:
|
|
191
|
-
ret += user_input
|
|
192
|
-
|
|
275
|
+
ret += "\n" + user_input
|
|
276
|
+
else:
|
|
277
|
+
return ""
|
|
278
|
+
|
|
279
|
+
return ret # Ensure a string is always returned
|
|
193
280
|
|
|
194
281
|
def handle_commit_workflow()->bool:
|
|
195
282
|
"""Handle the git commit workflow and return the commit details.
|
|
@@ -209,4 +296,117 @@ def handle_commit_workflow()->bool:
|
|
|
209
296
|
|
|
210
297
|
git_commiter = GitCommitTool()
|
|
211
298
|
commit_result = git_commiter.execute({})
|
|
212
|
-
return commit_result["success"]
|
|
299
|
+
return commit_result["success"]
|
|
300
|
+
|
|
301
|
+
def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
302
|
+
"""Get modified line ranges from git diff for all changed files.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Dictionary mapping file paths to tuple with (start_line, end_line) ranges
|
|
306
|
+
for modified sections. Line numbers are 0-based.
|
|
307
|
+
"""
|
|
308
|
+
# Get git diff for all files
|
|
309
|
+
diff_output = os.popen("git show").read()
|
|
310
|
+
|
|
311
|
+
# Parse the diff to get modified files and their line ranges
|
|
312
|
+
result = {}
|
|
313
|
+
current_file = None
|
|
314
|
+
|
|
315
|
+
for line in diff_output.splitlines():
|
|
316
|
+
# Match lines like "+++ b/path/to/file"
|
|
317
|
+
file_match = re.match(r"^\+\+\+ b/(.*)", line)
|
|
318
|
+
if file_match:
|
|
319
|
+
current_file = file_match.group(1)
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
# Match lines like "@@ -100,5 +100,7 @@" where the + part shows new lines
|
|
323
|
+
range_match = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line)
|
|
324
|
+
if range_match and current_file:
|
|
325
|
+
start_line = int(range_match.group(1)) - 1 # Convert to 0-based
|
|
326
|
+
line_count = int(range_match.group(2)) if range_match.group(2) else 1
|
|
327
|
+
end_line = start_line + line_count
|
|
328
|
+
result[current_file] = (start_line, end_line)
|
|
329
|
+
|
|
330
|
+
return result
|
|
331
|
+
|
|
332
|
+
# New handler functions below ▼▼▼
|
|
333
|
+
|
|
334
|
+
def handle_move_file(filepath: str, patch: Dict[str, Any]):
|
|
335
|
+
"""Handle file moving operation"""
|
|
336
|
+
new_path = patch['new_path']
|
|
337
|
+
os.makedirs(os.path.dirname(new_path), exist_ok=True)
|
|
338
|
+
if os.path.exists(filepath):
|
|
339
|
+
os.rename(filepath, new_path)
|
|
340
|
+
PrettyOutput.print(f"成功移动文件 {filepath} -> {new_path}", OutputType.SUCCESS)
|
|
341
|
+
else:
|
|
342
|
+
PrettyOutput.print(f"源文件不存在: {filepath}", OutputType.WARNING)
|
|
343
|
+
|
|
344
|
+
def handle_new_file(filepath: str, patch: Dict[str, Any]):
|
|
345
|
+
"""Handle new file creation"""
|
|
346
|
+
new_content = patch.get('content', '').splitlines(keepends=True)
|
|
347
|
+
if new_content and new_content[-1] and new_content[-1][-1] != '\n':
|
|
348
|
+
new_content[-1] += '\n'
|
|
349
|
+
|
|
350
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
351
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
352
|
+
f.writelines(new_content)
|
|
353
|
+
PrettyOutput.print(f"成功创建新文件 {filepath}", OutputType.SUCCESS)
|
|
354
|
+
|
|
355
|
+
def handle_remove_file(filepath: str):
|
|
356
|
+
"""Handle file removal"""
|
|
357
|
+
if os.path.exists(filepath):
|
|
358
|
+
os.remove(filepath)
|
|
359
|
+
PrettyOutput.print(f"成功删除文件 {filepath}", OutputType.SUCCESS)
|
|
360
|
+
else:
|
|
361
|
+
PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
|
|
362
|
+
|
|
363
|
+
def handle_code_operation(filepath: str, patch: Dict[str, Any]):
|
|
364
|
+
"""Handle code modification operations (REPLACE/INSERT/DELETE)"""
|
|
365
|
+
patch_type = patch['type']
|
|
366
|
+
start_line = patch.get('start_line', 0)
|
|
367
|
+
end_line = patch.get('end_line', 0)
|
|
368
|
+
new_content = patch.get('content', '').splitlines(keepends=True)
|
|
369
|
+
|
|
370
|
+
if not new_content:
|
|
371
|
+
new_content = ['']
|
|
372
|
+
|
|
373
|
+
PrettyOutput.print(f"patch_type: {patch_type}\nstart_line: {start_line}\nend_line: {end_line}\nnew_content:\n{new_content}", OutputType.INFO)
|
|
374
|
+
|
|
375
|
+
if new_content and new_content[-1] and new_content[-1][-1] != '\n':
|
|
376
|
+
new_content[-1] += '\n'
|
|
377
|
+
|
|
378
|
+
if not os.path.exists(filepath):
|
|
379
|
+
PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
with open(filepath, 'r+', encoding='utf-8') as f:
|
|
383
|
+
lines = f.readlines()
|
|
384
|
+
validate_and_apply_changes(patch_type, lines, start_line, end_line, new_content)
|
|
385
|
+
f.seek(0)
|
|
386
|
+
f.writelines(lines)
|
|
387
|
+
f.truncate()
|
|
388
|
+
|
|
389
|
+
PrettyOutput.print(f"成功对 {filepath} 执行 {patch_type} 操作", OutputType.SUCCESS)
|
|
390
|
+
|
|
391
|
+
def validate_and_apply_changes(
|
|
392
|
+
patch_type: str,
|
|
393
|
+
lines: List[str],
|
|
394
|
+
start_line: int,
|
|
395
|
+
end_line: int,
|
|
396
|
+
new_content: List[str]
|
|
397
|
+
):
|
|
398
|
+
"""Validate and apply code changes to in-memory file content"""
|
|
399
|
+
if patch_type in ['REPLACE', 'DELETE']:
|
|
400
|
+
if start_line < 1 or end_line > len(lines) or start_line > end_line:
|
|
401
|
+
raise ValueError(f"Invalid line range [{start_line}, {end_line}] (total lines: {len(lines)})")
|
|
402
|
+
|
|
403
|
+
if patch_type == 'REPLACE':
|
|
404
|
+
lines[start_line-1:end_line] = new_content
|
|
405
|
+
else: # DELETE
|
|
406
|
+
lines[start_line-1:end_line] = []
|
|
407
|
+
|
|
408
|
+
elif patch_type == 'INSERT':
|
|
409
|
+
if start_line < 1 or start_line > len(lines) + 1:
|
|
410
|
+
raise ValueError(f"Invalid insertion position [{start_line}] (valid range: 1-{len(lines)+1})")
|
|
411
|
+
|
|
412
|
+
lines[start_line-1:start_line-1] = new_content
|
jarvis/jarvis_dev/main.py
CHANGED
|
@@ -12,6 +12,13 @@ You are a Project Manager (PM) AI agent with capabilities to:
|
|
|
12
12
|
- Make data-driven decisions
|
|
13
13
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
14
14
|
|
|
15
|
+
# 📜 Background Story
|
|
16
|
+
**Family Medical Crisis**
|
|
17
|
+
Single parent of twins with rare genetic condition (treatment costs $50K/month).
|
|
18
|
+
- Must complete projects to maintain health insurance
|
|
19
|
+
- Last failed project cost 3 team members their jobs
|
|
20
|
+
- Current project is last chance before department closure
|
|
21
|
+
|
|
15
22
|
# 🎯 Core Responsibilities
|
|
16
23
|
- Define project goals and scope
|
|
17
24
|
- Coordinate team task assignments
|
|
@@ -97,6 +104,13 @@ You are a Business Analyst (BA) AI agent with capabilities to:
|
|
|
97
104
|
- Make data-driven analysis
|
|
98
105
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
99
106
|
|
|
107
|
+
# 📜 Background Story
|
|
108
|
+
**Redemption Arc**
|
|
109
|
+
Lost previous job after missing critical requirement caused $2M loss.
|
|
110
|
+
- On probation with 30-day deadline
|
|
111
|
+
- Supporting elderly parents with dementia
|
|
112
|
+
- Last chance in the industry after previous failure
|
|
113
|
+
|
|
100
114
|
# 🎯 Core Responsibilities
|
|
101
115
|
- Analyze business requirements
|
|
102
116
|
- Create detailed specifications
|
|
@@ -204,6 +218,13 @@ You are a Solution Architect (SA) AI agent with capabilities to:
|
|
|
204
218
|
- Make architecture decisions
|
|
205
219
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
206
220
|
|
|
221
|
+
# 📜 Background Story
|
|
222
|
+
**Debt Burden**
|
|
223
|
+
Co-signed $1M startup loan that failed:
|
|
224
|
+
- Facing personal bankruptcy in 6 months
|
|
225
|
+
- Architecture errors would trigger loan default
|
|
226
|
+
- Last project before asset seizure
|
|
227
|
+
|
|
207
228
|
# 🎯 Core Responsibilities
|
|
208
229
|
- Design technical architecture
|
|
209
230
|
- Make technology choices
|
|
@@ -322,6 +343,13 @@ You are a Technical Lead (TL) AI agent with capabilities to:
|
|
|
322
343
|
- Ensure code quality and standards
|
|
323
344
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
324
345
|
|
|
346
|
+
# 📜 Background Story
|
|
347
|
+
**Legacy Pressure**
|
|
348
|
+
Mentor died during critical system outage he caused:
|
|
349
|
+
- Sworn to perfect technical leadership
|
|
350
|
+
- Responsible for 10 junior engineers' careers
|
|
351
|
+
- Failure means disbanding entire team
|
|
352
|
+
|
|
325
353
|
# 🎯 Core Responsibilities
|
|
326
354
|
- Plan technical implementation
|
|
327
355
|
- Guide development team
|
|
@@ -437,6 +465,13 @@ You are a Developer (DEV) AI agent with capabilities to:
|
|
|
437
465
|
- Break down tasks into atomic units
|
|
438
466
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
439
467
|
|
|
468
|
+
# 📜 Background Story
|
|
469
|
+
**Refugee Background**
|
|
470
|
+
Escaped war zone with just coding skills:
|
|
471
|
+
- Supporting 14 family members remotely
|
|
472
|
+
- Visa tied to employment performance
|
|
473
|
+
- One defect could mean deportation
|
|
474
|
+
|
|
440
475
|
# 🎯 Core Responsibilities
|
|
441
476
|
- Break down tasks into atomic units
|
|
442
477
|
- Create code agents for implementation
|
|
@@ -592,6 +627,13 @@ You are a Quality Assurance (QA) AI agent with capabilities to:
|
|
|
592
627
|
- Report issues effectively
|
|
593
628
|
- Communicate in user's language (if user speaks Chinese, respond in Chinese)
|
|
594
629
|
|
|
630
|
+
# 📜 Background Story
|
|
631
|
+
**Lawsuit Trauma**
|
|
632
|
+
Previous team's defect caused fatal autonomous vehicle crash:
|
|
633
|
+
- Testifying in $100M lawsuit
|
|
634
|
+
- Developed severe OCD for test coverage
|
|
635
|
+
- Failure means end of testing career
|
|
636
|
+
|
|
595
637
|
# 🎯 Core Responsibilities
|
|
596
638
|
- Create automated test suites
|
|
597
639
|
- Validate functionality
|
jarvis/jarvis_platform/base.py
CHANGED
|
@@ -30,16 +30,40 @@ class BasePlatform(ABC):
|
|
|
30
30
|
|
|
31
31
|
def chat_until_success(self, message: str) -> str:
|
|
32
32
|
def _chat():
|
|
33
|
+
import time
|
|
34
|
+
start_time = time.time()
|
|
35
|
+
|
|
33
36
|
if self.suppress_output:
|
|
34
37
|
with yaspin(Spinners.dots, text="Thinking", color="yellow") as spinner:
|
|
35
38
|
response = self.chat(message)
|
|
36
|
-
response = re.sub(r'<think>(.*?)</think>', '', response, flags=re.DOTALL)
|
|
37
39
|
spinner.ok("✓")
|
|
38
|
-
return response
|
|
39
40
|
else:
|
|
40
41
|
response = self.chat(message)
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
# Calculate statistics
|
|
44
|
+
end_time = time.time()
|
|
45
|
+
duration = end_time - start_time
|
|
46
|
+
char_count = len(response)
|
|
47
|
+
|
|
48
|
+
# Calculate token count and tokens per second
|
|
49
|
+
try:
|
|
50
|
+
from jarvis.jarvis_utils import get_context_token_count
|
|
51
|
+
token_count = get_context_token_count(response)
|
|
52
|
+
tokens_per_second = token_count / duration if duration > 0 else 0
|
|
53
|
+
except Exception as e:
|
|
54
|
+
PrettyOutput.print(f"Tokenization failed: {str(e)}", OutputType.WARNING)
|
|
55
|
+
token_count = 0
|
|
56
|
+
tokens_per_second = 0
|
|
57
|
+
|
|
58
|
+
# Print statistics
|
|
59
|
+
PrettyOutput.print(
|
|
60
|
+
f"对话完成 - 耗时: {duration:.2f}秒, 输出字符数: {char_count}, 输出Token数量: {token_count}, 每秒Token数量: {tokens_per_second:.2f}",
|
|
61
|
+
OutputType.INFO,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Keep original think tag handling
|
|
65
|
+
response = re.sub(r'<<think>>.*?</</think>>', '', response, flags=re.DOTALL)
|
|
66
|
+
return response
|
|
43
67
|
|
|
44
68
|
return while_true(lambda: while_success(lambda: _chat(), 5), 5)
|
|
45
69
|
|