jarvis-ai-assistant 0.7.0__py3-none-any.whl → 0.7.6__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.
Files changed (159) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +243 -139
  3. jarvis/jarvis_agent/agent_manager.py +5 -10
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +265 -15
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +113 -98
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +6 -12
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +77 -14
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +12 -21
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/task_analyzer.py +26 -4
  31. jarvis/jarvis_agent/task_manager.py +11 -27
  32. jarvis/jarvis_agent/tool_executor.py +2 -3
  33. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  34. jarvis/jarvis_agent/web_server.py +55 -20
  35. jarvis/jarvis_c2rust/__init__.py +5 -5
  36. jarvis/jarvis_c2rust/cli.py +461 -499
  37. jarvis/jarvis_c2rust/collector.py +45 -53
  38. jarvis/jarvis_c2rust/constants.py +26 -0
  39. jarvis/jarvis_c2rust/library_replacer.py +264 -132
  40. jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
  41. jarvis/jarvis_c2rust/loaders.py +207 -0
  42. jarvis/jarvis_c2rust/models.py +28 -0
  43. jarvis/jarvis_c2rust/optimizer.py +1592 -395
  44. jarvis/jarvis_c2rust/transpiler.py +1722 -1064
  45. jarvis/jarvis_c2rust/utils.py +385 -0
  46. jarvis/jarvis_code_agent/build_validation_config.py +2 -3
  47. jarvis/jarvis_code_agent/code_agent.py +394 -320
  48. jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
  61. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
  62. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
  63. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
  64. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
  65. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  66. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
  68. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
  69. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  70. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
  71. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  72. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
  73. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
  74. jarvis/jarvis_code_agent/lint.py +258 -27
  75. jarvis/jarvis_code_agent/utils.py +0 -1
  76. jarvis/jarvis_code_analysis/code_review.py +19 -24
  77. jarvis/jarvis_data/config_schema.json +53 -26
  78. jarvis/jarvis_git_squash/main.py +4 -5
  79. jarvis/jarvis_git_utils/git_commiter.py +44 -49
  80. jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
  81. jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
  82. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  83. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  84. jarvis/jarvis_methodology/main.py +32 -48
  85. jarvis/jarvis_multi_agent/__init__.py +79 -61
  86. jarvis/jarvis_multi_agent/main.py +3 -7
  87. jarvis/jarvis_platform/base.py +469 -199
  88. jarvis/jarvis_platform/human.py +7 -8
  89. jarvis/jarvis_platform/kimi.py +30 -36
  90. jarvis/jarvis_platform/openai.py +65 -27
  91. jarvis/jarvis_platform/registry.py +26 -10
  92. jarvis/jarvis_platform/tongyi.py +24 -25
  93. jarvis/jarvis_platform/yuanbao.py +31 -42
  94. jarvis/jarvis_platform_manager/main.py +66 -77
  95. jarvis/jarvis_platform_manager/service.py +8 -13
  96. jarvis/jarvis_rag/cli.py +49 -51
  97. jarvis/jarvis_rag/embedding_manager.py +13 -18
  98. jarvis/jarvis_rag/llm_interface.py +8 -9
  99. jarvis/jarvis_rag/query_rewriter.py +10 -21
  100. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  101. jarvis/jarvis_rag/reranker.py +4 -5
  102. jarvis/jarvis_rag/retriever.py +28 -30
  103. jarvis/jarvis_sec/__init__.py +220 -3520
  104. jarvis/jarvis_sec/agents.py +143 -0
  105. jarvis/jarvis_sec/analysis.py +276 -0
  106. jarvis/jarvis_sec/cli.py +29 -6
  107. jarvis/jarvis_sec/clustering.py +1439 -0
  108. jarvis/jarvis_sec/file_manager.py +427 -0
  109. jarvis/jarvis_sec/parsers.py +73 -0
  110. jarvis/jarvis_sec/prompts.py +268 -0
  111. jarvis/jarvis_sec/report.py +83 -4
  112. jarvis/jarvis_sec/review.py +453 -0
  113. jarvis/jarvis_sec/utils.py +499 -0
  114. jarvis/jarvis_sec/verification.py +848 -0
  115. jarvis/jarvis_sec/workflow.py +7 -0
  116. jarvis/jarvis_smart_shell/main.py +38 -87
  117. jarvis/jarvis_stats/cli.py +1 -1
  118. jarvis/jarvis_stats/stats.py +7 -7
  119. jarvis/jarvis_stats/storage.py +15 -21
  120. jarvis/jarvis_tools/clear_memory.py +3 -20
  121. jarvis/jarvis_tools/cli/main.py +20 -23
  122. jarvis/jarvis_tools/edit_file.py +1066 -0
  123. jarvis/jarvis_tools/execute_script.py +42 -21
  124. jarvis/jarvis_tools/file_analyzer.py +6 -9
  125. jarvis/jarvis_tools/generate_new_tool.py +11 -20
  126. jarvis/jarvis_tools/lsp_client.py +1552 -0
  127. jarvis/jarvis_tools/methodology.py +2 -3
  128. jarvis/jarvis_tools/read_code.py +1525 -87
  129. jarvis/jarvis_tools/read_symbols.py +2 -3
  130. jarvis/jarvis_tools/read_webpage.py +7 -10
  131. jarvis/jarvis_tools/registry.py +370 -181
  132. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  133. jarvis/jarvis_tools/rewrite_file.py +105 -0
  134. jarvis/jarvis_tools/save_memory.py +3 -15
  135. jarvis/jarvis_tools/search_web.py +3 -7
  136. jarvis/jarvis_tools/sub_agent.py +17 -6
  137. jarvis/jarvis_tools/sub_code_agent.py +14 -16
  138. jarvis/jarvis_tools/virtual_tty.py +54 -32
  139. jarvis/jarvis_utils/clipboard.py +7 -10
  140. jarvis/jarvis_utils/config.py +98 -63
  141. jarvis/jarvis_utils/embedding.py +5 -5
  142. jarvis/jarvis_utils/fzf.py +8 -8
  143. jarvis/jarvis_utils/git_utils.py +81 -67
  144. jarvis/jarvis_utils/input.py +24 -49
  145. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  146. jarvis/jarvis_utils/methodology.py +33 -35
  147. jarvis/jarvis_utils/utils.py +245 -202
  148. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/METADATA +205 -70
  149. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  150. jarvis/jarvis_agent/edit_file_handler.py +0 -584
  151. jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
  152. jarvis/jarvis_agent/task_planner.py +0 -496
  153. jarvis/jarvis_platform/ai8.py +0 -332
  154. jarvis/jarvis_tools/ask_user.py +0 -54
  155. jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
  156. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  157. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +0 -0
  158. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  159. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -6,8 +6,6 @@ import typer
