jarvis-ai-assistant 0.1.126__py3-none-any.whl → 0.1.129__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 (42) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +108 -95
  3. jarvis/jarvis_agent/main.py +77 -0
  4. jarvis/jarvis_code_agent/builtin_input_handler.py +43 -0
  5. jarvis/jarvis_code_agent/code_agent.py +17 -81
  6. jarvis/jarvis_code_agent/file_input_handler.py +88 -0
  7. jarvis/jarvis_code_agent/patch.py +142 -114
  8. jarvis/jarvis_code_agent/shell_input_handler.py +8 -2
  9. jarvis/jarvis_codebase/main.py +240 -213
  10. jarvis/jarvis_dev/main.py +4 -3
  11. jarvis/jarvis_multi_agent/__init__.py +51 -40
  12. jarvis/jarvis_platform/base.py +6 -5
  13. jarvis/jarvis_platform_manager/main.py +1 -1
  14. jarvis/jarvis_rag/main.py +250 -186
  15. jarvis/jarvis_smart_shell/main.py +0 -1
  16. jarvis/jarvis_tools/ask_codebase.py +4 -3
  17. jarvis/jarvis_tools/chdir.py +22 -22
  18. jarvis/jarvis_tools/code_review.py +38 -33
  19. jarvis/jarvis_tools/execute_shell.py +0 -3
  20. jarvis/jarvis_tools/file_operation.py +56 -55
  21. jarvis/jarvis_tools/git_commiter.py +60 -50
  22. jarvis/jarvis_tools/read_code.py +143 -0
  23. jarvis/jarvis_tools/read_webpage.py +50 -30
  24. jarvis/jarvis_tools/registry.py +4 -21
  25. jarvis/jarvis_tools/search_web.py +61 -36
  26. jarvis/jarvis_tools/tool_generator.py +78 -36
  27. jarvis/jarvis_utils/__init__.py +17 -17
  28. jarvis/jarvis_utils/config.py +87 -51
  29. jarvis/jarvis_utils/embedding.py +49 -48
  30. jarvis/jarvis_utils/git_utils.py +34 -34
  31. jarvis/jarvis_utils/globals.py +26 -26
  32. jarvis/jarvis_utils/input.py +61 -45
  33. jarvis/jarvis_utils/methodology.py +94 -76
  34. jarvis/jarvis_utils/output.py +63 -62
  35. jarvis/jarvis_utils/utils.py +2 -2
  36. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/METADATA +1 -1
  37. jarvis_ai_assistant-0.1.129.dist-info/RECORD +78 -0
  38. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/entry_points.txt +2 -0
  39. jarvis_ai_assistant-0.1.126.dist-info/RECORD +0 -74
  40. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/LICENSE +0 -0
  41. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/WHEEL +0 -0
  42. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/top_level.txt +0 -0
jarvis/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Jarvis AI Assistant"""
2
2
 
3
- __version__ = "0.1.126"
3
+ __version__ = "0.1.129"
@@ -3,6 +3,7 @@ from typing import Any, Callable, List, Optional, Tuple, Union
3
3
 
4
4
  from prompt_toolkit import prompt
5
5
  import yaml
6
+ from yaspin import yaspin
6
7
 
7
8
  from jarvis.jarvis_agent.output_handler import OutputHandler
8
9
  from jarvis.jarvis_platform.base import BasePlatform
@@ -48,7 +49,8 @@ class Agent:
48
49
  record_methodology: Optional[bool] = None,
49
50
  need_summary: Optional[bool] = None,
50
51
  max_context_length: Optional[int] = None,
51
- execute_tool_confirm: Optional[bool] = None):
52
+ execute_tool_confirm: Optional[bool] = None,
53
+ multiline_inputer: Optional[Callable[[str], str]] = None):
52
54
  self.name = make_agent_name(name)
53
55
  self.description = description
54
56
  # 初始化平台和模型
@@ -66,9 +68,10 @@ class Agent:
66
68
  if model_name is not None:
67
69
  self.model.set_model_name(model_name)
68
70
 
71
+ self.model.set_suppress_output(False)
69
72
 
70
- self.output_handler = output_handler
71
-
73
+ self.output_handler = output_handler if output_handler else [ToolRegistry()]
74
+ self.multiline_inputer = multiline_inputer if multiline_inputer else get_multiline_input
72
75
 
73
76
  self.record_methodology = record_methodology if record_methodology is not None else is_record_methodology()
74
77
  self.use_methodology = use_methodology if use_methodology is not None else is_use_methodology()
@@ -165,6 +168,7 @@ class Agent:
165
168
  message, need_return = handler(message, self)
166
169
  if need_return:
167
170
  return message
171
+ PrettyOutput.section("模型输出", OutputType.SYSTEM)
168
172
  return self.model.chat_until_success(message) # type: ignore
169
173
 
170
174
 
@@ -184,35 +188,38 @@ class Agent:
184
188
  """
