jarvis-ai-assistant 0.1.134__py3-none-any.whl → 0.1.138__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.

Files changed (78) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +201 -79
  3. jarvis/jarvis_agent/builtin_input_handler.py +16 -6
  4. jarvis/jarvis_agent/file_input_handler.py +9 -9
  5. jarvis/jarvis_agent/jarvis.py +10 -10
  6. jarvis/jarvis_agent/main.py +12 -11
  7. jarvis/jarvis_agent/output_handler.py +3 -3
  8. jarvis/jarvis_agent/patch.py +86 -62
  9. jarvis/jarvis_agent/shell_input_handler.py +5 -3
  10. jarvis/jarvis_code_agent/code_agent.py +134 -99
  11. jarvis/jarvis_code_agent/file_select.py +24 -24
  12. jarvis/jarvis_dev/main.py +45 -51
  13. jarvis/jarvis_git_details/__init__.py +0 -0
  14. jarvis/jarvis_git_details/main.py +179 -0
  15. jarvis/jarvis_git_squash/main.py +7 -7
  16. jarvis/jarvis_lsp/base.py +11 -11
  17. jarvis/jarvis_lsp/cpp.py +14 -14
  18. jarvis/jarvis_lsp/go.py +13 -13
  19. jarvis/jarvis_lsp/python.py +8 -8
  20. jarvis/jarvis_lsp/registry.py +21 -21
  21. jarvis/jarvis_lsp/rust.py +15 -15
  22. jarvis/jarvis_methodology/main.py +101 -0
  23. jarvis/jarvis_multi_agent/__init__.py +11 -11
  24. jarvis/jarvis_multi_agent/main.py +6 -6
  25. jarvis/jarvis_platform/__init__.py +1 -1
  26. jarvis/jarvis_platform/ai8.py +67 -89
  27. jarvis/jarvis_platform/base.py +14 -13
  28. jarvis/jarvis_platform/kimi.py +25 -28
  29. jarvis/jarvis_platform/ollama.py +24 -26
  30. jarvis/jarvis_platform/openai.py +15 -19
  31. jarvis/jarvis_platform/oyi.py +48 -50
  32. jarvis/jarvis_platform/registry.py +27 -28
  33. jarvis/jarvis_platform/yuanbao.py +38 -42
  34. jarvis/jarvis_platform_manager/main.py +81 -81
  35. jarvis/jarvis_platform_manager/openai_test.py +21 -21
  36. jarvis/jarvis_rag/file_processors.py +18 -18
  37. jarvis/jarvis_rag/main.py +261 -277
  38. jarvis/jarvis_smart_shell/main.py +12 -12
  39. jarvis/jarvis_tools/ask_codebase.py +28 -28
  40. jarvis/jarvis_tools/ask_user.py +8 -8
  41. jarvis/jarvis_tools/base.py +4 -4
  42. jarvis/jarvis_tools/chdir.py +9 -9
  43. jarvis/jarvis_tools/code_review.py +19 -19
  44. jarvis/jarvis_tools/create_code_agent.py +15 -15
  45. jarvis/jarvis_tools/execute_python_script.py +3 -3
  46. jarvis/jarvis_tools/execute_shell.py +11 -11
  47. jarvis/jarvis_tools/execute_shell_script.py +3 -3
  48. jarvis/jarvis_tools/file_analyzer.py +29 -29
  49. jarvis/jarvis_tools/file_operation.py +22 -20
  50. jarvis/jarvis_tools/find_caller.py +25 -25
  51. jarvis/jarvis_tools/find_methodolopy.py +65 -0
  52. jarvis/jarvis_tools/find_symbol.py +24 -24
  53. jarvis/jarvis_tools/function_analyzer.py +27 -27
  54. jarvis/jarvis_tools/git_commiter.py +9 -9
  55. jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -19
  56. jarvis/jarvis_tools/methodology.py +23 -62
  57. jarvis/jarvis_tools/project_analyzer.py +29 -33
  58. jarvis/jarvis_tools/rag.py +15 -15
  59. jarvis/jarvis_tools/read_code.py +24 -22
  60. jarvis/jarvis_tools/read_webpage.py +31 -31
  61. jarvis/jarvis_tools/registry.py +72 -52
  62. jarvis/jarvis_tools/tool_generator.py +18 -18
  63. jarvis/jarvis_utils/config.py +23 -23
  64. jarvis/jarvis_utils/embedding.py +83 -83
  65. jarvis/jarvis_utils/git_utils.py +20 -20
  66. jarvis/jarvis_utils/globals.py +18 -6
  67. jarvis/jarvis_utils/input.py +10 -9
  68. jarvis/jarvis_utils/methodology.py +140 -136
  69. jarvis/jarvis_utils/output.py +11 -11
  70. jarvis/jarvis_utils/utils.py +22 -70
  71. {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/METADATA +1 -1
  72. jarvis_ai_assistant-0.1.138.dist-info/RECORD +85 -0
  73. {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/entry_points.txt +2 -0
  74. jarvis/jarvis_tools/select_code_files.py +0 -62
  75. jarvis_ai_assistant-0.1.134.dist-info/RECORD +0 -82
  76. {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/LICENSE +0 -0
  77. {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/WHEEL +0 -0
  78. {jarvis_ai_assistant-0.1.134.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/top_level.txt +0 -0
@@ -10,17 +10,17 @@ from jarvis.jarvis_utils.utils import init_env
10
10
 
11
11
  def load_config(config_path: str) -> dict:
12
12
  """Load configuration from YAML file
13
-
13
+
14
14
  Args:
15
15
  config_path: Path to the YAML configuration file
16
-
16
+
17
17
  Returns:
18
18
  dict: Configuration dictionary
19
19
  """
20
20
  if not os.path.exists(config_path):
21
21
  PrettyOutput.print(f"配置文件 {config_path} 不存在,使用默认配置", OutputType.WARNING)
22
22
  return {}
23
-
23
+
24
24
  with open(config_path, 'r', encoding='utf-8', errors="ignore") as f:
25
25
  try:
26
26
  config = yaml.safe_load(f)
@@ -33,42 +33,43 @@ def main():
33
33
  """Main entry point for Jarvis agent"""
34
34
  # Initialize environment
35
35
  init_env()
36
-
36
+
37
37
  # Set up argument parser
38
38
  parser = argparse.ArgumentParser(description='Jarvis AI assistant')
39
- parser.add_argument('-c', '--config', type=str, required=True,
39
+ parser.add_argument('-c', '--config', type=str, required=True,
40
40
  help='Path to the YAML configuration file')
41
41
  parser.add_argument('-t', '--task', type=str,
42
42
  help='Initial task to execute')
43
43
  args = parser.parse_args()
44
-
44
+
45
45
  # Load configuration
46
46
  config = load_config(args.config)
47
-
47
+
48
48
  # Create and run agent
49
49
  try:
50
50
  agent = Agent(**config)
51
-
51
+
52
52
  # Run agent with initial task if specified
53
53
  if args.task:
54
54
  PrettyOutput.print(f"执行初始任务: {args.task}", OutputType.INFO)
55
55
  agent.run(args.task)
56
56
  return 0
57
-
57
+
58
58
  # Enter interactive mode if no initial task
59
59
  while True:
60
60
  try:
61
61
  user_input = get_multiline_input("请输入你的任务(输入空行退出):")
62
62
  if not user_input:
63
63
  break
64
+ agent.set_addon_prompt("如果有必要,请先指定出行动计划,然后根据计划一步步执行")
64
65
  agent.run(user_input)
65
66
  except Exception as e:
66
67
  PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
67
-
68
+
68
69
  except Exception as e:
69
70
  PrettyOutput.print(f"初始化错误: {str(e)}", OutputType.ERROR)
70
71
  return 1
71
-
72
+
72
73
  return 0
73
74
 
74
75
  if __name__ == "__main__":
@@ -7,13 +7,13 @@ from typing import Any, Tuple
7
7
 
8
8
  class OutputHandler(ABC):
9
9
  @abstractmethod
10
- def handle(self, response: str) -> Tuple[bool, Any]:
10
+ def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
11
11
  pass
12
-
12
+
13
13
  @abstractmethod
14
14
  def can_handle(self, response: str) -> bool:
15
15
  pass
16
-
16
+
17
17
  @abstractmethod
18
18
  def prompt(self) -> str:
19
19
  pass
@@ -11,21 +11,24 @@ from jarvis.jarvis_tools.git_commiter import GitCommitTool
11
11
  from jarvis.jarvis_tools.file_operation import FileOperationTool
12
12
  from jarvis.jarvis_utils.config import is_confirm_before_apply_patch
13
13
  from jarvis.jarvis_utils.git_utils import get_commits_between, get_latest_commit_hash
14
+ from jarvis.jarvis_utils.globals import add_read_file_record, has_read_file
14
15
  from jarvis.jarvis_utils.input import get_multiline_input
15
16
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
16
17
  from jarvis.jarvis_utils.utils import ct, ot, get_file_line_count, user_confirm
17
18
 
19
+
18
20
  class PatchOutputHandler(OutputHandler):
19
21
  def name(self) -> str:
20
22
  return "PATCH"
21
- def handle(self, response: str) -> Tuple[bool, Any]:
22
- return False, apply_patch(response)
23
-
23
+
24
+ def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
25
+ return False, apply_patch(response, agent)
26
+
24
27
  def can_handle(self, response: str) -> bool:
25
28
  if _parse_patch(response):
26
29
  return True
27
30
  return False
28
-
31
+
29
32
  def prompt(self) -> str:
30
33
  return f"""
31
34
  # 代码补丁规范
@@ -44,18 +47,17 @@ Reason: [修改原因]
44
47
  ```
45
48
 
46
49
  ## 核心原则
47
- 1. **上下文完整性**:代码片段必须包含足够的上下文(修改前后各3行)
48
- 2. **精准修改**:只显示需要修改的代码部分,不需要展示整个文件内容
49
- 3. **最小补丁原则**:始终生成最小范围的补丁,只包含必要的上下文和实际修改
50
- 4. **格式严格保持**:
50
+ 1. **精准修改**:只显示需要修改的代码部分,不需要展示整个文件内容
51
+ 2. **最小补丁原则**:始终生成最小范围的补丁,只包含必要的上下文和实际修改
52
+ 3. **格式严格保持**:
51
53
  - 严格保持原始代码的缩进方式(空格或制表符)
52
54
  - 保持原始代码的空行数量和位置
53
55
  - 保持原始代码的行尾空格处理方式
54
56
  - 不改变原始代码的换行风格
55
- 5. **新旧区分**:
57
+ 4. **新旧区分**:
56
58
  - 对于新文件:提供完整的代码内容
57
- - 对于现有文件:只提供修改部分及必要上下文,不要提供整个文件
58
- 6. **理由说明**:每个补丁必须包含清晰的修改理由,解释为什么需要此更改
59
+ - 对于现有文件:只提供修改部分,不要提供整个文件
60
+ 5. **理由说明**:每个补丁必须包含清晰的修改理由,解释为什么需要此更改
59
61
 
60
62
  ## 格式兼容性要求
61
63
  1. **缩进一致性**:
@@ -86,17 +88,21 @@ def add(a, b):
86
88
 
87
89
  ## 最佳实践
88
90
  - 每个补丁专注于单一职责的修改
89
- - 提供足够的上下文,但避免包含过多无关代码
91
+ - 避免包含过多无关代码
90
92
  - 确保修改理由清晰明确,便于理解变更目的
91
93
  - 保持代码风格一致性,遵循项目现有的编码规范
92
94
  - 在修改前仔细分析原代码的格式风格,确保补丁与之完全兼容
93
95
  - 绝不提供完整文件内容,除非是新建文件
96
+ - 每个文件的修改是独立的,不能出现“参照xxx文件的修改”这样的描述
97
+ - 不要出现未实现的代码,如:TODO
94
98
  """
95
99
 
100
+
96
101
  def _parse_patch(patch_str: str) -> Dict[str, str]:
97
102
  """解析新的上下文补丁格式"""
98
103
  result = {}
99
- patches = re.findall(ot("PATCH")+r'\n?(.*?)\n?'+ct("PATCH"), patch_str, re.DOTALL)
104
+ patches = re.findall(ot("PATCH")+r'\n?(.*?)\n?' +
105
+ ct("PATCH"), patch_str, re.DOTALL)
100
106
  if patches:
101
107
  for patch in patches:
102
108
  first_line = patch.splitlines()[0]
@@ -111,7 +117,8 @@ def _parse_patch(patch_str: str) -> Dict[str, str]:
111
117
  result[filepath] += "\n\n" + patch
112
118
  return result
113
119
 
114
- def apply_patch(output_str: str) -> str:
120
+
121
+ def apply_patch(output_str: str, agent: Any) -> str:
115
122
  """Apply patches to files"""
116
123
  with yaspin(text="正在应用补丁...", color="cyan") as spinner:
117
124
  try:
@@ -119,12 +126,17 @@ def apply_patch(output_str: str) -> str:
119
126
  except Exception as e:
120
127
  PrettyOutput.print(f"解析补丁失败: {str(e)}", OutputType.ERROR)
121
128
  return ""
122
-
129
+
123
130
  # 获取当前提交hash作为起始点
124
- spinner.text= "开始获取当前提交hash..."
131
+ spinner.text = "开始获取当前提交hash..."
125
132
  start_hash = get_latest_commit_hash()
126
133
  spinner.write("✅ 当前提交hash获取完成")
127
-
134
+
135
+ not_read_file = [f for f in patches.keys() if not has_read_file(f)]
136
+ if not_read_file:
137
+ yaspin.write(f"❌ 以下文件未读取: {not_read_file},应用补丁存在风险,将先读取文件后再生成补丁", color="red")
138
+ return f"以下文件未读取: {not_read_file},应用补丁存在风险,请先读取文件后再生成补丁"
139
+
128
140
  # 按文件逐个处理
129
141
  for filepath, patch_content in patches.items():
130
142
  try:
@@ -135,6 +147,7 @@ def apply_patch(output_str: str) -> str:
135
147
  os.makedirs(os.path.dirname(filepath), exist_ok=True)
136
148
  open(filepath, 'w', encoding='utf-8').close()
137
149
  spinner.write("✅ 文件创建完成")
150
+ add_read_file_record(filepath)
138
151
  with spinner.hidden():
139
152
  while not handle_code_operation(filepath, patch_content):
140
153
  if user_confirm("补丁应用失败,是否重试?", default=True):
@@ -146,7 +159,7 @@ def apply_patch(output_str: str) -> str:
146
159
  spinner.text = f"文件 {filepath} 处理失败: {str(e)}, 回滚文件"
147
160
  revert_file(filepath) # 回滚单个文件
148
161
  spinner.write(f"✅ 文件 {filepath} 回滚完成")
149
-
162
+
150
163
  final_ret = ""
151
164
  diff = get_diff()
152
165
  if diff:
@@ -157,30 +170,29 @@ def apply_patch(output_str: str) -> str:
157
170
  # 获取提交信息
158
171
  end_hash = get_latest_commit_hash()
159
172
  commits = get_commits_between(start_hash, end_hash)
160
-
173
+
161
174
  # 添加提交信息到final_ret
162
175
  if commits:
163
176
  final_ret += "✅ 补丁已应用\n"
164
177
  final_ret += "# 提交信息:\n"
165
178
  for commit_hash, commit_message in commits:
166
179
  final_ret += f"- {commit_hash[:7]}: {commit_message}\n"
167
-
180
+
168
181
  final_ret += f"# 应用补丁:\n```diff\n{diff}\n```"
169
-
182
+
170
183
  # 增加代码变更分析和错误提示
171
- final_ret += "\n\n# 代码变更分析:\n"
172
- final_ret += "1. 请使用静态检查工具(如有)检查以上变更是否引入了潜在错误\n"
173
- final_ret += "2. 如果发现代码错误,请立即提出修复方案\n"
174
- final_ret += "3. 修复代码错误的优先级高于继续实现功能\n"
175
- final_ret += "4. 确保修改后代码的一致性和完整性\n"
176
- final_ret += "5. 请确认所有相关点是否已修改完成,包括但不限于:\n"
177
- final_ret += " - 所有需要修改的文件\n"
178
- final_ret += " - 所有需要更新的函数\n"
179
- final_ret += " - 所有需要调整的依赖关系\n"
180
- final_ret += " - 所有需要同步的文档\n"
181
- final_ret += "\n\n"
182
- final_ret += "如果没有问题,请继续进行下一步修改,如果所有修改都已经完成,请终止"
183
-
184
+
185
+ addon_prompt = "1. 请调用静态检查工具(如有)检查以上变更是否引入了潜在错误\n"
186
+ addon_prompt += "2. 如果发现代码错误,请立即提出修复方案\n"
187
+ addon_prompt += "3. 如果错误并非是本次修改引入,要询问用户是否需要立即修复\n"
188
+ addon_prompt += "\n\n"
189
+ addon_prompt += "如果没有问题,请继续进行下一步修改\n"
190
+ addon_prompt += f"如果用户的需求已经完成,请终止,不要输出新的 {ot('PATCH')},不要实现任何超出用户需求外的内容\n"
191
+ addon_prompt += "如果有任何信息不清楚,调用工具获取信息\n"
192
+ addon_prompt += "每次响应必须且只能包含一个操作\n"
193
+
194
+ agent.set_addon_prompt(addon_prompt)
195
+
184
196
  else:
185
197
  final_ret += "✅ 补丁已应用(没有新的提交)"
186
198
  else:
@@ -199,8 +211,10 @@ def apply_patch(output_str: str) -> str:
199
211
  custom_reply = get_multiline_input("请输入自定义回复")
200
212
  if not custom_reply.strip(): # 如果自定义回复为空,返回空字符串
201
213
  return ""
202
- return final_ret + "\n\n" + custom_reply
203
-
214
+ agent.set_addon_prompt(custom_reply)
215
+ return final_ret
216
+
217
+
204
218
  def revert_file(filepath: str):
205
219
  """增强版git恢复,处理新文件"""
206
220
  import subprocess
@@ -211,7 +225,8 @@ def revert_file(filepath: str):
211
225
  stderr=subprocess.PIPE
212
226
  )
213
227
  if result.returncode == 0:
214
- subprocess.run(['git', 'checkout', 'HEAD', '--', filepath], check=True)
228
+ subprocess.run(['git', 'checkout', 'HEAD',
229
+ '--', filepath], check=True)
215
230
  else:
216
231
  if os.path.exists(filepath):
217
232
  os.remove(filepath)
@@ -219,11 +234,15 @@ def revert_file(filepath: str):
219
234
  except subprocess.CalledProcessError as e:
220
235
  PrettyOutput.print(f"恢复文件失败: {str(e)}", OutputType.ERROR)
221
236
  # 修改后的恢复函数
237
+
238
+
222
239
  def revert_change():
223
240
  import subprocess
224
241
  subprocess.run(['git', 'reset', '--hard', 'HEAD'], check=True)
225
242
  subprocess.run(['git', 'clean', '-fd'], check=True)
226
243
  # 修改后的获取差异函数
244
+
245
+
227
246
  def get_diff() -> str:
228
247
  """使用git获取暂存区差异"""
229
248
  import subprocess
@@ -241,9 +260,10 @@ def get_diff() -> str:
241
260
  except subprocess.CalledProcessError as e:
242
261
  return f"获取差异失败: {str(e)}"
243
262
 
244
- def handle_commit_workflow()->bool:
263
+
264
+ def handle_commit_workflow() -> bool:
245
265
  """Handle the git commit workflow and return the commit details.
246
-
266
+
247
267
  Returns:
248
268
  tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
249
269
  """
@@ -257,7 +277,7 @@ def handle_commit_workflow()->bool:
257
277
 
258
278
  def handle_code_operation(filepath: str, patch_content: str) -> bool:
259
279
  """处理代码操作"""
260
- if get_file_line_count(filepath) < 100:
280
+ if get_file_line_count(filepath) < 5:
261
281
  return handle_small_code_operation(filepath, patch_content)
262
282
  else:
263
283
  retry_count = 5
@@ -273,11 +293,12 @@ def handle_small_code_operation(filepath: str, patch_content: str) -> bool:
273
293
  with yaspin(text=f"正在修改文件 {filepath}...", color="cyan") as spinner:
274
294
  try:
275
295
  with spinner.hidden():
276
- old_file_content = FileOperationTool().execute({"operation": "read", "files": [{"path": filepath}]})
296
+ old_file_content = FileOperationTool().execute(
297
+ {"operation": "read", "files": [{"path": filepath}]})
277
298
  if not old_file_content["success"]:
278
299
  spinner.write("❌ 文件读取失败")
279
300
  return False
280
-
301
+
281
302
  prompt = f"""
282
303
  # 代码合并专家指南
283
304
 
@@ -314,15 +335,16 @@ def handle_small_code_operation(filepath: str, patch_content: str) -> bool:
314
335
  {ct("MERGED_CODE")}
315
336
  """
316
337
  model = PlatformRegistry().get_normal_platform()
317
- model.set_suppress_output(True)
338
+ model.set_suppress_output(False)
318
339
  count = 30
319
340
  start_line = -1
320
341
  end_line = -1
321
342
  code = []
322
343
  finished = False
323
- while count>0:
344
+ while count > 0:
324
345
  count -= 1
325
- response = model.chat_until_success(prompt).splitlines()
346
+ with spinner.hidden():
347
+ response = model.chat_until_success(prompt).splitlines()
326
348
  try:
327
349
  start_line = response.index(ot("MERGED_CODE")) + 1
328
350
  try:
@@ -333,7 +355,7 @@ def handle_small_code_operation(filepath: str, patch_content: str) -> bool:
333
355
  except:
334
356
  pass
335
357
 
336
- try:
358
+ try:
337
359
  response.index(ot("!!!FINISHED!!!"))
338
360
  finished = True
339
361
  break
@@ -375,14 +397,15 @@ def handle_large_code_operation(filepath: str, patch_content: str, model: BasePl
375
397
  with yaspin(text=f"正在处理文件 {filepath}...", color="cyan") as spinner:
376
398
  try:
377
399
  # 读取原始文件内容
378
- old_file_content = FileOperationTool().execute({"operation": "read", "files": [{"path": filepath}]})
400
+ old_file_content = FileOperationTool().execute(
401
+ {"operation": "read", "files": [{"path": filepath}]})
379
402
  if not old_file_content["success"]:
380
403
  spinner.text = "文件读取失败"
381
404
  spinner.fail("❌")
382
405
  return False
383
-
384
- model.set_suppress_output(True)
385
-
406
+
407
+ model.set_suppress_output(False)
408
+
386
409
  prompt = f"""
387
410
  # 代码补丁生成专家指南
388
411
 
@@ -435,20 +458,21 @@ def handle_large_code_operation(filepath: str, patch_content: str, model: BasePl
435
458
  {ct("DIFF")}
436
459
  """
437
460
  # 获取补丁内容
438
- response = model.chat_until_success(prompt)
439
-
461
+ with spinner.hidden():
462
+ response = model.chat_until_success(prompt)
463
+
440
464
  # 解析差异化补丁
441
- diff_blocks = re.finditer(ot("DIFF")+r'\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?'+ct("DIFF"),
442
- response, re.DOTALL)
443
-
465
+ diff_blocks = re.finditer(ot("DIFF")+r'\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?'+ct("DIFF"),
466
+ response, re.DOTALL)
467
+
444
468
  # 读取原始文件内容
445
469
  with open(filepath, 'r', encoding='utf-8', errors="ignore") as f:
446
470
  file_content = f.read()
447
-
471
+
448
472
  # 应用所有差异化补丁
449
473
  modified_content = file_content
450
474
  patch_count = 0
451
-
475
+
452
476
  for match in diff_blocks:
453
477
  search_text = match.group(1).strip()
454
478
  replace_text = match.group(2).strip()
@@ -461,23 +485,23 @@ def handle_large_code_operation(filepath: str, patch_content: str, model: BasePl
461
485
  spinner.fail("❌")
462
486
  return False
463
487
  # 应用替换
464
- modified_content = modified_content.replace(search_text, replace_text)
488
+ modified_content = modified_content.replace(
489
+ search_text, replace_text)
465
490
  spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
466
491
  else:
467
492
  spinner.text = f"补丁 #{patch_count} 应用失败:无法找到匹配的代码段"
468
493
  spinner.fail("❌")
469
494
  return False
470
-
495
+
471
496
  # 写入修改后的内容
472
497
  with open(filepath, 'w', encoding='utf-8', errors="ignore") as f:
473
498
  f.write(modified_content)
474
-
499
+
475
500
  spinner.text = f"文件 {filepath} 修改完成,应用了 {patch_count} 个补丁"
476
501
  spinner.ok("✅")
477
502
  return True
478
-
503
+
479
504
  except Exception as e:
480
505
  spinner.text = f"文件修改失败: {str(e)}"
481
506
  spinner.fail("❌")
482
507
  return False
483
-
@@ -21,7 +21,9 @@ def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
21
21
  "arguments": {
22
22
  "script_content": script
23
23
  }
24
- })
25
- return f"{user_input}\n\n用户执行以下脚本:\n{script}\n\n执行结果:\n{output}", False
24
+ }, agent)
25
+ if user_confirm("是否将执行结果反馈给Agent?", default=True):
26
+ return f"{user_input}\n\n用户执行以下脚本:\n{script}\n\n执行结果:\n{output}", False
27
+ return "", True
26
28
  return user_input, False
27
-
29
+