6
6
 
7
7
  from jarvis.jarvis_agent import (
8
8
  Agent,
9
- OutputType,
10
- PrettyOutput,
11
9
  get_multiline_input,
12
10
  origin_agent_system_prompt,
13
11
  )
@@ -28,7 +26,6 @@ class AgentManager:
28
26
  multiline_inputer: Optional[Callable[[str], str]] = None,
29
27
  confirm_callback: Optional[Callable[[str, bool], bool]] = None,
30
28
  non_interactive: Optional[bool] = None,
31
- plan: Optional[bool] = None,
32
29
  ):
33
30
  self.model_group = model_group
34
31
  self.tool_group = tool_group
@@ -40,7 +37,6 @@ class AgentManager:
40
37
  self.multiline_inputer = multiline_inputer
41
38
  self.confirm_callback = confirm_callback
42
39
  self.non_interactive = non_interactive
43
- self.plan = plan
44
40
 
45
41
  def initialize(self) -> Agent:
46
42
  """初始化Agent"""
@@ -59,15 +55,14 @@ class AgentManager:
59
55
  multiline_inputer=self.multiline_inputer,
60
56
  confirm_callback=self.confirm_callback,
61
57
  non_interactive=self.non_interactive,
62
- plan=self.plan,
63
58
  )
64
59
 
65
60
  # 尝试恢复会话
66
61
  if self.restore_session:
67
62
  if self.agent.restore_session():
68
- PrettyOutput.print("会话已成功恢复。", OutputType.SUCCESS)
63
+ print("会话已成功恢复。")
69
64
  else:
70
- PrettyOutput.print("无法恢复会话。", OutputType.WARNING)
65
+ print("⚠️ 无法恢复会话。")
71
66
 
72
67
  return self.agent
73
68
 
@@ -81,12 +76,12 @@ class AgentManager:
81
76
  self.agent.run(task_content)
82
77
  raise typer.Exit(code=0)
83
78
 