185
189
  # Create a new model instance to summarize, avoid affecting the main conversation
186
190
 
187
- PrettyOutput.print("总结对话历史,准备生成摘要,开始新对话...", OutputType.PROGRESS)
188
-
189
- prompt = """请总结之前对话中的关键信息,包括:
190
- 1. 当前任务目标
191
- 2. 已确认的关键信息
192
- 3. 已尝试的解决方案
193
- 4. 当前进展
194
- 5. 待解决的问题
195
-
196
- 请用简洁的要点形式描述,突出重要信息。不要包含对话细节。
197
- """
198
-
199
- try:
200
- summary = self._call_model(self.prompt + "\n" + prompt)
191
+ with yaspin(text="正在总结对话历史...", color="cyan") as spinner:
201
192
 
202
- # 清空当前对话历史,但保留系统消息
203
- self.conversation_length = 0 # Reset conversation length
193
+ prompt = """请总结之前对话中的关键信息,包括:
194
+ 1. 当前任务目标
195
+ 2. 已确认的关键信息
196
+ 3. 已尝试的解决方案
197
+ 4. 当前进展
198
+ 5. 待解决的问题
199
+
200
+ 请用简洁的要点形式描述,突出重要信息。不要包含对话细节。
201
+ """
204
202
 
205
- # 添加总结作为新的上下文
206
- self.prompt = f"""以下是之前对话的关键信息总结:
203
+ try:
204
+ with spinner.hidden():
205
+ summary = self._call_model(self.prompt + "\n" + prompt)
206
+
207
+ # 清空当前对话历史,但保留系统消息
208
+ self.conversation_length = 0 # Reset conversation length
209
+
210
+ # 添加总结作为新的上下文
211
+ self.prompt = f"""以下是之前对话的关键信息总结:
207
212
 
208
- {summary}
213
+ {summary}
209
214
 
210
- 请基于以上信息继续完成任务。
211
- """
212
- self.conversation_length = len(self.prompt) # 设置新的起始长度
213
-
214
- except Exception as e:
215
- PrettyOutput.print(f"总结对话历史失败: {str(e)}", OutputType.ERROR)
215
+ 请基于以上信息继续完成任务。
216
+ """
217
+ self.conversation_length = len(self.prompt) # 设置新的起始长度
218
+ spinner.text = "总结对话历史完成"
219
+ spinner.ok("✅")
220
+ except Exception as e:
221
+ spinner.text = "总结对话历史失败"
222
+ spinner.fail("❌")
216
223
 
217
224
  def _call_tools(self, response: str) -> Tuple[bool, Any]:
218
225
  tool_list = []
@@ -225,7 +232,12 @@ class Agent:
225
232
  if len(tool_list) == 0:
226
233
  return False, ""
227
234
  if not self.execute_tool_confirm or user_confirm(f"需要执行{tool_list[0].name()}确认执行?", True):
228
- return tool_list[0].handle(response)
235
+ with yaspin(text=f"正在执行{tool_list[0].name()}...", color="cyan") as spinner:
236
+ with spinner.hidden():
237
+ result = tool_list[0].handle(response)
238
+ spinner.text = f"{tool_list[0].name()}执行完成"
239
+ spinner.ok("✅")
240
+ return result
229
241
  return False, ""
230
242
 
231
243
 
@@ -239,56 +251,56 @@ class Agent:
239
251
  - For main agent: May generate methodology if enabled
240
252
  - For sub-agent: May generate summary if enabled
