jarvis-ai-assistant 0.1.193__py3-none-any.whl → 0.1.195__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 +45 -41
- jarvis/jarvis_agent/builtin_input_handler.py +26 -4
- jarvis/jarvis_agent/jarvis.py +30 -19
- jarvis/jarvis_agent/main.py +20 -12
- jarvis/jarvis_agent/output_handler.py +7 -7
- jarvis/jarvis_agent/shell_input_handler.py +14 -11
- jarvis/jarvis_code_agent/code_agent.py +81 -79
- jarvis/jarvis_code_agent/lint.py +92 -105
- jarvis/jarvis_code_analysis/checklists/__init__.py +1 -1
- jarvis/jarvis_code_analysis/checklists/c_cpp.py +1 -1
- jarvis/jarvis_code_analysis/checklists/csharp.py +1 -1
- jarvis/jarvis_code_analysis/checklists/data_format.py +1 -1
- jarvis/jarvis_code_analysis/checklists/devops.py +1 -1
- jarvis/jarvis_code_analysis/checklists/docs.py +1 -1
- jarvis/jarvis_code_analysis/checklists/go.py +1 -1
- jarvis/jarvis_code_analysis/checklists/infrastructure.py +1 -1
- jarvis/jarvis_code_analysis/checklists/java.py +1 -1
- jarvis/jarvis_code_analysis/checklists/javascript.py +1 -1
- jarvis/jarvis_code_analysis/checklists/kotlin.py +1 -1
- jarvis/jarvis_code_analysis/checklists/loader.py +31 -29
- jarvis/jarvis_code_analysis/checklists/php.py +1 -1
- jarvis/jarvis_code_analysis/checklists/python.py +1 -1
- jarvis/jarvis_code_analysis/checklists/ruby.py +1 -1
- jarvis/jarvis_code_analysis/checklists/rust.py +1 -1
- jarvis/jarvis_code_analysis/checklists/shell.py +1 -1
- jarvis/jarvis_code_analysis/checklists/sql.py +1 -1
- jarvis/jarvis_code_analysis/checklists/swift.py +1 -1
- jarvis/jarvis_code_analysis/checklists/web.py +1 -1
- jarvis/jarvis_code_analysis/code_review.py +292 -190
- jarvis/jarvis_dev/main.py +73 -56
- jarvis/jarvis_git_details/main.py +29 -33
- jarvis/jarvis_git_squash/main.py +13 -11
- jarvis/jarvis_git_utils/git_commiter.py +15 -5
- jarvis/jarvis_mcp/__init__.py +8 -10
- jarvis/jarvis_mcp/sse_mcp_client.py +182 -205
- jarvis/jarvis_mcp/stdio_mcp_client.py +93 -120
- jarvis/jarvis_mcp/streamable_mcp_client.py +117 -142
- jarvis/jarvis_methodology/main.py +71 -39
- jarvis/jarvis_multi_agent/__init__.py +24 -16
- jarvis/jarvis_multi_agent/main.py +10 -4
- jarvis/jarvis_platform/__init__.py +1 -1
- jarvis/jarvis_platform/base.py +44 -18
- jarvis/jarvis_platform/human.py +15 -3
- jarvis/jarvis_platform/kimi.py +117 -81
- jarvis/jarvis_platform/openai.py +23 -28
- jarvis/jarvis_platform/registry.py +43 -29
- jarvis/jarvis_platform/tongyi.py +16 -10
- jarvis/jarvis_platform/yuanbao.py +197 -144
- jarvis/jarvis_platform_manager/main.py +4 -2
- jarvis/jarvis_smart_shell/main.py +35 -30
- jarvis/jarvis_tools/ask_user.py +8 -16
- jarvis/jarvis_tools/base.py +3 -2
- jarvis/jarvis_tools/chdir.py +7 -19
- jarvis/jarvis_tools/cli/main.py +14 -10
- jarvis/jarvis_tools/code_plan.py +10 -31
- jarvis/jarvis_tools/create_code_agent.py +6 -11
- jarvis/jarvis_tools/create_sub_agent.py +10 -22
- jarvis/jarvis_tools/edit_file.py +98 -76
- jarvis/jarvis_tools/execute_script.py +46 -46
- jarvis/jarvis_tools/file_analyzer.py +22 -34
- jarvis/jarvis_tools/file_operation.py +69 -62
- jarvis/jarvis_tools/generate_new_tool.py +0 -2
- jarvis/jarvis_tools/methodology.py +19 -23
- jarvis/jarvis_tools/read_code.py +35 -35
- jarvis/jarvis_tools/read_webpage.py +7 -16
- jarvis/jarvis_tools/registry.py +63 -30
- jarvis/jarvis_tools/rewrite_file.py +26 -29
- jarvis/jarvis_tools/search_web.py +5 -8
- jarvis/jarvis_tools/virtual_tty.py +133 -122
- jarvis/jarvis_utils/__init__.py +0 -1
- jarvis/jarvis_utils/builtin_replace_map.py +9 -9
- jarvis/jarvis_utils/config.py +60 -37
- jarvis/jarvis_utils/embedding.py +24 -19
- jarvis/jarvis_utils/file_processors.py +16 -9
- jarvis/jarvis_utils/git_utils.py +157 -107
- jarvis/jarvis_utils/globals.py +1 -1
- jarvis/jarvis_utils/input.py +85 -52
- jarvis/jarvis_utils/jarvis_history.py +43 -0
- jarvis/jarvis_utils/methodology.py +31 -24
- jarvis/jarvis_utils/output.py +164 -80
- jarvis/jarvis_utils/tag.py +2 -1
- jarvis/jarvis_utils/utils.py +84 -52
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/METADATA +362 -230
- jarvis_ai_assistant-0.1.195.dist-info/RECORD +98 -0
- jarvis/jarvis_agent/file_input_handler.py +0 -112
- jarvis/jarvis_event/__init__.py +0 -0
- jarvis_ai_assistant-0.1.193.dist-info/RECORD +0 -99
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/top_level.txt +0 -0
jarvis/jarvis_tools/edit_file.py
CHANGED
@@ -58,33 +58,24 @@ class FileSearchReplaceTool:
|
|
58
58
|
parameters = {
|
59
59
|
"type": "object",
|
60
60
|
"properties": {
|
61
|
-
"file": {
|
62
|
-
"type": "string",
|
63
|
-
"description": "需要修改的文件路径"
|
64
|
-
},
|
61
|
+
"file": {"type": "string", "description": "需要修改的文件路径"},
|
65
62
|
"changes": {
|
66
63
|
"type": "array",
|
67
64
|
"description": "一组或多组修改,每个修改必须包含1-2行上下文用于精确定位",
|
68
65
|
"items": {
|
69
66
|
"type": "object",
|
70
67
|
"properties": {
|
71
|
-
"reason": {
|
72
|
-
"type": "string",
|
73
|
-
"description": "修改的原因"
|
74
|
-
},
|
68
|
+
"reason": {"type": "string", "description": "修改的原因"},
|
75
69
|
"search": {
|
76
70
|
"type": "string",
|
77
|
-
"description": "需要查找的原始代码"
|
71
|
+
"description": "需要查找的原始代码",
|
78
72
|
},
|
79
|
-
"replace": {
|
80
|
-
"type": "string",
|
81
|
-
"description": "替换后的新代码"
|
82
|
-
}
|
73
|
+
"replace": {"type": "string", "description": "替换后的新代码"},
|
83
74
|
},
|
84
|
-
}
|
85
|
-
}
|
75
|
+
},
|
76
|
+
},
|
86
77
|
},
|
87
|
-
"required": ["file", "changes"]
|
78
|
+
"required": ["file", "changes"],
|
88
79
|
}
|
89
80
|
|
90
81
|
def __init__(self):
|
@@ -131,15 +122,15 @@ class FileSearchReplaceTool:
|
|
131
122
|
import os
|
132
123
|
|
133
124
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
134
|
-
|
125
|
+
|
135
126
|
stdout_messages = []
|
136
127
|
stderr_messages = []
|
137
128
|
success = True
|
138
|
-
|
129
|
+
|
139
130
|
file_path = os.path.abspath(args["file"])
|
140
131
|
changes = args["changes"]
|
141
132
|
agent = args.get("agent", None)
|
142
|
-
|
133
|
+
|
143
134
|
# 创建已处理文件变量,用于失败时回滚
|
144
135
|
original_content = None
|
145
136
|
processed = False
|
@@ -147,11 +138,11 @@ class FileSearchReplaceTool:
|
|
147
138
|
try:
|
148
139
|
file_exists = os.path.exists(file_path)
|
149
140
|
content = ""
|
150
|
-
|
141
|
+
|
151
142
|
try:
|
152
143
|
# 如果文件存在,则读取内容
|
153
144
|
if file_exists:
|
154
|
-
with open(file_path,
|
145
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
155
146
|
content = f.read()
|
156
147
|
original_content = content
|
157
148
|
|
@@ -161,21 +152,26 @@ class FileSearchReplaceTool:
|
|
161
152
|
return {
|
162
153
|
"success": False,
|
163
154
|
"stdout": "",
|
164
|
-
"stderr": f"请先读取文件 {file_path} 的内容后再编辑"
|
155
|
+
"stderr": f"请先读取文件 {file_path} 的内容后再编辑",
|
165
156
|
}
|
166
157
|
|
167
|
-
|
168
|
-
|
158
|
+
with yaspin(
|
159
|
+
text=f"正在处理文件 {file_path}...", color="cyan"
|
160
|
+
) as spinner:
|
169
161
|
success, temp_content = fast_edit(file_path, changes, spinner)
|
170
162
|
if not success:
|
171
|
-
success, temp_content = slow_edit(
|
163
|
+
success, temp_content = slow_edit(
|
164
|
+
file_path,
|
165
|
+
yaml.safe_dump(changes, allow_unicode=True),
|
166
|
+
spinner,
|
167
|
+
)
|
172
168
|
if not success:
|
173
169
|
spinner.text = f"文件 {file_path} 处理失败"
|
174
170
|
spinner.fail("❌")
|
175
171
|
return {
|
176
172
|
"success": False,
|
177
173
|
"stdout": "",
|
178
|
-
"stderr": temp_content
|
174
|
+
"stderr": temp_content,
|
179
175
|
}
|
180
176
|
else:
|
181
177
|
spinner.text = f"文件 {file_path} 内容生成完成"
|
@@ -184,22 +180,23 @@ class FileSearchReplaceTool:
|
|
184
180
|
spinner.text = f"文件 {file_path} 内容生成完成"
|
185
181
|
spinner.ok("✅")
|
186
182
|
|
187
|
-
|
188
183
|
# 只有当所有替换操作都成功时,才写回文件
|
189
184
|
if success and (temp_content != original_content or not file_exists):
|
190
185
|
# 确保目录存在
|
191
|
-
os.makedirs(
|
192
|
-
|
193
|
-
|
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:
|
194
191
|
f.write(temp_content)
|
195
|
-
|
192
|
+
|
196
193
|
processed = True
|
197
|
-
|
194
|
+
|
198
195
|
action = "创建并写入" if not file_exists else "成功修改"
|
199
196
|
stdout_message = f"文件 {file_path} {action} 完成"
|
200
197
|
stdout_messages.append(stdout_message)
|
201
198
|
PrettyOutput.print(stdout_message, OutputType.SUCCESS)
|
202
|
-
|
199
|
+
|
203
200
|
except Exception as e:
|
204
201
|
stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
|
205
202
|
stderr_messages.append(stderr_message)
|
@@ -209,19 +206,19 @@ class FileSearchReplaceTool:
|
|
209
206
|
return {
|
210
207
|
"success": success,
|
211
208
|
"stdout": "\n".join(stdout_messages) if success else "",
|
212
|
-
"stderr": "\n".join(stderr_messages) if not success else ""
|
209
|
+
"stderr": "\n".join(stderr_messages) if not success else "",
|
213
210
|
}
|
214
|
-
|
211
|
+
|
215
212
|
except Exception as e:
|
216
213
|
error_msg = f"文件搜索替换操作失败: {str(e)}"
|
217
214
|
PrettyOutput.print(error_msg, OutputType.WARNING)
|
218
|
-
|
215
|
+
|
219
216
|
# 如果有已修改的文件,尝试回滚
|
220
217
|
if processed:
|
221
218
|
rollback_message = "操作失败,正在回滚修改..."
|
222
219
|
stderr_messages.append(rollback_message)
|
223
220
|
PrettyOutput.print(rollback_message, OutputType.WARNING)
|
224
|
-
|
221
|
+
|
225
222
|
try:
|
226
223
|
if original_content is None:
|
227
224
|
# 如果是新创建的文件,则删除
|
@@ -230,30 +227,29 @@ class FileSearchReplaceTool:
|
|
230
227
|
stderr_messages.append(f"已删除新创建的文件: {file_path}")
|
231
228
|
else:
|
232
229
|
# 如果是修改的文件,则恢复原内容
|
233
|
-
with open(file_path,
|
230
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
234
231
|
f.write(original_content)
|
235
232
|
stderr_messages.append(f"已回滚文件: {file_path}")
|
236
233
|
except:
|
237
234
|
stderr_messages.append(f"回滚文件失败: {file_path}")
|
238
|
-
|
235
|
+
|
239
236
|
return {
|
240
237
|
"success": False,
|
241
238
|
"stdout": "",
|
242
|
-
"stderr": error_msg + "\n" + "\n".join(stderr_messages)
|
239
|
+
"stderr": error_msg + "\n" + "\n".join(stderr_messages),
|
243
240
|
}
|
244
|
-
|
245
241
|
|
246
242
|
|
247
243
|
def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool, str]:
|
248
244
|
"""执行精确的文件编辑操作,使用AI模型生成差异补丁并应用。
|
249
|
-
|
245
|
+
|
250
246
|
核心功能:
|
251
247
|
1. 使用AI模型分析补丁内容并生成精确的代码差异
|
252
248
|
2. 应用生成的差异补丁到目标文件
|
253
249
|
3. 提供3次重试机制确保操作可靠性
|
254
250
|
4. 支持大文件处理(自动上传到模型平台)
|
255
251
|
5. 严格的格式一致性检查
|
256
|
-
|
252
|
+
|
257
253
|
参数:
|
258
254
|
filepath: 要编辑的文件路径(绝对或相对路径)
|
259
255
|
patch_content: YAML格式的补丁内容,包含:
|
@@ -261,17 +257,17 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
|
|
261
257
|
- search: 需要查找的原始代码(必须包含足够上下文)
|
262
258
|
- replace: 替换后的新代码
|
263
259
|
spinner: Yaspin实例,用于显示处理状态
|
264
|
-
|
260
|
+
|
265
261
|
返回值:
|
266
|
-
Tuple[bool, str]:
|
262
|
+
Tuple[bool, str]:
|
267
263
|
- 第一个元素表示操作是否成功(True/False)
|
268
264
|
- 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
|
269
|
-
|
265
|
+
|
270
266
|
异常处理:
|
271
267
|
1. 文件不存在或权限不足时会捕获异常并返回失败
|
272
268
|
2. 模型生成补丁失败时会自动重试最多3次
|
273
269
|
3. 补丁应用失败时会自动回滚文件修改
|
274
|
-
|
270
|
+
|
275
271
|
实现细节:
|
276
272
|
1. 检查文件是否在工作目录下(影响版本控制)
|
277
273
|
2. 根据文件大小决定是否上传到模型平台
|
@@ -279,21 +275,30 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
|
|
279
275
|
4. 确保补丁应用前进行唯一性匹配检查
|
280
276
|
"""
|
281
277
|
import os
|
278
|
+
|
282
279
|
work_dir = os.path.abspath(os.curdir)
|
283
280
|
filepath = os.path.abspath(filepath)
|
284
281
|
if not filepath.startswith(work_dir):
|
285
|
-
PrettyOutput.print(
|
282
|
+
PrettyOutput.print(
|
283
|
+
f"文件 {filepath} 不在工作目录 {work_dir} 下,不会进行版本控制管理",
|
284
|
+
OutputType.WARNING,
|
285
|
+
)
|
286
286
|
model = PlatformRegistry().get_normal_platform()
|
287
287
|
try:
|
288
|
-
file_content = FileOperationTool().execute(
|
288
|
+
file_content = FileOperationTool().execute(
|
289
|
+
{"operation": "read", "files": [{"path": filepath}]}
|
290
|
+
)["stdout"]
|
289
291
|
is_large_context = is_context_overflow(file_content)
|
290
292
|
upload_success = False
|
291
293
|
# 读取原始文件内容
|
292
|
-
with spinner.hidden():
|
293
|
-
if
|
294
|
+
with spinner.hidden():
|
295
|
+
if (
|
296
|
+
is_large_context
|
297
|
+
and model.support_upload_files()
|
298
|
+
and model.upload_files([filepath])
|
299
|
+
):
|
294
300
|
upload_success = True
|
295
301
|
|
296
|
-
|
297
302
|
model.set_suppress_output(True)
|
298
303
|
|
299
304
|
main_prompt = f"""
|
@@ -338,7 +343,7 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
|
|
338
343
|
{"<" * 5} REPLACE
|
339
344
|
{ct("DIFF")}
|
340
345
|
"""
|
341
|
-
|
346
|
+
|
342
347
|
for _ in range(3):
|
343
348
|
if is_large_context:
|
344
349
|
if upload_success:
|
@@ -357,15 +362,22 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
|
|
357
362
|
response = model.chat_until_success(main_prompt + file_prompt)
|
358
363
|
|
359
364
|
# 解析差异化补丁
|
360
|
-
diff_blocks = re.finditer(
|
361
|
-
|
365
|
+
diff_blocks = re.finditer(
|
366
|
+
ot("DIFF")
|
367
|
+
+ r"\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?"
|
368
|
+
+ ct("DIFF"),
|
369
|
+
response,
|
370
|
+
re.DOTALL,
|
371
|
+
)
|
362
372
|
|
363
373
|
patches = []
|
364
374
|
for match in diff_blocks:
|
365
|
-
patches.append(
|
366
|
-
|
367
|
-
|
368
|
-
|
375
|
+
patches.append(
|
376
|
+
{
|
377
|
+
"search": match.group(1).strip(),
|
378
|
+
"replace": match.group(2).strip(),
|
379
|
+
}
|
380
|
+
)
|
369
381
|
|
370
382
|
success, modified_content_or_err = fast_edit(filepath, patches, spinner)
|
371
383
|
if success:
|
@@ -380,32 +392,34 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
|
|
380
392
|
return False, f"文件修改失败: {str(e)}"
|
381
393
|
|
382
394
|
|
383
|
-
def fast_edit(
|
395
|
+
def fast_edit(
|
396
|
+
filepath: str, patches: List[Dict[str, str]], spinner: Yaspin
|
397
|
+
) -> Tuple[bool, str]:
|
384
398
|
"""快速应用预先生成的补丁到目标文件。
|
385
|
-
|
399
|
+
|
386
400
|
核心功能:
|
387
401
|
1. 直接应用已生成的代码补丁
|
388
402
|
2. 执行严格的唯一匹配检查
|
389
403
|
3. 提供详细的补丁应用状态反馈
|
390
404
|
4. 失败时自动回滚文件修改
|
391
|
-
|
405
|
+
|
392
406
|
参数:
|
393
407
|
filepath: 要编辑的文件路径(绝对或相对路径)
|
394
408
|
patches: 补丁列表,每个补丁包含:
|
395
409
|
- search: 需要查找的原始代码
|
396
410
|
- replace: 替换后的新代码
|
397
411
|
spinner: Yaspin实例,用于显示处理状态
|
398
|
-
|
412
|
+
|
399
413
|
返回值:
|
400
|
-
Tuple[bool, str]:
|
414
|
+
Tuple[bool, str]:
|
401
415
|
- 第一个元素表示操作是否成功(True/False)
|
402
416
|
- 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
|
403
|
-
|
417
|
+
|
404
418
|
异常处理:
|
405
419
|
1. 文件不存在或权限不足时会捕获异常并返回失败
|
406
420
|
2. 补丁不匹配或有多处匹配时会返回失败
|
407
421
|
3. 失败时会自动回滚文件修改
|
408
|
-
|
422
|
+
|
409
423
|
实现细节:
|
410
424
|
1. 读取文件内容到内存
|
411
425
|
2. 依次应用每个补丁,检查唯一匹配性
|
@@ -413,7 +427,7 @@ def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> T
|
|
413
427
|
4. 所有补丁成功应用后才写入文件
|
414
428
|
"""
|
415
429
|
# 读取原始文件内容
|
416
|
-
with open(filepath,
|
430
|
+
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
417
431
|
file_content = f.read()
|
418
432
|
|
419
433
|
# 应用所有差异化补丁
|
@@ -433,26 +447,35 @@ def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> T
|
|
433
447
|
err_msg = f"搜索文本 {search_text} 在文件中存在多处,请检查补丁内容"
|
434
448
|
break
|
435
449
|
# 应用替换
|
436
|
-
modified_content = modified_content.replace(
|
437
|
-
search_text, replace_text)
|
450
|
+
modified_content = modified_content.replace(search_text, replace_text)
|
438
451
|
spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
|
439
452
|
else:
|
440
453
|
# 尝试增加缩进重试
|
441
454
|
found = False
|
442
455
|
for space_count in range(1, 17):
|
443
|
-
|
444
|
-
|
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
|
+
)
|
445
465
|
if indented_search in modified_content:
|
446
466
|
if modified_content.count(indented_search) > 1:
|
447
467
|
success = False
|
448
468
|
err_msg = f"搜索文本 {indented_search} 在文件中存在多处,请检查补丁内容"
|
449
469
|
break
|
450
470
|
modified_content = modified_content.replace(
|
451
|
-
indented_search, indented_replace
|
452
|
-
|
471
|
+
indented_search, indented_replace
|
472
|
+
)
|
473
|
+
spinner.write(
|
474
|
+
f"✅ 补丁 #{patch_count} 应用成功 (自动增加 {space_count} 个空格缩进)"
|
475
|
+
)
|
453
476
|
found = True
|
454
477
|
break
|
455
|
-
|
478
|
+
|
456
479
|
if not found:
|
457
480
|
success = False
|
458
481
|
err_msg = f"搜索文本 {search_text} 在文件中不存在,尝试增加1-16个空格缩进后仍未找到匹配"
|
@@ -461,7 +484,6 @@ def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> T
|
|
461
484
|
revert_file(filepath)
|
462
485
|
return False, err_msg
|
463
486
|
|
464
|
-
|
465
487
|
spinner.text = f"文件 {filepath} 修改完成,应用了 {patch_count} 个补丁"
|
466
488
|
spinner.ok("✅")
|
467
|
-
return True, modified_content
|
489
|
+
return True, modified_content
|
@@ -9,27 +9,27 @@ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
9
|
|
10
10
|
class ScriptTool:
|
11
11
|
"""Combined script execution tool
|
12
|
-
|
12
|
+
|
13
13
|
Executes scripts with any interpreter with a unified interface.
|
14
14
|
"""
|
15
|
+
|
15
16
|
name = "execute_script"
|
16
|
-
description =
|
17
|
-
|
18
|
-
+ "
|
17
|
+
description = (
|
18
|
+
"执行脚本并返回结果,支持任意解释器。"
|
19
|
+
+ "注意:由于模型上下文长度限制,请避免在脚本中输出大量信息,应该使用rg过滤输出。"
|
20
|
+
+ "与virtual_tty不同,此工具会创建一个临时的脚本文件,并使用脚本命令执行脚本,不具备交互式操作的能力,"
|
19
21
|
+ "适用于需要执行脚本并获取结果的场景。不适合需要交互式操作的场景(如:ssh连接、sftp传输、gdb/dlv调试等)。"
|
22
|
+
)
|
20
23
|
parameters = {
|
21
24
|
"type": "object",
|
22
25
|
"properties": {
|
23
26
|
"interpreter": {
|
24
27
|
"type": "string",
|
25
|
-
"description": "脚本解释器: 如bash, python3, expect, perl, ruby等任意解释器。如需直接执行shell命令, 可使用bash作为解释器"
|
28
|
+
"description": "脚本解释器: 如bash, python3, expect, perl, ruby等任意解释器。如需直接执行shell命令, 可使用bash作为解释器",
|
26
29
|
},
|
27
|
-
"script_content": {
|
28
|
-
"type": "string",
|
29
|
-
"description": "要执行的脚本内容"
|
30
|
-
}
|
30
|
+
"script_content": {"type": "string", "description": "要执行的脚本内容"},
|
31
31
|
},
|
32
|
-
"required": ["script_content"]
|
32
|
+
"required": ["script_content"],
|
33
33
|
}
|
34
34
|
|
35
35
|
# Map of common file extensions for interpreters (can be extended as needed)
|
@@ -63,69 +63,76 @@ class ScriptTool:
|
|
63
63
|
|
64
64
|
def get_display_output(self, file_path: str) -> str:
|
65
65
|
"""消除控制字符,得到用户实际看到的文本,去除script命令首尾行"""
|
66
|
-
import re
|
67
66
|
# 读取文件内容并尝试多种编码
|
68
|
-
with open(file_path,
|
67
|
+
with open(file_path, "rb") as f:
|
69
68
|
data = f.read()
|
70
69
|
|
71
70
|
import pyte
|
71
|
+
|
72
72
|
screen = pyte.Screen(300, 100000)
|
73
73
|
stream = pyte.ByteStream(screen)
|
74
74
|
stream.feed(data)
|
75
|
-
|
75
|
+
|
76
76
|
# 清理每行右侧空格,并过滤空行
|
77
77
|
cleaned = []
|
78
78
|
cleaned = []
|
79
79
|
for y in range(screen.lines):
|
80
80
|
line = screen.buffer[y]
|
81
|
-
stripped = "".join(
|
82
|
-
char.data for char in line.values()
|
83
|
-
).rstrip()
|
81
|
+
stripped = "".join(char.data for char in line.values()).rstrip()
|
84
82
|
if stripped:
|
85
83
|
cleaned.append(stripped)
|
86
84
|
return "\n".join(cleaned[1:-1])
|
87
85
|
|
88
|
-
def _execute_script_with_interpreter(
|
86
|
+
def _execute_script_with_interpreter(
|
87
|
+
self, interpreter: str, script_content: str
|
88
|
+
) -> Dict[str, Any]:
|
89
89
|
"""Execute a script with the specified interpreter
|
90
|
-
|
90
|
+
|
91
91
|
Args:
|
92
92
|
interpreter: The interpreter to use (any valid interpreter command)
|
93
93
|
script_content: Content of the script
|
94
|
-
|
94
|
+
|
95
95
|
Returns:
|
96
96
|
Dictionary with execution results
|
97
97
|
"""
|
98
98
|
try:
|
99
99
|
# Get file extension for the interpreter
|
100
100
|
extension = self.INTERPRETER_EXTENSIONS.get(interpreter, "script")
|
101
|
-
|
101
|
+
|
102
102
|
# Create temporary script file
|
103
|
-
script_path = os.path.join(
|
104
|
-
|
103
|
+
script_path = os.path.join(
|
104
|
+
tempfile.gettempdir(),
|
105
|
+
f"jarvis_{interpreter.replace('/', '_')}_{os.getpid()}.{extension}",
|
106
|
+
)
|
107
|
+
output_file = os.path.join(
|
108
|
+
tempfile.gettempdir(), f"jarvis_output_{os.getpid()}.log"
|
109
|
+
)
|
105
110
|
try:
|
106
|
-
with open(script_path,
|
111
|
+
with open(script_path, "w", encoding="utf-8", errors="ignore") as f:
|
107
112
|
f.write(script_content)
|
108
|
-
|
113
|
+
|
109
114
|
# Use script command to capture both stdout and stderr
|
110
|
-
tee_command =
|
111
|
-
|
115
|
+
tee_command = (
|
116
|
+
f"script -q -c '{interpreter} {script_path}' {output_file}"
|
117
|
+
)
|
118
|
+
|
112
119
|
# Execute command and capture return code
|
113
120
|
os.system(tee_command)
|
114
|
-
|
121
|
+
|
115
122
|
# Read and process output file
|
116
123
|
try:
|
117
124
|
# 消除控制字符,得到用户实际看到的文本
|
118
125
|
output = self.get_display_output(output_file)
|
119
126
|
except Exception as e:
|
120
127
|
output = f"读取输出文件失败: {str(e)}"
|
121
|
-
|
128
|
+
|
122
129
|
# Return successful result
|
123
130
|
return {
|
124
131
|
"success": True,
|
125
132
|
"stdout": output,
|
126
133
|
"stderr": "",
|
127
134
|
}
|
128
|
-
|
135
|
+
|
129
136
|
finally:
|
130
137
|
# Clean up temporary files
|
131
138
|
Path(script_path).unlink(missing_ok=True)
|
@@ -133,18 +140,14 @@ class ScriptTool:
|
|
133
140
|
|
134
141
|
except Exception as e:
|
135
142
|
PrettyOutput.print(str(e), OutputType.ERROR)
|
136
|
-
return {
|
137
|
-
"success": False,
|
138
|
-
"stdout": "",
|
139
|
-
"stderr": str(e)
|
140
|
-
}
|
143
|
+
return {"success": False, "stdout": "", "stderr": str(e)}
|
141
144
|
|
142
145
|
def execute(self, args: Dict) -> Dict[str, Any]:
|
143
146
|
"""Execute script based on interpreter and content
|
144
|
-
|
147
|
+
|
145
148
|
Args:
|
146
149
|
args: Dictionary containing interpreter (or script_type) and script_content
|
147
|
-
|
150
|
+
|
148
151
|
Returns:
|
149
152
|
Dictionary with execution results
|
150
153
|
"""
|
@@ -154,23 +157,20 @@ class ScriptTool:
|
|
154
157
|
return {
|
155
158
|
"success": False,
|
156
159
|
"stdout": "",
|
157
|
-
"stderr": "Missing or empty script_content parameter"
|
160
|
+
"stderr": "Missing or empty script_content parameter",
|
158
161
|
}
|
159
|
-
|
162
|
+
|
160
163
|
# Get interpreter, default to bash if not specified
|
161
164
|
interpreter = args.get("interpreter", "bash")
|
162
|
-
|
165
|
+
|
163
166
|
# Execute the script with the specified interpreter
|
164
167
|
return self._execute_script_with_interpreter(interpreter, script_content)
|
165
|
-
|
168
|
+
|
166
169
|
except Exception as e:
|
167
170
|
PrettyOutput.print(str(e), OutputType.ERROR)
|
168
|
-
return {
|
169
|
-
|
170
|
-
|
171
|
-
"stderr": str(e)
|
172
|
-
}
|
173
|
-
|
171
|
+
return {"success": False, "stdout": "", "stderr": str(e)}
|
172
|
+
|
173
|
+
|
174
174
|
if __name__ == "__main__":
|
175
175
|
script_tool = ScriptTool()
|
176
176
|
print(script_tool.get_display_output("/home/wangmaobin/code/Jarvis/a.txt"))
|