84
- # 处理预定义任务(非交互模式下跳过;支持配置跳过加载)
85
- if not is_non_interactive() and not is_skip_predefined_tasks() and self.agent.first:
79
+ # 处理预定义任务(非交互模式下跳过;支持配置跳过加载;命令行指定任务时跳过)
80
+ if not is_non_interactive() and not is_skip_predefined_tasks() and not task_content and self.agent.first:
86
81
  task_manager = TaskManager()
87
82
  tasks = task_manager.load_tasks()
88
83
  if tasks and (selected_task := task_manager.select_task(tasks)):
89
- PrettyOutput.print(f"开始执行任务: \n{selected_task}", OutputType.INFO)
84
+ print(f"ℹ️ 开始执行任务: \n{selected_task}")
90
85
  self.agent.run(selected_task)
91
86
  raise typer.Exit(code=0)
92
87
 
@@ -55,14 +55,10 @@ def builtin_input_handler(user_input: str, agent_: Any) -> Tuple[str, bool]:
55
55
  return "", True
56
56
  elif tag == "SaveSession":
57
57
  if agent.save_session():
58
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
59
-
60
- PrettyOutput.print("会话已成功保存。正在退出...", OutputType.SUCCESS)
58
+ print("✅ 会话已成功保存。正在退出...")
61
59
  sys.exit(0)
62
60
  else:
63
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
64
-
65
- PrettyOutput.print("保存会话失败。", OutputType.ERROR)
61
+ print("❌ 保存会话失败。")
66
62
  return "", True
67
63
 
68
64
  processed_tag = set()
@@ -9,8 +9,6 @@ from typing import Optional
9
9
 
10
10
  import typer
11
11
 
12
- from jarvis.jarvis_agent import OutputType, PrettyOutput
13
-
14
12
 
15
13
  class ConfigEditor:
16
14
  """配置文件编辑器"""
@@ -47,11 +45,8 @@ class ConfigEditor:
47
45
  )
48
46
  raise typer.Exit(code=0)
49
47
  except (subprocess.CalledProcessError, FileNotFoundError) as e:
50
- PrettyOutput.print(f"Failed to open editor: {e}", OutputType.ERROR)
48
+ print(f"Failed to open editor: {e}")
51
49
  raise typer.Exit(code=1)
52
50
  else:
53
- PrettyOutput.print(
54
- "No suitable editor found. Please install one of: vim, nano, emacs, code",
55
- OutputType.ERROR,
56
- )
51
+ print("❌ No suitable editor found. Please install one of: vim, nano, emacs, code")
57
52
  raise typer.Exit(code=1)
@@ -6,41 +6,111 @@
6
6
  - 提供简单可靠的发布/订阅机制
7
7
  - 回调异常隔离,避免影响主流程
8
8
  - 不引入额外依赖,便于在 Agent 中渐进集成
9
+ - 支持优先级排序,优先级数字越小,执行越早
9
10
  """
10
11
  from collections import defaultdict
11
- from typing import Callable, DefaultDict, List, Any
12
-
12
+ from typing import Callable, DefaultDict, List, Any, Tuple
13
13
 
14
14
 
15
15
  class EventBus:
16
16
  """
17
17
  简单的同步事件总线。
18
- - subscribe(event, callback): 订阅事件
19
- - emit(event, **kwargs): 广播事件
18
+ - subscribe(event, callback, priority=100): 订阅事件,支持优先级
19
+ - emit(event, **kwargs): 广播事件,按优先级顺序执行回调
20
20
  - unsubscribe(event, callback): 取消订阅