241
253
  """
242
- PrettyOutput.section("任务完成", OutputType.SUCCESS)
243
-
244
254
  if not self.is_sub_agent:
245
255
  if self.record_methodology:
246
-
247
- try:
248
- # 让模型判断是否需要生成方法论
249
- analysis_prompt = """当前任务已结束,请分析是否需要生成方法论。
250
- 如果你认为需要生成方法论,请先确定是创建新方法论还是更新现有方法论。如果是更新现有方法论,请使用'update',否则使用'add'。
251
- 如果你认为不需要方法论,请解释原因。
252
- 方法论应适用于通用场景,不要包含任务特定信息,如代码提交信息等。
253
- 方法论应包含:问题重述、最优解决方案、注意事项(如有),除此之外不要包含其他内容。
254
- 只输出方法论工具调用指令,或不生成方法论的解释。不要输出其他内容。
255
- """
256
- self.prompt = analysis_prompt
257
- response = self._call_model(self.prompt)
258
-
259
- self._call_tools(response)
260
-
261
- except Exception as e:
262
- PrettyOutput.print(f"生成方法论失败: {str(e)}", OutputType.ERROR)
263
-
256
+ with yaspin(text="正在生成方法论...", color="cyan") as spinner:
257
+ try:
258
+
259
+ # 让模型判断是否需要生成方法论
260
+ analysis_prompt = """当前任务已结束,请分析是否需要生成方法论。
261
+ 如果你认为需要生成方法论,请先确定是创建新方法论还是更新现有方法论。如果是更新现有方法论,请使用'update',否则使用'add'。
262
+ 如果你认为不需要方法论,请解释原因。
263
+ 方法论应适用于通用场景,不要包含任务特定信息,如代码提交信息等。
264
+ 方法论应包含:问题重述、最优解决方案、注意事项(如有),除此之外不要包含其他内容。
265
+ 只输出方法论工具调用指令,或不生成方法论的解释。不要输出其他内容。
266
+ """
267
+ self.prompt = analysis_prompt
268
+ with spinner.hidden():
269
+ response = self._call_model(self.prompt)
270
+
271
+ with spinner.hidden():
272
+ self._call_tools(response)
273
+ spinner.text = "方法论生成完成"
274
+ spinner.ok("✅")
275
+ except Exception as e:
276
+ spinner.text = "方法论生成失败"
277
+ spinner.fail("❌")
264
278
  return "任务完成"
265
279
 
266
280
  if self.need_summary:
267
- self.prompt = self.summary_prompt
268
- return self._call_model(self.prompt)
281
+ with yaspin(text="正在生成总结...", color="cyan") as spinner:
282
+ self.prompt = self.summary_prompt
283
+ with spinner.hidden():
284
+ ret = self._call_model(self.prompt)
285
+ spinner.text = "总结生成完成"
286
+ spinner.ok("✅")
287
+ return ret
269
288
 
270
289
  return "任务完成"
271
290
 
272
291
 
273
- def run(self, user_input: str, file_list: Optional[List[str]] = None) -> Any:
292
+ def run(self, user_input: str) -> Any:
274
293
  """Process user input and execute the task.
275
294
 
276
295
  Args:
277
296
  user_input: My task description or request
278
- file_list: Optional list of files to process
279
297
 
280
298
  Returns:
281
299
  str|Dict: Task summary report or message to send
