jarvis-ai-assistant 0.3.30__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +458 -152
- jarvis/jarvis_agent/agent_manager.py +17 -13
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +329 -0
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +628 -55
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +34 -10
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +105 -9
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +20 -22
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +31 -6
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +786 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +575 -0
- jarvis/jarvis_c2rust/collector.py +250 -0
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +1254 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +2157 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2983 -0
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +132 -0
- jarvis/jarvis_code_agent/code_agent.py +1371 -220
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
- jarvis/jarvis_code_agent/lint.py +501 -8
- jarvis/jarvis_code_agent/utils.py +141 -0
- jarvis/jarvis_code_analysis/code_review.py +493 -584
- jarvis/jarvis_data/config_schema.json +128 -12
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +82 -75
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
- jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +287 -55
- jarvis/jarvis_multi_agent/main.py +36 -4
- jarvis/jarvis_platform/base.py +524 -202
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +88 -25
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +32 -43
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +53 -55
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +305 -0
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +139 -0
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +336 -0
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +226 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +2 -2
- jarvis/jarvis_stats/stats.py +8 -8
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +21 -23
- jarvis/jarvis_tools/edit_file.py +1019 -132
- jarvis/jarvis_tools/execute_script.py +83 -25
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +14 -21
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1736 -35
- jarvis/jarvis_tools/read_symbols.py +140 -0
- jarvis/jarvis_tools/read_webpage.py +12 -13
- jarvis/jarvis_tools/registry.py +427 -200
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +72 -158
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +18 -18
- jarvis/jarvis_tools/sub_agent.py +36 -43
- jarvis/jarvis_tools/sub_code_agent.py +25 -26
- jarvis/jarvis_tools/virtual_tty.py +55 -33
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +232 -45
- jarvis/jarvis_utils/embedding.py +8 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +225 -36
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +99 -48
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +52 -48
- jarvis/jarvis_utils/utils.py +819 -491
- jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
- jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_agent/edit_file_handler.py +0 -296
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""Agent管理器模块,负责Agent的初始化和任务执行"""
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional, Callable
|
|
4
4
|
|
|
5
5
|
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
|
)
|
|
14
|
-
from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
|
|
15
|
-
from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
|
|
16
12
|
from jarvis.jarvis_agent.task_manager import TaskManager
|
|
17
|
-
from jarvis.
|
|
13
|
+
from jarvis.jarvis_utils.config import is_non_interactive, is_skip_predefined_tasks
|
|
18
14
|
|
|
19
15
|
|
|
20
16
|
class AgentManager:
|
|
@@ -27,6 +23,9 @@ class AgentManager:
|
|
|
27
23
|
restore_session: bool = False,
|
|
28
24
|
use_methodology: Optional[bool] = None,
|
|
29
25
|
use_analysis: Optional[bool] = None,
|
|
26
|
+
multiline_inputer: Optional[Callable[[str], str]] = None,
|
|
27
|
+
confirm_callback: Optional[Callable[[str, bool], bool]] = None,
|
|
28
|
+
non_interactive: Optional[bool] = None,
|
|
30
29
|
):
|
|
31
30
|
self.model_group = model_group
|
|
32
31
|
self.tool_group = tool_group
|
|
@@ -34,6 +33,10 @@ class AgentManager:
|
|
|
34
33
|
self.use_methodology = use_methodology
|
|
35
34
|
self.use_analysis = use_analysis
|
|
36
35
|
self.agent: Optional[Agent] = None
|
|
36
|
+
# 可选:注入输入与确认回调,用于Web模式等前端替代交互
|
|
37
|
+
self.multiline_inputer = multiline_inputer
|
|
38
|
+
self.confirm_callback = confirm_callback
|
|
39
|
+
self.non_interactive = non_interactive
|
|
37
40
|
|
|
38
41
|
def initialize(self) -> Agent:
|
|
39
42
|
"""初始化Agent"""
|
|
@@ -46,19 +49,20 @@ class AgentManager:
|
|
|
46
49
|
self.agent = Agent(
|
|
47
50
|
system_prompt=origin_agent_system_prompt,
|
|
48
51
|
model_group=self.model_group,
|
|
49
|
-
input_handler=[shell_input_handler, builtin_input_handler],
|
|
50
|
-
output_handler=[ToolRegistry()], # type: ignore
|
|
51
52
|
need_summary=False,
|
|
52
53
|
use_methodology=self.use_methodology,
|
|
53
54
|
use_analysis=self.use_analysis,
|
|
55
|
+
multiline_inputer=self.multiline_inputer,
|
|
56
|
+
confirm_callback=self.confirm_callback,
|
|
57
|
+
non_interactive=self.non_interactive,
|
|
54
58
|
)
|
|
55
59
|
|
|
56
60
|
# 尝试恢复会话
|
|
57
61
|
if self.restore_session:
|
|
58
62
|
if self.agent.restore_session():
|
|
59
|
-
|
|
63
|
+
print("✅ 会话已成功恢复。")
|
|
60
64
|
else:
|
|
61
|
-
|
|
65
|
+
print("⚠️ 无法恢复会话。")
|
|
62
66
|
|
|
63
67
|
return self.agent
|
|
64
68
|
|
|
@@ -72,12 +76,12 @@ class AgentManager:
|
|
|
72
76
|
self.agent.run(task_content)
|
|
73
77
|
raise typer.Exit(code=0)
|
|
74
78
|
|
|
75
|
-
#
|
|
76
|
-
if self.agent.first:
|
|
79
|
+
# 处理预定义任务(非交互模式下跳过;支持配置跳过加载;命令行指定任务时跳过)
|
|
80
|
+
if not is_non_interactive() and not is_skip_predefined_tasks() and not task_content and self.agent.first:
|
|
77
81
|
task_manager = TaskManager()
|
|
78
82
|
tasks = task_manager.load_tasks()
|
|
79
83
|
if tasks and (selected_task := task_manager.select_task(tasks)):
|
|
80
|
-
|
|
84
|
+
print(f"ℹ️ 开始执行任务: \n{selected_task}")
|
|
81
85
|
self.agent.run(selected_task)
|
|
82
86
|
raise typer.Exit(code=0)
|
|
83
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
|
-
|
|
59
|
-
|
|
60
|
-
PrettyOutput.print("会话已成功保存。正在退出...", OutputType.SUCCESS)
|
|
58
|
+
print("✅ 会话已成功保存。正在退出...")
|
|
61
59
|
sys.exit(0)
|
|
62
60
|
else:
|
|
63
|
-
|
|
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
|
-
|
|
48
|
+
print(f"❌ Failed to open editor: {e}")
|
|
51
49
|
raise typer.Exit(code=1)
|
|
52
50
|
else:
|
|
53
|
-
|
|
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)
|
jarvis/jarvis_agent/event_bus.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
112
|
+
callbacks = self._get_sorted_callbacks(event)
|
|
113
|
+
for cb in callbacks:
|
|
44
114
|
try:
|
|
45
115
|
cb(**payload)
|
|
46
116
|
except Exception:
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import re
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Tuple, List, Dict, Optional, Callable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# 语言提取器注册表(导出供其他模块使用)
|
|
8
|
+
_LANGUAGE_EXTRACTORS: Dict[str, Callable[[], Optional[Any]]] = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_text_file(filepath: str) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Check if a file is a text file.
|
|
14
|
+
"""
|
|
15
|
+
try:
|
|
16
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
17
|
+
f.read(1024) # Try to read a small chunk
|
|
18
|
+
return True
|
|
19
|
+
except (UnicodeDecodeError, IOError):
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def count_lines(filepath: str) -> int:
|
|
24
|
+
"""
|
|
25
|
+
Count the number of lines in a file.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
|
29
|
+
return sum(1 for _ in f)
|
|
30
|
+
except IOError:
|
|
31
|
+
return 0
|
|
32
|
+
|
|
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
|
+
|
|
193
|
+
def file_context_handler(user_input: str, agent_: Any) -> Tuple[str, bool]:
|
|
194
|
+
"""
|
|
195
|
+
Extracts file paths from the input, extracts symbols from those files,
|
|
196
|
+
and appends the symbol list to the input.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
user_input: The user's input string.
|
|
200
|
+
agent_: The agent instance.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
A tuple containing the modified user input and a boolean indicating if
|
|
204
|
+
further processing should be skipped.
|
|
205
|
+
"""
|
|
206
|
+
# Regex to find paths in single quotes
|
|
207
|
+
raw_paths = re.findall(r"'([^']+)'", user_input)
|
|
208
|
+
# Convert to absolute paths and de-duplicate by absolute path while preserving order
|
|
209
|
+
abs_to_raws: dict[str, list[str]] = {}
|
|
210
|
+
file_paths = []
|
|
211
|
+
for _raw in raw_paths:
|
|
212
|
+
abs_path = os.path.abspath(_raw)
|
|
213
|
+
if abs_path not in abs_to_raws:
|
|
214
|
+
abs_to_raws[abs_path] = []
|
|
215
|
+
file_paths.append(abs_path)
|
|
216
|
+
abs_to_raws[abs_path].append(_raw)
|
|
217
|
+
|
|
218
|
+
if not file_paths:
|
|
219
|
+
return user_input, False
|
|
220
|
+
|
|
221
|
+
added_context = ""
|
|
222
|
+
|
|
223
|
+
for abs_path in file_paths:
|
|
224
|
+
if os.path.isfile(abs_path) and is_text_file(abs_path):
|
|
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)
|
|
234
|
+
|
|
235
|
+
if added_context:
|
|
236
|
+
user_input = user_input.strip() + added_context
|
|
237
|
+
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
+
print("⚠️ 不支持上传文件,将忽略文件列表")
|
|
75
74
|
if self.agent.use_methodology:
|
|
76
75
|
self._load_local_methodology()
|
|
77
76
|
|