21
+
22
+ 优先级说明:
23
+ - 数字越小,优先级越高,执行越早
24
+ - 默认优先级为 100(中等优先级)
25
+ - 建议优先级范围:0-200
26
+ - 相同优先级时,按注册顺序执行(先注册的先执行)
21
27
  """
22
28
 
23
29
  def __init__(self) -> None:
24
- self._listeners: DefaultDict[str, List[Callable[..., None]]] = defaultdict(list)
30
+ # 存储 (priority, order, callback) 元组列表,按优先级和注册顺序排序
31
+ # order 用于相同优先级时保持注册顺序
32
+ self._listeners: DefaultDict[str, List[Tuple[int, int, Callable[..., None]]]] = defaultdict(list)
33
+ # 注册顺序计数器(每个事件独立计数)
34
+ self._order_counter: DefaultDict[str, int] = defaultdict(int)
35
+ # 缓存排序后的回调列表,避免每次emit都排序
36
+ self._sorted_cache: DefaultDict[str, List[Callable[..., None]]] = defaultdict(list)
37
+ # 标记是否需要重新排序
38
+ self._dirty: DefaultDict[str, bool] = defaultdict(lambda: False)
25
39
 
26
- def subscribe(self, event: str, callback: Callable[..., None]) -> None:
40
+ def subscribe(self, event: str, callback: Callable[..., None], priority: int = 100) -> None:
41
+ """
42
+ 订阅事件。
43
+
44
+ 参数:
45
+ event: 事件名称
46
+ callback: 回调函数
47
+ priority: 优先级,数字越小优先级越高(默认100)
48
+ 相同优先级时,按注册顺序执行(先注册的先执行)
49
+ """
27
50
  if not callable(callback):
28
51
  raise TypeError("callback must be callable")
29
- self._listeners[event].append(callback)
52
+ # 获取当前注册顺序
53
+ order = self._order_counter[event]
54
+ self._order_counter[event] += 1
55
+ # 添加 (priority, order, callback) 元组
56
+ self._listeners[event].append((priority, order, callback))
57
+ # 标记需要重新排序
58
+ self._dirty[event] = True
30
59
 
31
60
  def unsubscribe(self, event: str, callback: Callable[..., None]) -> None:
61
+ """
62
+ 取消订阅事件。
63
+
64
+ 参数:
65
+ event: 事件名称
66
+ callback: 要取消的回调函数
67
+ """
32
68
  if event not in self._listeners:
33
69
  return
34
- try:
35
- self._listeners[event].remove(callback)
36
- except ValueError:
37
- pass
70
+ # 查找并移除匹配的回调
71
+ listeners = self._listeners[event]
72
+ for i, (_, _, cb) in enumerate(listeners):
73
+ if cb == callback:
74
+ listeners.pop(i)
75
+ self._dirty[event] = True
76
+ break
77
+
78
+ def _get_sorted_callbacks(self, event: str) -> List[Callable[..., None]]:
79
+ """
80
+ 获取排序后的回调列表(带缓存)。
81
+
82
+ 参数:
83
+ event: 事件名称
84
+
85
+ 返回:
86
+ 按优先级排序的回调函数列表(相同优先级时按注册顺序)
87
+ """
88
+ # 如果缓存有效,直接返回
89
+ if not self._dirty[event] and event in self._sorted_cache:
90
+ return self._sorted_cache[event]
91
+
92
+ # 按优先级排序(数字越小优先级越高),相同优先级时按注册顺序(order)
93
+ listeners = self._listeners[event]
94
+ sorted_listeners = sorted(listeners, key=lambda x: (x[0], x[1]))
95
+ callbacks = [cb for _, _, cb in sorted_listeners]
96
+
97
+ # 更新缓存
98
+ self._sorted_cache[event] = callbacks
99
+ self._dirty[event] = False
100
+
101
+ return callbacks
38
102
 
39
103
  def emit(self, event: str, **payload: Any) -> None:
40
104
  """
41
105
  广播事件。回调中的异常将被捕获并忽略,以保证主流程稳定。