282
300
  """
283
301
  try:
284
302
  set_agent(self.name, self)
285
- PrettyOutput.section("准备环境", OutputType.PLANNING)
286
- if file_list:
287
- self.model.upload_files(file_list) # type: ignore
288
-
289
- # 显示任务开始
290
- PrettyOutput.section(f"开始新任务: {self.name}", OutputType.PLANNING)
291
-
303
+
292
304
  self.prompt = f"{user_input}"
293
305
 
294
306
  if self.first:
@@ -298,9 +310,6 @@ class Agent:
298
310
 
299
311
  while True:
300
312
  try:
301
- # 显示思考状态
302
- PrettyOutput.print("正在分析任务...", OutputType.PROGRESS)
303
-
304
313
  # 累加对话长度
305
314
  self.conversation_length += get_context_token_count(self.prompt)
306
315
 
@@ -325,7 +334,7 @@ class Agent:
325
334
  return self._complete_task()
326
335
 
327
336
  # 获取用户输入
328
- user_input = get_multiline_input(f"{self.name}: 请输入,或输入空行来结束当前任务:")
337
+ user_input = self.multiline_inputer(f"{self.name}: 请输入,或输入空行来结束当前任务:")
329
338
 
330
339
  if user_input:
331
340
  self.prompt = user_input
@@ -360,41 +369,46 @@ class Agent:
360
369
  def _load_tasks() -> dict:
361
370
  """Load tasks from .jarvis files in user home and current directory."""
362
371
  tasks = {}
363
-
372
+
364
373
  # Check .jarvis/pre-command in user directory
365
374
  user_jarvis = os.path.expanduser("~/.jarvis/pre-command")
366
375
  if os.path.exists(user_jarvis):
367
- try:
368
- with open(user_jarvis, "r", encoding="utf-8") as f:
369
- user_tasks = yaml.safe_load(f)
370
-
371
- if isinstance(user_tasks, dict):
372
- # Validate and add user directory tasks
373
- for name, desc in user_tasks.items():
374
- if desc: # Ensure description is not empty
375
- tasks[str(name)] = str(desc)
376
- else:
377
- PrettyOutput.print("警告: ~/.jarvis/pre-command 文件应该包含一个字典,键为任务名称,值为任务描述", OutputType.WARNING)
378
- except Exception as e:
379
- PrettyOutput.print(f"加载 ~/.jarvis/pre-command 文件失败: {str(e)}", OutputType.ERROR)
380
-
376
+ with yaspin(text=f"从{user_jarvis}加载预定义任务...", color="cyan") as spinner:
377
+ try:
378
+ with open(user_jarvis, "r", encoding="utf-8") as f:
379
+ user_tasks = yaml.safe_load(f)
380
+
381
+ if isinstance(user_tasks, dict):
382
+ # Validate and add user directory tasks
383
+ for name, desc in user_tasks.items():
384
+ if desc: # Ensure description is not empty
385
+ tasks[str(name)] = str(desc)
386
+ spinner.text = "预定义任务加载完成"
387
+ spinner.ok("✅")
388
+ except Exception as e:
389
+ spinner.text = "预定义任务加载失败"
390
+ spinner.fail("❌")
391
+
381
392
  # Check .jarvis/pre-command in current directory
382
393
  if os.path.exists(".jarvis/pre-command"):
383
- try:
384
- with open(".jarvis/pre-command", "r", encoding="utf-8") as f:
385
- local_tasks = yaml.safe_load(f)
386
-
387
- if isinstance(local_tasks, dict):
388
- # Validate and add current directory tasks, overwrite user directory tasks if there is a name conflict
389
- for name, desc in local_tasks.items():
390
- if desc: # Ensure description is not empty
391
- tasks[str(name)] = str(desc)
392
- else:
393
- PrettyOutput.print("警告: .jarvis/pre-command 文件应该包含一个字典,键为任务名称,值为任务描述", OutputType.WARNING)
394
- except Exception as e:
395
- PrettyOutput.print(f"加载 .jarvis/pre-command 文件失败: {str(e)}", OutputType.ERROR)
394
+ with yaspin(text=f"从{os.path.abspath('.jarvis/pre-command')}加载预定义任务...", color="cyan") as spinner:
395
+ try:
396
+ with open(".jarvis/pre-command", "r", encoding="utf-8") as f:
397
+ local_tasks = yaml.safe_load(f)
398
+
399
+ if isinstance(local_tasks, dict):
400
+ # Validate and add current directory tasks, overwrite user directory tasks if there is a name conflict
401
+ for name, desc in local_tasks.items():
402
+ if desc: # Ensure description is not empty
403
+ tasks[str(name)] = str(desc)
404
+ spinner.text = "预定义任务加载完成"
405
+ spinner.ok("✅")
406
+ except Exception as e:
407
+ spinner.text = "预定义任务加载失败"
408
+ spinner.fail("❌")
396
409
 
397
410
  return tasks
411
+
398
412
  def _select_task(tasks: dict) -> str:
399
413
  """Let user select a task from the list or skip. Returns task description if selected."""
400
414
  if not tasks:
@@ -533,7 +547,6 @@ def main():
533
547
  # Add argument parser
534
548
  init_env()
535
549
  parser = argparse.ArgumentParser(description='Jarvis AI assistant')
536
- parser.add_argument('-f', '--files', nargs='*', help='List of files to process')
537
550
  parser.add_argument('-p', '--platform', type=str, help='Platform to use')
538
551
  parser.add_argument('-m', '--model', type=str, help='Model to use')
539
552
  args = parser.parse_args()
@@ -548,7 +561,7 @@ def main():
548
561
  selected_task = _select_task(tasks)
549
562
  if selected_task:
550
563
  PrettyOutput.print(f"执行任务: {selected_task}", OutputType.INFO)
551
- agent.run(selected_task, args.files)
564
+ agent.run(selected_task)
552
565
  return 0
553
566
 
554
567
  # 如果没有选择预定义任务,进入交互模式
@@ -557,7 +570,7 @@ def main():
557
570
  user_input = get_multiline_input("请输入你的任务(输入空行退出):")
558
571
  if not user_input:
559
572
  break
560
- agent.run(user_input, args.files)
573
+ agent.run(user_input)
561
574
  except Exception as e:
562
575
  PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
563
576
 
@@ -0,0 +1,77 @@
1
+ import argparse
2
+ import yaml
3
+ import os
4
+ from typing import Optional, List
5
+ from jarvis.jarvis_agent import Agent
6
+ from jarvis.jarvis_utils.input import get_multiline_input
7
+ from jarvis.jarvis_utils.output import PrettyOutput, OutputType
8
+ from jarvis.jarvis_utils.utils import init_env
9
+
10
+ # 从__init__.py导入系统提示
11
+ from jarvis.jarvis_agent import origin_agent_system_prompt
12
+
13
+ def load_config(config_path: str) -> dict:
14
+ """Load configuration from YAML file
15
+
16
+ Args:
17
+ config_path: Path to the YAML configuration file
18
+
19
+ Returns:
20
+ dict: Configuration dictionary
21
+ """
22
+ if not os.path.exists(config_path):
23
+ PrettyOutput.print(f"配置文件 {config_path} 不存在,使用默认配置", OutputType.WARNING)
24
+ return {}
25
+
26
+ with open(config_path, 'r', encoding='utf-8') as f:
27
+ try:
28
+ config = yaml.safe_load(f)
29
+ return config if config else {}
30
+ except yaml.YAMLError as e:
31
+ PrettyOutput.print(f"配置文件解析失败: {str(e)}", OutputType.ERROR)
32
+ return {}
33
+
34
+ def main():
35
+ """Main entry point for Jarvis agent"""
36
+ # Initialize environment
37
+ init_env()
38
+
39
+ # Set up argument parser
40
+ parser = argparse.ArgumentParser(description='Jarvis AI assistant')
41
+ parser.add_argument('-c', '--config', type=str, required=True,
42
+ help='Path to the YAML configuration file')
43
+ parser.add_argument('-t', '--task', type=str,
44
+ help='Initial task to execute')
45
+ args = parser.parse_args()
46
+
47
+ # Load configuration
48
+ config = load_config(args.config)
49
+
50
+ # Create and run agent
51
+ try:
52
+ agent = Agent(**config)
53
+
54
+ # Run agent with initial task if specified
55
+ if args.task:
56
+ PrettyOutput.print(f"执行初始任务: {args.task}", OutputType.INFO)
57
+ agent.run(args.task)
58
+ return 0
59
+
60
+ # Enter interactive mode if no initial task
61
+ while True:
62
+ try:
63
+ user_input = get_multiline_input("请输入你的任务(输入空行退出):")
64
+ if not user_input:
65
+ break
66
+ agent.run(user_input)
67
+ except Exception as e:
68
+ PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
69
+
70
+ except Exception as e:
71
+ PrettyOutput.print(f"初始化错误: {str(e)}", OutputType.ERROR)
72
+ return 1
73
+
74
+ return 0
75
+
76
+ if __name__ == "__main__":
77
+ exit(main())
@@ -0,0 +1,43 @@
1
+ import re
2
+ from typing import Any, Tuple
3
+
4
+ from jarvis.jarvis_utils.output import PrettyOutput, OutputType
5
+
6
+
7
+ def builtin_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
8
+ """
9
+ 处理内置的特殊输入标记,并追加相应的提示词
10
+
11
+ 参数:
12
+ user_input: 用户输入
13
+ agent: 代理对象
14
+
15
+ 返回:
16
+ Tuple[str, bool]: 处理后的输入和是否需要进一步处理
17
+ """
18
+ # 查找特殊标记
19
+ special_tags = re.findall(r"'<([^>]+)>'", user_input)
20
+
21
+ if not special_tags:
22
+ return user_input, False
23
+
24
+ # 使用集合去重
25
+ processed_tags = set()
26
+ # 处理每个标记
27
+ for tag in special_tags:
28
+ if tag in processed_tags:
29
+ continue
30
+ processed_tags.add(tag)
31
+
32
+ if tag == "CodeBase":
33
+ user_input = user_input.replace(f"'<{tag}>'", "")
34
+ user_input += "\n请使用ask_codebase工具查询代码库"
35
+ elif tag == "Web":
36
+ user_input = user_input.replace(f"'<{tag}>'", "")
37
+ user_input += "\n请使用search_web工具进行网页搜索"
38
+ elif tag == "RAG":
39
+ user_input = user_input.replace(f"'<{tag}>'", "")
40
+ user_input += "\n请使用rag工具进行知识库检索"
41
+ # 移除对未知标记的警告输出
42
+
43
+ return user_input, False
@@ -3,7 +3,11 @@ import subprocess
3
3
  import os
4
4
  from typing import Any, Tuple
5
5
 
6
+ from yaspin import yaspin
7
+
6
8
  from jarvis.jarvis_agent import Agent
9
+ from jarvis.jarvis_code_agent.builtin_input_handler import builtin_input_handler
10
+ from jarvis.jarvis_code_agent.file_input_handler import file_input_handler
7
11
  from jarvis.jarvis_code_agent.shell_input_handler import shell_input_handler
8
12
  from jarvis.jarvis_code_agent.patch import PatchOutputHandler
9
13
  from jarvis.jarvis_platform.registry import PlatformRegistry
@@ -19,80 +23,6 @@ from jarvis.jarvis_utils.utils import init_env, user_confirm
19
23
 
20
24
 
21
25
 
22
- def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
23
- prompt = user_input
24
- files = []
25
-
26
- file_refs = re.findall(r"'([^']+)'", user_input)
27
- for ref in file_refs:
28
- # Handle file:start,end or file:start:end format
29
- if ':' in ref:
30
- file_path, line_range = ref.split(':', 1)
31
- # Initialize with default values
32
- start_line = 1 # 1-based
33
- end_line = -1
34
-
35
- # Process line range if specified
36
- if ',' in line_range or ':' in line_range:
37
- try:
38
- raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
39
-
40
- # Handle special values and Python-style negative indices
41
- try:
42
- with open(file_path, 'r', encoding='utf-8') as f:
43
- total_lines = len(f.readlines())
44
- except FileNotFoundError:
45
- PrettyOutput.print(f"文件不存在: {file_path}", OutputType.WARNING)
46
- continue
47
- # Process start line
48
- if raw_start == 0: # 0表示整个文件
49
- start_line = 1
50
- end_line = total_lines
51
- else:
52
- start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
53
-
54
- # Process end line
55
- if raw_end == 0: # 0表示整个文件(如果start也是0)
56
- end_line = total_lines
57
- else:
58
- end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
59
-
60
- # Auto-correct ranges
61
- start_line = max(1, min(start_line, total_lines))
62
- end_line = max(start_line, min(end_line, total_lines))
63
-
64
- # Final validation
65
- if start_line < 1 or end_line > total_lines or start_line > end_line:
66
- raise ValueError
67
-
68
- except:
69
- continue
70
-
71
- # Add file if it exists
72
- if os.path.isfile(file_path):
73
- files.append({
74
- "path": file_path,
75
- "start_line": start_line,
76
- "end_line": end_line
77
- })
78
- else:
79
- # Handle simple file path
80
- if os.path.isfile(ref):
81
- files.append({
82
- "path": ref,
83
- "start_line": 1, # 1-based
84
- "end_line": -1
85
- })
86
-
87
- # Read and process files if any were found
88
- if files:
89
- result = FileOperationTool().execute({"operation":"read","files": files})
90
- if result["success"]:
91
- return result["stdout"] + "\n" + prompt, False
92
-
93
- return prompt, False
94
-
95
-
96
26
 
97
27
  class CodeAgent:
98
28
  def __init__(self):
@@ -203,18 +133,23 @@ class CodeAgent:
203
133
  output_handler=[tool_registry, PatchOutputHandler()],
204
134
  platform=PlatformRegistry().get_codegen_platform(),
205
135
  record_methodology=False,
206
- input_handler=[shell_input_handler, file_input_handler],
136
+ input_handler=[shell_input_handler, file_input_handler, builtin_input_handler],
207
137
  need_summary=False)
208
138
 
209
139
 
210
140
 
211
141
  def _init_env(self):
212
- curr_dir = os.getcwd()
213
- git_dir = find_git_root(curr_dir)
214
- self.root_dir = git_dir
215
- if has_uncommitted_changes():
216
- git_commiter = GitCommitTool()
217
- git_commiter.execute({})
142
+ with yaspin(text="正在初始化环境...", color="cyan") as spinner:
143
+ curr_dir = os.getcwd()
144
+ git_dir = find_git_root(curr_dir)
145
+ self.root_dir = git_dir
146
+ if has_uncommitted_changes():
147
+ with spinner.hidden():
148
+ git_commiter = GitCommitTool()
149
+ git_commiter.execute({})
150
+ else:
151
+ spinner.text = "环境初始化完成"
152
+ spinner.ok("✅")
218
153
 
219
154
 
220
155
 
@@ -229,6 +164,7 @@ class CodeAgent:
229
164
  """
