jarvis-ai-assistant 0.1.199__py3-none-any.whl → 0.1.201__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +5 -0
- jarvis/jarvis_agent/builtin_input_handler.py +1 -0
- jarvis/jarvis_agent/edit_file_handler.py +415 -0
- jarvis/jarvis_code_agent/code_agent.py +4 -3
- jarvis/jarvis_platform/base.py +3 -0
- jarvis/jarvis_platform/yuanbao.py +5 -5
- jarvis/jarvis_tools/edit_file.py +165 -383
- jarvis/jarvis_tools/registry.py +11 -11
- jarvis/jarvis_utils/builtin_replace_map.py +0 -88
- jarvis/jarvis_utils/utils.py +7 -1
- {jarvis_ai_assistant-0.1.199.dist-info → jarvis_ai_assistant-0.1.201.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.1.199.dist-info → jarvis_ai_assistant-0.1.201.dist-info}/RECORD +17 -16
- {jarvis_ai_assistant-0.1.199.dist-info → jarvis_ai_assistant-0.1.201.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.199.dist-info → jarvis_ai_assistant-0.1.201.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.199.dist-info → jarvis_ai_assistant-0.1.201.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.199.dist-info → jarvis_ai_assistant-0.1.201.dist-info}/top_level.txt +0 -0
jarvis/jarvis_tools/edit_file.py
CHANGED
@@ -17,30 +17,21 @@
|
|
17
17
|
- 支持大文件处理(自动上传到模型平台)
|
18
18
|
- 提供3次重试机制确保操作可靠性
|
19
19
|
"""
|
20
|
-
import
|
21
|
-
from typing import Any, Dict, List, Tuple
|
20
|
+
from typing import Any, Dict
|
22
21
|
|
23
|
-
import yaml
|
24
22
|
from yaspin import yaspin
|
25
|
-
from yaspin.core import Yaspin
|
26
23
|
|
27
|
-
from jarvis.
|
28
|
-
from jarvis.jarvis_tools.file_operation import FileOperationTool
|
29
|
-
from jarvis.jarvis_utils.git_utils import revert_file
|
30
|
-
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
31
|
-
from jarvis.jarvis_utils.tag import ct, ot
|
32
|
-
from jarvis.jarvis_utils.utils import is_context_overflow
|
24
|
+
from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
|
33
25
|
|
34
26
|
|
35
27
|
class FileSearchReplaceTool:
|
36
28
|
name = "edit_file"
|
37
|
-
description = """
|
29
|
+
description = """代码编辑工具,用于精确修改一个或多个文件
|
38
30
|
|
39
31
|
# 文件编辑工具使用指南
|
40
32
|
|
41
|
-
|
42
33
|
## 基本使用
|
43
|
-
1.
|
34
|
+
1. 指定需要修改的文件路径(单个或多个)
|
44
35
|
2. 提供一组或多组修改,每个修改包含:
|
45
36
|
- reason: 修改原因描述
|
46
37
|
- search: 需要查找的原始代码(必须包含足够上下文)
|
@@ -52,59 +43,68 @@ class FileSearchReplaceTool:
|
|
52
43
|
2. **最小补丁原则**: 生成最小范围的补丁,包含必要的上下文
|
53
44
|
3. **唯一匹配**: 确保搜索文本在文件中唯一匹配
|
54
45
|
4. **格式保持**: 严格保持原始代码的格式风格
|
55
|
-
|
46
|
+
5. **部分成功**: 支持多个文件编辑,允许部分文件编辑成功
|
56
47
|
|
57
48
|
"""
|
58
49
|
parameters = {
|
59
50
|
"type": "object",
|
60
51
|
"properties": {
|
61
|
-
"
|
62
|
-
"changes": {
|
52
|
+
"files": {
|
63
53
|
"type": "array",
|
64
|
-
"description": "
|
54
|
+
"description": "需要修改的文件路径列表",
|
65
55
|
"items": {
|
66
56
|
"type": "object",
|
67
57
|
"properties": {
|
68
|
-
"
|
69
|
-
"
|
70
|
-
"type": "
|
71
|
-
"description": "
|
58
|
+
"path": {"type": "string", "description": "文件路径"},
|
59
|
+
"changes": {
|
60
|
+
"type": "array",
|
61
|
+
"description": "一组或多组修改,每个修改必须包含1-2行上下文用于精确定位",
|
62
|
+
"items": {
|
63
|
+
"type": "object",
|
64
|
+
"properties": {
|
65
|
+
"reason": {"type": "string", "description": "修改的原因"},
|
66
|
+
"search": {
|
67
|
+
"type": "string",
|
68
|
+
"description": "需要查找的原始代码",
|
69
|
+
},
|
70
|
+
"replace": {"type": "string", "description": "替换后的新代码"},
|
71
|
+
},
|
72
|
+
},
|
72
73
|
},
|
73
|
-
"replace": {"type": "string", "description": "替换后的新代码"},
|
74
74
|
},
|
75
|
+
"required": ["path", "changes"],
|
75
76
|
},
|
76
77
|
},
|
77
78
|
},
|
78
|
-
"required": ["
|
79
|
+
"required": ["files"],
|
79
80
|
}
|
80
81
|
|
81
|
-
def __init__(self):
|
82
|
-
"""初始化文件搜索替换工具"""
|
83
|
-
pass
|
84
82
|
|
85
83
|
def execute(self, args: Dict) -> Dict[str, Any]:
|
86
84
|
"""执行文件编辑操作,支持快速编辑和AI辅助编辑两种模式。
|
87
85
|
|
88
86
|
主要功能:
|
89
|
-
1.
|
90
|
-
2.
|
87
|
+
1. 处理多个文件的创建或修改,支持不存在的文件
|
88
|
+
2. 每个文件独立处理,允许部分文件编辑成功
|
91
89
|
3. 自动选择编辑模式(fast_edit或slow_edit)
|
92
90
|
4. 保存修改前后的文件状态以便回滚
|
93
91
|
5. 提供详细的执行状态输出
|
94
92
|
|
95
93
|
参数:
|
96
94
|
args: 包含以下键的字典:
|
97
|
-
-
|
98
|
-
|
99
|
-
-
|
100
|
-
|
101
|
-
|
95
|
+
- files: 文件列表,每个文件包含(必填):
|
96
|
+
- path: 要修改的文件路径
|
97
|
+
- changes: 修改列表,每个修改包含:
|
98
|
+
- reason: 修改原因描述
|
99
|
+
- search: 需要查找的原始代码(必须包含足够上下文)
|
100
|
+
- replace: 替换后的新代码
|
102
101
|
|
103
102
|
返回:
|
104
103
|
Dict[str, Any] 包含:
|
105
|
-
- success:
|
104
|
+
- success: 是否至少有一个文件编辑成功(True/False)
|
106
105
|
- stdout: 成功时的输出消息
|
107
106
|
- stderr: 失败时的错误消息
|
107
|
+
- results: 每个文件的处理结果列表
|
108
108
|
|
109
109
|
异常处理:
|
110
110
|
1. 捕获并记录文件操作异常
|
@@ -125,365 +125,147 @@ class FileSearchReplaceTool:
|
|
125
125
|
|
126
126
|
stdout_messages = []
|
127
127
|
stderr_messages = []
|
128
|
-
|
129
|
-
|
130
|
-
file_path = os.path.abspath(args["file"])
|
131
|
-
changes = args["changes"]
|
132
|
-
agent = args.get("agent", None)
|
128
|
+
overall_success = False
|
129
|
+
file_results = []
|
133
130
|
|
134
|
-
|
135
|
-
|
136
|
-
|
131
|
+
for file_info in args["files"]:
|
132
|
+
file_path = os.path.abspath(file_info["path"])
|
133
|
+
changes = file_info["changes"]
|
134
|
+
agent = args.get("agent", None)
|
137
135
|
|
138
|
-
|
139
|
-
|
140
|
-
|
136
|
+
# 创建已处理文件变量,用于失败时回滚
|
137
|
+
original_content = None
|
138
|
+
processed = False
|
139
|
+
file_success = True
|
141
140
|
|
142
141
|
try:
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
with yaspin(
|
159
|
-
text=f"正在处理文件 {file_path}...", color="cyan"
|
160
|
-
) as spinner:
|
161
|
-
success, temp_content = fast_edit(file_path, changes, spinner)
|
162
|
-
if not success:
|
163
|
-
success, temp_content = slow_edit(
|
164
|
-
file_path,
|
165
|
-
yaml.safe_dump(changes, allow_unicode=True),
|
166
|
-
spinner,
|
167
|
-
)
|
168
|
-
if not success:
|
169
|
-
spinner.text = f"文件 {file_path} 处理失败"
|
170
|
-
spinner.fail("❌")
|
171
|
-
return {
|
142
|
+
file_exists = os.path.exists(file_path)
|
143
|
+
content = ""
|
144
|
+
|
145
|
+
try:
|
146
|
+
# 如果文件存在,则读取内容
|
147
|
+
if file_exists:
|
148
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
149
|
+
content = f.read()
|
150
|
+
original_content = content
|
151
|
+
|
152
|
+
if file_exists and agent:
|
153
|
+
files = agent.get_user_data("files")
|
154
|
+
if not files or file_path not in files:
|
155
|
+
file_results.append({
|
156
|
+
"file": file_path,
|
172
157
|
"success": False,
|
173
158
|
"stdout": "",
|
174
|
-
"stderr":
|
175
|
-
}
|
159
|
+
"stderr": f"请先读取文件 {file_path} 的内容后再编辑"
|
160
|
+
})
|
161
|
+
continue
|
162
|
+
|
163
|
+
with yaspin(
|
164
|
+
text=f"正在处理文件 {file_path}...", color="cyan"
|
165
|
+
) as spinner:
|
166
|
+
# 首先尝试fast_edit模式
|
167
|
+
success, temp_content = EditFileHandler._fast_edit(file_path, changes, spinner)
|
168
|
+
if not success:
|
169
|
+
# 如果fast_edit失败,尝试slow_edit模式
|
170
|
+
success, temp_content = EditFileHandler._slow_edit(
|
171
|
+
file_path,
|
172
|
+
changes,
|
173
|
+
spinner,
|
174
|
+
agent
|
175
|
+
)
|
176
|
+
if not success:
|
177
|
+
spinner.text = f"文件 {file_path} 处理失败"
|
178
|
+
spinner.fail("❌")
|
179
|
+
file_results.append({
|
180
|
+
"file": file_path,
|
181
|
+
"success": False,
|
182
|
+
"stdout": "",
|
183
|
+
"stderr": temp_content
|
184
|
+
})
|
185
|
+
continue
|
186
|
+
else:
|
187
|
+
spinner.text = f"文件 {file_path} 内容生成完成"
|
188
|
+
spinner.ok("✅")
|
176
189
|
else:
|
177
190
|
spinner.text = f"文件 {file_path} 内容生成完成"
|
178
191
|
spinner.ok("✅")
|
179
|
-
else:
|
180
|
-
spinner.text = f"文件 {file_path} 内容生成完成"
|
181
|
-
spinner.ok("✅")
|
182
|
-
|
183
|
-
# 只有当所有替换操作都成功时,才写回文件
|
184
|
-
if success and (temp_content != original_content or not file_exists):
|
185
|
-
# 确保目录存在
|
186
|
-
os.makedirs(
|
187
|
-
os.path.dirname(os.path.abspath(file_path)), exist_ok=True
|
188
|
-
)
|
189
|
-
|
190
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
191
|
-
f.write(temp_content)
|
192
192
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
except Exception as e:
|
201
|
-
stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
|
202
|
-
stderr_messages.append(stderr_message)
|
203
|
-
PrettyOutput.print(stderr_message, OutputType.WARNING)
|
204
|
-
success = False
|
205
|
-
|
206
|
-
return {
|
207
|
-
"success": success,
|
208
|
-
"stdout": "\n".join(stdout_messages) if success else "",
|
209
|
-
"stderr": "\n".join(stderr_messages) if not success else "",
|
210
|
-
}
|
211
|
-
|
212
|
-
except Exception as e:
|
213
|
-
error_msg = f"文件搜索替换操作失败: {str(e)}"
|
214
|
-
PrettyOutput.print(error_msg, OutputType.WARNING)
|
215
|
-
|
216
|
-
# 如果有已修改的文件,尝试回滚
|
217
|
-
if processed:
|
218
|
-
rollback_message = "操作失败,正在回滚修改..."
|
219
|
-
stderr_messages.append(rollback_message)
|
220
|
-
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
193
|
+
# 只有当所有替换操作都成功时,才写回文件
|
194
|
+
if success and (temp_content != original_content or not file_exists):
|
195
|
+
# 确保目录存在
|
196
|
+
os.makedirs(
|
197
|
+
os.path.dirname(os.path.abspath(file_path)), exist_ok=True
|
198
|
+
)
|
221
199
|
|
222
|
-
try:
|
223
|
-
if original_content is None:
|
224
|
-
# 如果是新创建的文件,则删除
|
225
|
-
if os.path.exists(file_path):
|
226
|
-
os.remove(file_path)
|
227
|
-
stderr_messages.append(f"已删除新创建的文件: {file_path}")
|
228
|
-
else:
|
229
|
-
# 如果是修改的文件,则恢复原内容
|
230
200
|
with open(file_path, "w", encoding="utf-8") as f:
|
231
|
-
f.write(
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
spinner: Yaspin实例,用于显示处理状态
|
260
|
-
|
261
|
-
返回值:
|
262
|
-
Tuple[bool, str]:
|
263
|
-
- 第一个元素表示操作是否成功(True/False)
|
264
|
-
- 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
|
265
|
-
|
266
|
-
异常处理:
|
267
|
-
1. 文件不存在或权限不足时会捕获异常并返回失败
|
268
|
-
2. 模型生成补丁失败时会自动重试最多3次
|
269
|
-
3. 补丁应用失败时会自动回滚文件修改
|
270
|
-
|
271
|
-
实现细节:
|
272
|
-
1. 检查文件是否在工作目录下(影响版本控制)
|
273
|
-
2. 根据文件大小决定是否上传到模型平台
|
274
|
-
3. 使用精确的DIFF格式解析模型生成的补丁
|
275
|
-
4. 确保补丁应用前进行唯一性匹配检查
|
276
|
-
"""
|
277
|
-
import os
|
278
|
-
|
279
|
-
work_dir = os.path.abspath(os.curdir)
|
280
|
-
filepath = os.path.abspath(filepath)
|
281
|
-
if not filepath.startswith(work_dir):
|
282
|
-
PrettyOutput.print(
|
283
|
-
f"文件 {filepath} 不在工作目录 {work_dir} 下,不会进行版本控制管理",
|
284
|
-
OutputType.WARNING,
|
285
|
-
)
|
286
|
-
model = PlatformRegistry().get_normal_platform()
|
287
|
-
try:
|
288
|
-
file_content = FileOperationTool().execute(
|
289
|
-
{"operation": "read", "files": [{"path": filepath}]}
|
290
|
-
)["stdout"]
|
291
|
-
is_large_context = is_context_overflow(file_content)
|
292
|
-
upload_success = False
|
293
|
-
# 读取原始文件内容
|
294
|
-
with spinner.hidden():
|
295
|
-
if (
|
296
|
-
is_large_context
|
297
|
-
and model.support_upload_files()
|
298
|
-
and model.upload_files([filepath])
|
299
|
-
):
|
300
|
-
upload_success = True
|
301
|
-
|
302
|
-
model.set_suppress_output(True)
|
303
|
-
|
304
|
-
main_prompt = f"""
|
305
|
-
# 代码补丁生成专家指南
|
306
|
-
|
307
|
-
## 任务描述
|
308
|
-
你是一位精确的代码补丁生成专家,需要根据补丁描述生成精确的代码差异。
|
309
|
-
|
310
|
-
### 补丁内容
|
311
|
-
```
|
312
|
-
{patch_content}
|
313
|
-
```
|
314
|
-
|
315
|
-
## 补丁生成要求
|
316
|
-
1. **精确性**:严格按照补丁的意图修改代码
|
317
|
-
2. **格式一致性**:严格保持原始代码的格式风格,如果补丁中缩进或者空行与原代码不一致,则需要修正补丁中的缩进或者空行
|
318
|
-
3. **最小化修改**:只修改必要的代码部分,保持其他部分不变
|
319
|
-
4. **上下文完整性**:提供足够的上下文,确保补丁能准确应用
|
320
|
-
|
321
|
-
## 输出格式规范
|
322
|
-
- 使用{ot("DIFF")}块包围每个需要修改的代码段
|
323
|
-
- 每个{ot("DIFF")}块必须包含SEARCH部分和REPLACE部分
|
324
|
-
- SEARCH部分是需要查找的原始代码
|
325
|
-
- REPLACE部分是替换后的新代码
|
326
|
-
- 确保SEARCH部分能在原文件中**唯一匹配**
|
327
|
-
- 如果修改较大,可以使用多个{ot("DIFF")}块
|
328
|
-
|
329
|
-
## 输出模板
|
330
|
-
{ot("DIFF")}
|
331
|
-
{">" * 5} SEARCH
|
332
|
-
[需要查找的原始代码,包含足够上下文,避免出现可匹配多处的情况]
|
333
|
-
{'='*5}
|
334
|
-
[替换后的新代码]
|
335
|
-
{"<" * 5} REPLACE
|
336
|
-
{ct("DIFF")}
|
337
|
-
|
338
|
-
{ot("DIFF")}
|
339
|
-
{">" * 5} SEARCH
|
340
|
-
[另一处需要查找的原始代码,包含足够上下文,避免出现可匹配多处的情况]
|
341
|
-
{'='*5}
|
342
|
-
[另一处替换后的新代码]
|
343
|
-
{"<" * 5} REPLACE
|
344
|
-
{ct("DIFF")}
|
345
|
-
"""
|
201
|
+
f.write(temp_content)
|
202
|
+
|
203
|
+
processed = True
|
204
|
+
|
205
|
+
action = "创建并写入" if not file_exists else "成功修改"
|
206
|
+
stdout_message = f"文件 {file_path} {action} 完成"
|
207
|
+
stdout_messages.append(stdout_message)
|
208
|
+
PrettyOutput.print(stdout_message, OutputType.SUCCESS)
|
209
|
+
overall_success = True
|
210
|
+
|
211
|
+
file_results.append({
|
212
|
+
"file": file_path,
|
213
|
+
"success": True,
|
214
|
+
"stdout": stdout_message,
|
215
|
+
"stderr": ""
|
216
|
+
})
|
217
|
+
|
218
|
+
except Exception as e:
|
219
|
+
stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
|
220
|
+
stderr_messages.append(stderr_message)
|
221
|
+
PrettyOutput.print(stderr_message, OutputType.WARNING)
|
222
|
+
file_success = False
|
223
|
+
file_results.append({
|
224
|
+
"file": file_path,
|
225
|
+
"success": False,
|
226
|
+
"stdout": "",
|
227
|
+
"stderr": stderr_message
|
228
|
+
})
|
346
229
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
#
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
except Exception as e:
|
390
|
-
spinner.text = f"文件修改失败: {str(e)}"
|
391
|
-
spinner.fail("❌")
|
392
|
-
return False, f"文件修改失败: {str(e)}"
|
393
|
-
|
394
|
-
|
395
|
-
def fast_edit(
|
396
|
-
filepath: str, patches: List[Dict[str, str]], spinner: Yaspin
|
397
|
-
) -> Tuple[bool, str]:
|
398
|
-
"""快速应用预先生成的补丁到目标文件。
|
399
|
-
|
400
|
-
核心功能:
|
401
|
-
1. 直接应用已生成的代码补丁
|
402
|
-
2. 执行严格的唯一匹配检查
|
403
|
-
3. 提供详细的补丁应用状态反馈
|
404
|
-
4. 失败时自动回滚文件修改
|
405
|
-
|
406
|
-
参数:
|
407
|
-
filepath: 要编辑的文件路径(绝对或相对路径)
|
408
|
-
patches: 补丁列表,每个补丁包含:
|
409
|
-
- search: 需要查找的原始代码
|
410
|
-
- replace: 替换后的新代码
|
411
|
-
spinner: Yaspin实例,用于显示处理状态
|
412
|
-
|
413
|
-
返回值:
|
414
|
-
Tuple[bool, str]:
|
415
|
-
- 第一个元素表示操作是否成功(True/False)
|
416
|
-
- 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
|
417
|
-
|
418
|
-
异常处理:
|
419
|
-
1. 文件不存在或权限不足时会捕获异常并返回失败
|
420
|
-
2. 补丁不匹配或有多处匹配时会返回失败
|
421
|
-
3. 失败时会自动回滚文件修改
|
422
|
-
|
423
|
-
实现细节:
|
424
|
-
1. 读取文件内容到内存
|
425
|
-
2. 依次应用每个补丁,检查唯一匹配性
|
426
|
-
3. 记录每个补丁的应用状态
|
427
|
-
4. 所有补丁成功应用后才写入文件
|
428
|
-
"""
|
429
|
-
# 读取原始文件内容
|
430
|
-
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
431
|
-
file_content = f.read()
|
432
|
-
|
433
|
-
# 应用所有差异化补丁
|
434
|
-
modified_content = file_content
|
435
|
-
patch_count = 0
|
436
|
-
success = True
|
437
|
-
err_msg = ""
|
438
|
-
for patch in patches:
|
439
|
-
search_text = patch["search"]
|
440
|
-
replace_text = patch["replace"]
|
441
|
-
patch_count += 1
|
442
|
-
# 检查搜索文本是否存在于文件中
|
443
|
-
if search_text in modified_content:
|
444
|
-
# 如果有多处,报错
|
445
|
-
if modified_content.count(search_text) > 1:
|
446
|
-
success = False
|
447
|
-
err_msg = f"搜索文本 ```\n{search_text}\n``` 在文件中存在多处,请检查补丁内容"
|
448
|
-
break
|
449
|
-
# 应用替换
|
450
|
-
modified_content = modified_content.replace(search_text, replace_text)
|
451
|
-
spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
|
452
|
-
else:
|
453
|
-
# 尝试增加缩进重试
|
454
|
-
found = False
|
455
|
-
for space_count in range(1, 17):
|
456
|
-
# 跳过空行不增加空格
|
457
|
-
indented_search = "\n".join(
|
458
|
-
" " * space_count + line if line.strip() else line
|
459
|
-
for line in search_text.split("\n")
|
460
|
-
)
|
461
|
-
indented_replace = "\n".join(
|
462
|
-
" " * space_count + line if line.strip() else line
|
463
|
-
for line in replace_text.split("\n")
|
464
|
-
)
|
465
|
-
if indented_search in modified_content:
|
466
|
-
if modified_content.count(indented_search) > 1:
|
467
|
-
success = False
|
468
|
-
err_msg = f"搜索文本 ```\n{indented_search}\n``` 在文件中存在多处,请检查补丁内容"
|
469
|
-
break
|
470
|
-
modified_content = modified_content.replace(
|
471
|
-
indented_search, indented_replace
|
472
|
-
)
|
473
|
-
spinner.write(
|
474
|
-
f"✅ 补丁 #{patch_count} 应用成功 (自动增加 {space_count} 个空格缩进)"
|
475
|
-
)
|
476
|
-
found = True
|
477
|
-
break
|
478
|
-
|
479
|
-
if not found:
|
480
|
-
success = False
|
481
|
-
err_msg = f"搜索文本 ```\n{search_text}\n``` 在文件中不存在,尝试增加1-16个空格缩进后仍未找到匹配"
|
482
|
-
break
|
483
|
-
if not success:
|
484
|
-
revert_file(filepath)
|
485
|
-
return False, err_msg
|
486
|
-
|
487
|
-
spinner.text = f"文件 {filepath} 修改完成,应用了 {patch_count} 个补丁"
|
488
|
-
spinner.ok("✅")
|
489
|
-
return True, modified_content
|
230
|
+
except Exception as e:
|
231
|
+
error_msg = f"文件搜索替换操作失败: {str(e)}"
|
232
|
+
PrettyOutput.print(error_msg, OutputType.WARNING)
|
233
|
+
|
234
|
+
# 如果有已修改的文件,尝试回滚
|
235
|
+
if processed:
|
236
|
+
rollback_message = "操作失败,正在回滚修改..."
|
237
|
+
stderr_messages.append(rollback_message)
|
238
|
+
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
239
|
+
|
240
|
+
try:
|
241
|
+
if original_content is None:
|
242
|
+
# 如果是新创建的文件,则删除
|
243
|
+
if os.path.exists(file_path):
|
244
|
+
os.remove(file_path)
|
245
|
+
stderr_messages.append(f"已删除新创建的文件: {file_path}")
|
246
|
+
else:
|
247
|
+
# 如果是修改的文件,则恢复原内容
|
248
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
249
|
+
f.write(original_content)
|
250
|
+
stderr_messages.append(f"已回滚文件: {file_path}")
|
251
|
+
except:
|
252
|
+
stderr_messages.append(f"回滚文件失败: {file_path}")
|
253
|
+
|
254
|
+
file_results.append({
|
255
|
+
"file": file_path,
|
256
|
+
"success": False,
|
257
|
+
"stdout": "",
|
258
|
+
"stderr": error_msg
|
259
|
+
})
|
260
|
+
|
261
|
+
# 整合所有错误信息到stderr
|
262
|
+
all_stderr = []
|
263
|
+
for result in file_results:
|
264
|
+
if not result["success"]:
|
265
|
+
all_stderr.append(f"文件 {result['file']} 处理失败: {result['stderr']}")
|
266
|
+
|
267
|
+
return {
|
268
|
+
"success": overall_success,
|
269
|
+
"stdout": "\n".join(stdout_messages) if overall_success else "",
|
270
|
+
"stderr": "\n".join(all_stderr) if not overall_success else ""
|
271
|
+
}
|
jarvis/jarvis_tools/registry.py
CHANGED
@@ -553,24 +553,24 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
553
553
|
for item in data:
|
554
554
|
try:
|
555
555
|
msg = yaml.safe_load(item)
|
556
|
-
if "name" in msg and "arguments" in msg and "want" in msg:
|
557
|
-
ret.append(msg)
|
558
|
-
else:
|
559
|
-
return (
|
560
|
-
{},
|
561
|
-
f"""工具调用格式错误,请检查工具调用格式(缺少name、arguments、want字段)。
|
562
|
-
|
563
|
-
{tool_call_help}""",
|
564
|
-
)
|
565
556
|
except Exception as e:
|
566
557
|
return (
|
567
558
|
{},
|
568
|
-
f"""
|
569
|
-
|
559
|
+
f"""yaml 解析失败,请检查工具调用格式。
|
570
560
|
{e}
|
571
561
|
|
572
562
|
{tool_call_help}""",
|
573
563
|
)
|
564
|
+
|
565
|
+
if "name" in msg and "arguments" in msg and "want" in msg:
|
566
|
+
ret.append(msg)
|
567
|
+
else:
|
568
|
+
return (
|
569
|
+
{},
|
570
|
+
f"""工具调用格式错误,请检查工具调用格式(缺少name、arguments、want字段)。
|
571
|
+
|
572
|
+
{tool_call_help}""",
|
573
|
+
)
|
574
574
|
if len(ret) > 1:
|
575
575
|
return {}, "检测到多个工具调用,请一次只处理一个工具调用。"
|
576
576
|
return ret[0] if ret else {}, ""
|