106
+ 回调按优先级顺序执行(优先级数字越小,执行越早)。
107
+
108
+ 参数:
109
+ event: 事件名称
110
+ **payload: 事件负载数据
42
111
  """
43
- for cb in list(self._listeners.get(event, [])):
112
+ callbacks = self._get_sorted_callbacks(event)
113
+ for cb in callbacks:
44
114
  try:
45
115
  cb(**payload)
46
116
  except Exception:
@@ -1,9 +1,11 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import re
3
3
  import os
4
- from typing import Any, Tuple
4
+ from typing import Any, Tuple, List, Dict, Optional, Callable
5
5
 
6
- from jarvis.jarvis_tools.read_code import ReadCodeTool
6
+
7
+ # 语言提取器注册表(导出供其他模块使用)
8
+ _LANGUAGE_EXTRACTORS: Dict[str, Callable[[], Optional[Any]]] = {}
7
9
 
8
10
 
9
11
  def is_text_file(filepath: str) -> bool:
@@ -29,10 +31,169 @@ def count_lines(filepath: str) -> int:
29
31
  return 0
30
32
 
31
33
 
34
+ def register_language_extractor(extensions, extractor_factory=None):
35
+ """
36
+ Register a symbol extractor for one or more file extensions.
37
+
38
+ Can be used as a decorator or as a regular function.
39
+
40
+ Args:
41
+ extensions: List of file extensions (e.g., ['.py', '.pyw']) or single extension string.
42
+ If used as decorator, this is the first argument.
43
+ extractor_factory: A callable that returns an extractor instance or None if unavailable.
44
+ The extractor must have an extract_symbols(file_path: str, content: str) method
45
+ that returns a list of Symbol objects.
46
+ If used as decorator, this is the decorated function.
47
+
48
+ Examples:
49
+ # As decorator:
50
+ @register_language_extractor(['.py', '.pyw'])
51
+ def create_python_extractor():
52
+ from jarvis.jarvis_code_agent.code_analyzer.languages.python_language import PythonSymbolExtractor
53
+ return PythonSymbolExtractor()
54
+
55
+ # As regular function:
56
+ def create_java_extractor():
57
+ # ... create extractor ...
58
+ return JavaExtractor()
59
+
60
+ register_language_extractor('.java', create_java_extractor)
61
+ """
62
+ # Support both decorator and function call syntax
63
+ if extractor_factory is None:
64
+ # Used as decorator: @register_language_extractor(['.ext'])
65
+ def decorator(func: Callable[[], Optional[Any]]) -> Callable[[], Optional[Any]]:
66
+ if isinstance(extensions, str):
67
+ exts = [extensions]
68
+ else:
69
+ exts = extensions
70
+
71
+ for ext in exts:
72
+ ext_lower = ext.lower()
73
+ if not ext_lower.startswith('.'):
74
+ ext_lower = '.' + ext_lower
75
+ _LANGUAGE_EXTRACTORS[ext_lower] = func
76
+
77
+ return func
78
+
79
+ return decorator
80
+ else:
81
+ # Used as regular function: register_language_extractor(['.ext'], factory)
82
+ if isinstance(extensions, str):
83
+ extensions = [extensions]
84
+
85
+ for ext in extensions:
86
+ ext_lower = ext.lower()
87
+ if not ext_lower.startswith('.'):
88
+ ext_lower = '.' + ext_lower
89
+ _LANGUAGE_EXTRACTORS[ext_lower] = extractor_factory
90
+
91
+
92
+ def _get_symbol_extractor(filepath: str) -> Optional[Any]:
93
+ """Get appropriate symbol extractor for the file based on extension"""
94
+ ext = os.path.splitext(filepath)[1].lower()
95
+
96
+ # Check registered extractors
97
+ if ext in _LANGUAGE_EXTRACTORS:
98
+ try:
99
+ return _LANGUAGE_EXTRACTORS[ext]()
100
+ except Exception:
101
+ return None
102
+
103
+ return None
104
+
105
+
106
+ # Initialize built-in extractors on module load
107
+ # Import language_extractors module to trigger automatic registration
108
+ try:
109
+ import jarvis.jarvis_agent.language_extractors # noqa: F401
110
+ except (ImportError, Exception):
111
+ pass
112
+
113
+
114
+
115
+ def extract_symbols_from_file(filepath: str) -> List[Dict[str, Any]]:
116
+ """Extract symbols from a file using tree-sitter or AST"""
117
+ extractor = _get_symbol_extractor(filepath)
118
+ if not extractor:
119
+ return []
120
+
121
+ try:
122
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
123
+ content = f.read()
124
+
125
+ symbols = extractor.extract_symbols(filepath, content)
126
+
127
+ # Convert Symbol objects to dict format
128
+ result = []
129
+ for symbol in symbols:
130
+ result.append({
131
+ "name": symbol.name,
132
+ "type": symbol.kind,
133
+ "line": symbol.line_start,
134
+ "signature": symbol.signature or f"{symbol.kind} {symbol.name}",
135
+ })
136
+
137
+ return result
138
+ except Exception:
139
+ return []
140
+
141
+
142
+ def format_symbols_output(filepath: str, symbols: List[Dict[str, Any]]) -> str:
143
+ """Format symbols list as output string"""
144
+ if not symbols:
145
+ return ""
146
+
147
+ # Group symbols by type
148
+ by_type: Dict[str, List[Dict[str, Any]]] = {}
149
+ for symbol in symbols:
150
+ symbol_type = symbol["type"]
151
+ if symbol_type not in by_type:
152
+ by_type[symbol_type] = []
153
+ by_type[symbol_type].append(symbol)
154
+
155
+ # Sort symbols within each type by line number
156
+ for symbol_type in by_type:
157
+ by_type[symbol_type].sort(key=lambda x: x["line"])
158
+
159
+ output_lines = [f"\n📋 文件符号: {filepath}"]
160
+ output_lines.append("─" * 60)
161
+
162
+ # Type names in Chinese
163
+ type_names = {
164
+ "function": "函数",
165
+ "async_function": "异步函数",
166
+ "class": "类",
167
+ "struct": "结构体",
168
+ "enum": "枚举",
169
+ "interface": "接口",
170
+ "trait": "特征",
171
+ "variable": "变量",
172
+ "constant": "常量",
173
+ }
174
+
175
+ for symbol_type, type_symbols in sorted(by_type.items()):
176
+ type_name = type_names.get(symbol_type, symbol_type)
177
+ output_lines.append(f"\n{type_name} ({len(type_symbols)} 个):")
178
+ for symbol in type_symbols:
179
+ line_info = f" 行 {symbol['line']:4d}: {symbol['name']}"
180
+ if 'signature' in symbol and symbol['signature']:
181
+ sig = symbol['signature'].strip()
182
+ if len(sig) > 50:
183
+ sig = sig[:47] + "..."
184
+ line_info += f" - {sig}"
185
+ output_lines.append(line_info)
186
+
187
+ output_lines.append("─" * 60)
188
+ output_lines.append("")
189
+
190
+ return "\n".join(output_lines)
191
+
192
+
32
193
  def file_context_handler(user_input: str, agent_: Any) -> Tuple[str, bool]:
33
194
  """