230
165
  try:
231
166
  self._init_env()
167
+
232
168
  start_commit = get_latest_commit_hash()
233
169
 
234
170
  try:
@@ -0,0 +1,88 @@
1
+
2
+
3
+ import os
4
+ import re
5
+ from typing import Any, Tuple
6
+
7
+ from yaspin import yaspin
8
+
9
+ from jarvis.jarvis_tools.file_operation import FileOperationTool
10
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
11
+
12
+
13
+ def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
14
+ prompt = user_input
15
+ files = []
16
+
17
+ file_refs = re.findall(r"'([^']+)'", user_input)
18
+ for ref in file_refs:
19
+ # Handle file:start,end or file:start:end format
20
+ if ':' in ref:
21
+ file_path, line_range = ref.split(':', 1)
22
+ # Initialize with default values
23
+ start_line = 1 # 1-based
24
+ end_line = -1
25
+
26
+ # Process line range if specified
27
+ if ',' in line_range or ':' in line_range:
28
+ try:
29
+ raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
30
+
31
+ # Handle special values and Python-style negative indices
32
+ try:
33
+ with open(file_path, 'r', encoding='utf-8') as f:
34
+ total_lines = len(f.readlines())
35
+ except FileNotFoundError:
36
+ PrettyOutput.print(f"文件不存在: {file_path}", OutputType.WARNING)
37
+ continue
38
+ # Process start line
39
+ if raw_start == 0: # 0表示整个文件
40
+ start_line = 1
41
+ end_line = total_lines
42
+ else:
43
+ start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
44
+
45
+ # Process end line
46
+ if raw_end == 0: # 0表示整个文件(如果start也是0)
47
+ end_line = total_lines
48
+ else:
49
+ end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
50
+
51
+ # Auto-correct ranges
52
+ start_line = max(1, min(start_line, total_lines))
53
+ end_line = max(start_line, min(end_line, total_lines))
54
+
55
+ # Final validation
56
+ if start_line < 1 or end_line > total_lines or start_line > end_line:
57
+ raise ValueError
58
+
59
+ except:
60
+ continue
61
+
62
+ # Add file if it exists
63
+ if os.path.isfile(file_path):
64
+ files.append({
65
+ "path": file_path,
66
+ "start_line": start_line,
67
+ "end_line": end_line
68
+ })
69
+ else:
70
+ # Handle simple file path
71
+ if os.path.isfile(ref):
72
+ files.append({
73
+ "path": ref,
74
+ "start_line": 1, # 1-based
75
+ "end_line": -1
76
+ })
77
+
78
+ # Read and process files if any were found
79
+ if files:
80
+ with yaspin(text="正在读取文件...", color="cyan") as spinner:
81
+ result = FileOperationTool().execute({"operation":"read","files": files})
82
+ if result["success"]:
83
+ spinner.text = "文件读取完成"
84
+ spinner.ok("✅")
85
+ return result["stdout"] + "\n" + prompt, False
86
+
87
+ return prompt, False
88
+