34
- Extracts file paths from the input, reads their content if they are valid text files
35
- and appends the content to the input.
195
+ Extracts file paths from the input, extracts symbols from those files,
196
+ and appends the symbol list to the input.
36
197
 
37
198
  Args:
38
199
  user_input: The user's input string.
@@ -58,22 +219,111 @@ def file_context_handler(user_input: str, agent_: Any) -> Tuple[str, bool]:
58
219
  return user_input, False
59
220
 
60
221
  added_context = ""
61
- read_code_tool = ReadCodeTool()
62
222
 
63
223
  for abs_path in file_paths:
64
224
  if os.path.isfile(abs_path) and is_text_file(abs_path):
65
- line_count = count_lines(abs_path)
66
- if line_count > 0:
67
- # Use ReadCodeTool to get formatted content
68
- result = read_code_tool._handle_single_file(abs_path)
69
- if result["success"]:
70
- # Remove all original path tokens that map to this absolute path to avoid redundancy
71
- for _raw in abs_to_raws.get(abs_path, []):
72
- user_input = user_input.replace(f"'{_raw}'", "")
73
- # Append the full, formatted output from the tool, which includes headers and line numbers
74
- added_context += "\n" + result["stdout"]
225
+ # Extract symbols from the file
226
+ symbols = extract_symbols_from_file(abs_path)
227
+
228
+ if symbols:
229
+ # Remove all original path tokens that map to this absolute path to avoid redundancy
230
+ for _raw in abs_to_raws.get(abs_path, []):
231
+ user_input = user_input.replace(f"'{_raw}'", "")
232
+ # Append the formatted symbols output
233
+ added_context += format_symbols_output(abs_path, symbols)
75
234
 
76
235
  if added_context:
77
236
  user_input = user_input.strip() + added_context
78
237
 
79
238
  return user_input, False
239
+
240
+
241
+ # ============================================================================
242
+ # 如何添加新语言支持
243
+ # ============================================================================
244
+ #
245
+ # 推荐方式:在 language_extractors/ 目录下创建新文件
246
+ #
247
+ # 1. 创建新文件:jarvis_agent/language_extractors/java_extractor.py
248
+ #
249
+ # # -*- coding: utf-8 -*-
250
+ # """Java language symbol extractor."""
251
+ #
252
+ # from typing import Optional, Any, List
253
+ # from jarvis.jarvis_agent.file_context_handler import register_language_extractor
254
+ # from jarvis.jarvis_code_agent.code_analyzer.symbol_extractor import Symbol
255
+ #
256
+ # def create_java_extractor() -> Optional[Any]:
257
+ # try:
258
+ # from tree_sitter import Language, Parser
259
+ # import tree_sitter_java
260
+ #
261
+ # JAVA_LANGUAGE = tree_sitter_java.language()
262
+ # JAVA_SYMBOL_QUERY = """
263
+ # (method_declaration
264
+ # name: (identifier) @method.name)
265
+ #
266
+ # (class_declaration
267
+ # name: (identifier) @class.name)
268
+ # """
269
+ #
270
+ # class JavaSymbolExtractor:
271
+ # def __init__(self):
272
+ # self.language = JAVA_LANGUAGE
273
+ # self.parser = Parser()
274
+ # self.parser.set_language(self.language)
275
+ # self.symbol_query = JAVA_SYMBOL_QUERY
276
+ #
277
+ # def extract_symbols(self, file_path: str, content: str) -> List[Any]:
278
+ # try:
279
+ # tree = self.parser.parse(bytes(content, "utf8"))
280
+ # query = self.language.query(self.symbol_query)
281
+ # captures = query.captures(tree.root_node)
282
+ #
283
+ # symbols = []
284
+ # for node, name in captures:
285
+ # kind_map = {
286
+ # "method.name": "method",
287
+ # "class.name": "class",
288
+ # }
289
+ # symbol_kind = kind_map.get(name)
290
+ # if symbol_kind:
291
+ # symbols.append(Symbol(
292
+ # name=node.text.decode('utf8'),
293
+ # kind=symbol_kind,
294
+ # file_path=file_path,
295
+ # line_start=node.start_point[0] + 1,
296
+ # line_end=node.end_point[0] + 1,
297
+ # ))
298
+ # return symbols
299
+ # except Exception:
300
+ # return []
301
+ #
302
+ # return JavaSymbolExtractor()
303
+ # except (ImportError, Exception):
304
+ # return None
305
+ #
306
+ # def register_java_extractor() -> None:
307
+ # register_language_extractor(['.java', '.jav'], create_java_extractor)
308
+ #
309
+ #
310
+ # 2. 在 language_extractors/__init__.py 中添加导入和注册:
311
+ #
312
+ # try:
313
+ # from .java_extractor import register_java_extractor
314
+ # register_java_extractor()
315
+ # except (ImportError, Exception):
316
+ # pass
317
+ #
318
+ #
319
+ # 方法2: 在运行时动态注册(不推荐,但可用)
320
+ #
321
+ # from jarvis.jarvis_agent.file_context_handler import register_language_extractor
322
+ #
323
+ # def create_ruby_extractor():
324
+ # # ... 实现提取器 ...
325
+ # return RubyExtractor()
326
+ #
327
+ # register_language_extractor('.rb', create_ruby_extractor)
328
+ #
329
+ # ============================================================================
@@ -7,7 +7,6 @@ import os
7
7
  import tempfile
8
8
 
9
9
  from jarvis.jarvis_utils.methodology import load_methodology, upload_methodology
10
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
11
10
  from jarvis.jarvis_agent.utils import join_prompts
12
11
 
13
12
 
@@ -41,7 +40,7 @@ class FileMethodologyManager:
41
40
  """处理方法论上传"""
42
41
  if not upload_methodology(self.agent.model, other_files=self.agent.files): # type: ignore
43
42
  if self.agent.files:
44
- PrettyOutput.print("文件上传失败,将忽略文件列表", OutputType.WARNING)
43
+ print("⚠️ 文件上传失败,将忽略文件列表")
45
44
  # 上传失败则回退到本地加载
46
45
  self._load_local_methodology()
47
46
  else:
@@ -61,7 +60,7 @@ class FileMethodologyManager:
61
60
  def _handle_files_upload(self):
62
61
  """处理普通文件上传"""
63
62
  if not self.agent.model.upload_files(self.agent.files): # type: ignore
64
- PrettyOutput.print("文件上传失败,将忽略文件列表", OutputType.WARNING)
63
+ print("⚠️ 文件上传失败,将忽略文件列表")
65
64
  else:
66
65
  self.agent.session.prompt = join_prompts([
67
66
  self.agent.session.prompt,
@@ -71,7 +70,7 @@ class FileMethodologyManager:
71
70
  def _handle_local_mode(self):
72
71
  """处理本地模式(不支持文件上传)"""
73
72
  if self.agent.files:
74
- PrettyOutput.print("不支持上传文件,将忽略文件列表", OutputType.WARNING)
73
+ print("⚠️ 不支持上传文件,将忽略文件列表")
75
74
  if self.agent.use_methodology:
76
75
  self._load_local_methodology()
77
76