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
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
from jarvis.jarvis_utils.globals import get_interrupt, set_interrupt
|
|
8
8
|
|
|
9
|
-
from jarvis.jarvis_agent.prompts import
|
|
10
|
-
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
|
+
from jarvis.jarvis_agent.prompts import get_task_analysis_prompt
|
|
11
10
|
from jarvis.jarvis_agent.utils import join_prompts
|
|
12
11
|
from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL, BEFORE_SUMMARY, TASK_COMPLETED
|
|
13
12
|
|
|
@@ -46,7 +45,7 @@ class TaskAnalyzer:
|
|
|
46
45
|
self._process_analysis_loop()
|
|
47
46
|
|
|
48
47
|
except Exception:
|
|
49
|
-
|
|
48
|
+
print("❌ 分析失败")
|
|
50
49
|
finally:
|
|
51
50
|
# 标记已完成一次分析,避免事件回调重复执行
|
|
52
51
|
self._analysis_done = True
|
|
@@ -57,7 +56,30 @@ class TaskAnalyzer:
|
|
|
57
56
|
|
|
58
57
|
def _prepare_analysis_prompt(self, satisfaction_feedback: str) -> str:
|
|
59
58
|
"""准备分析提示"""
|
|
60
|
-
|
|
59
|
+
# 检查是否有 save_memory 工具(工具可用性)
|
|
60
|
+
has_save_memory = False
|
|
61
|
+
# 检查是否有 generate_new_tool 工具
|
|
62
|
+
has_generate_new_tool = False
|
|
63
|
+
try:
|
|
64
|
+
tool_registry = self.agent.get_tool_registry()
|
|
65
|
+
if tool_registry:
|
|
66
|
+
# 检查 save_memory 工具
|
|
67
|
+
save_memory_tool = tool_registry.get_tool("save_memory")
|
|
68
|
+
has_save_memory = save_memory_tool is not None
|
|
69
|
+
|
|
70
|
+
# 检查 generate_new_tool 工具
|
|
71
|
+
generate_tool = tool_registry.get_tool("generate_new_tool")
|
|
72
|
+
has_generate_new_tool = generate_tool is not None
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# 根据配置获取相应的提示词
|
|
77
|
+
analysis_prompt = get_task_analysis_prompt(
|
|
78
|
+
has_save_memory=has_save_memory,
|
|
79
|
+
has_generate_new_tool=has_generate_new_tool
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return join_prompts([analysis_prompt, satisfaction_feedback])
|
|
61
83
|
|
|
62
84
|
def _process_analysis_loop(self):
|
|
63
85
|
"""处理分析循环"""
|
|
@@ -128,7 +150,7 @@ class TaskAnalyzer:
|
|
|
128
150
|
|
|
129
151
|
def _handle_interrupt_with_tool_calls(self, user_input: str) -> str:
|
|
130
152
|
"""处理有工具调用时的中断"""
|
|
131
|
-
if self.agent.
|
|
153
|
+
if self.agent.confirm_callback("检测到有工具调用,是否继续处理工具调用?", True):
|
|
132
154
|
return join_prompts([
|
|
133
155
|
f"被用户中断,用户补充信息为:{user_input}",
|
|
134
156
|
"用户同意继续工具调用。"
|
|
@@ -144,7 +166,7 @@ class TaskAnalyzer:
|
|
|
144
166
|
satisfaction_feedback = ""
|
|
145
167
|
|
|
146
168
|
if not auto_completed and self.agent.use_analysis:
|
|
147
|
-
if self.agent.
|
|
169
|
+
if self.agent.confirm_callback("您对本次任务的完成是否满意?", True):
|
|
148
170
|
satisfaction_feedback = "用户对本次任务的完成表示满意。"
|
|
149
171
|
else:
|
|
150
172
|
feedback = self.agent._multiline_input(
|
|
@@ -158,6 +180,9 @@ class TaskAnalyzer:
|
|
|
158
180
|
satisfaction_feedback = (
|
|
159
181
|
"用户对本次任务的完成不满意,未提供具体反馈意见。"
|
|
160
182
|
)
|
|
183
|
+
elif auto_completed and self.agent.use_analysis:
|
|
184
|
+
# 自动完成模式下,仍然执行分析,但不收集用户反馈
|
|
185
|
+
satisfaction_feedback = "任务已自动完成,无需用户反馈。"
|
|
161
186
|
|
|
162
187
|
return satisfaction_feedback
|
|
163
188
|
|
|
@@ -9,8 +9,6 @@ from rich.table import Table
|
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
|
|
11
11
|
from jarvis.jarvis_agent import (
|
|
12
|
-
OutputType,
|
|
13
|
-
PrettyOutput,
|
|
14
12
|
get_multiline_input,
|
|
15
13
|
user_confirm,
|
|
16
14
|
)
|
|
@@ -31,9 +29,7 @@ class TaskManager:
|
|
|
31
29
|
data_dir = get_data_dir()
|
|
32
30
|
pre_command_path = os.path.join(data_dir, "pre-command")
|
|
33
31
|
if os.path.exists(pre_command_path):
|
|
34
|
-
|
|
35
|
-
f"从{pre_command_path}加载预定义任务...", OutputType.INFO
|
|
36
|
-
)
|
|
32
|
+
print(f"ℹ️ 从{pre_command_path}加载预定义任务...")
|
|
37
33
|
try:
|
|
38
34
|
with open(
|
|
39
35
|
pre_command_path, "r", encoding="utf-8", errors="ignore"
|
|
@@ -43,19 +39,15 @@ class TaskManager:
|
|
|
43
39
|
for name, desc in user_tasks.items():
|
|
44
40
|
if desc:
|
|
45
41
|
tasks[str(name)] = str(desc)
|
|
46
|
-
|
|
47
|
-
f"预定义任务加载完成 {pre_command_path}", OutputType.SUCCESS
|
|
48
|
-
)
|
|
42
|
+
print(f"✅ 预定义任务加载完成 {pre_command_path}")
|
|
49
43
|
except (yaml.YAMLError, OSError):
|
|
50
|
-
|
|
51
|
-
f"预定义任务加载失败 {pre_command_path}", OutputType.ERROR
|
|
52
|
-
)
|
|
44
|
+
print(f"❌ 预定义任务加载失败 {pre_command_path}")
|
|
53
45
|
|
|
54
46
|
# Check .jarvis/pre-command in current directory
|
|
55
47
|
pre_command_path = ".jarvis/pre-command"
|
|
56
48
|
if os.path.exists(pre_command_path):
|
|
57
49
|
abs_path = os.path.abspath(pre_command_path)
|
|
58
|
-
|
|
50
|
+
print(f"ℹ️ 从{abs_path}加载预定义任务...")
|
|
59
51
|
try:
|
|
60
52
|
with open(
|
|
61
53
|
pre_command_path, "r", encoding="utf-8", errors="ignore"
|
|
@@ -65,13 +57,9 @@ class TaskManager:
|
|
|
65
57
|
for name, desc in local_tasks.items():
|
|
66
58
|
if desc:
|
|
67
59
|
tasks[str(name)] = str(desc)
|
|
68
|
-
|
|
69
|
-
f"预定义任务加载完成 {pre_command_path}", OutputType.SUCCESS
|
|
70
|
-
)
|
|
60
|
+
print(f"✅ 预定义任务加载完成 {pre_command_path}")
|
|
71
61
|
except (yaml.YAMLError, OSError):
|
|
72
|
-
|
|
73
|
-
f"预定义任务加载失败 {pre_command_path}", OutputType.ERROR
|
|
74
|
-
)
|
|
62
|
+
print(f"❌ 预定义任务加载失败 {pre_command_path}")
|
|
75
63
|
|
|
76
64
|
return tasks
|
|
77
65
|
|
|
@@ -89,7 +77,7 @@ class TaskManager:
|
|
|
89
77
|
for i, name in enumerate(task_names, 1):
|
|
90
78
|
table.add_row(str(i), name)
|
|
91
79
|
Console().print(table)
|
|
92
|
-
|
|
80
|
+
print("ℹ️ [0] 跳过预定义任务")
|
|
93
81
|
|
|
94
82
|
# Try fzf selection first (with numbered options and a skip option)
|
|
95
83
|
fzf_list = [f"{0:>3} | 跳过预定义任务"] + [
|
|
@@ -104,7 +92,7 @@ class TaskManager:
|
|
|
104
92
|
return ""
|
|
105
93
|
if 1 <= idx <= len(task_names):
|
|
106
94
|
selected_task = tasks[task_names[idx - 1]]
|
|
107
|
-
|
|
95
|
+
print(f"ℹ️ 将要执行任务:\n {selected_task}")
|
|
108
96
|
# 询问是否需要补充信息
|
|
109
97
|
need_additional = user_confirm("需要为此任务添加补充信息吗?", default=False)
|
|
110
98
|
if need_additional:
|
|
@@ -129,9 +117,7 @@ class TaskManager:
|
|
|
129
117
|
return ""
|
|
130
118
|
if 1 <= choice <= len(task_names):
|
|
131
119
|
selected_task = tasks[task_names[choice - 1]]
|
|
132
|
-
|
|
133
|
-
f"将要执行任务:\n {selected_task}", OutputType.INFO
|
|
134
|
-
)
|
|
120
|
+
print(f"ℹ️ 将要执行任务:\n {selected_task}")
|
|
135
121
|
# 询问是否需要补充信息
|
|
136
122
|
need_additional = user_confirm(
|
|
137
123
|
"需要为此任务添加补充信息吗?", default=False
|
|
@@ -144,11 +130,9 @@ class TaskManager:
|
|
|
144
130
|
f"补充信息:\n{additional_input}"
|
|
145
131
|
])
|
|
146
132
|
return selected_task
|
|
147
|
-
|
|
148
|
-
"无效的选择。请选择列表中的一个号码。", OutputType.WARNING
|
|
149
|
-
)
|
|
133
|
+
print("⚠️ 无效的选择。请选择列表中的一个号码。")
|
|
150
134
|
|
|
151
135
|
except (KeyboardInterrupt, EOFError):
|
|
152
136
|
return ""
|
|
153
137
|
except ValueError as val_err:
|
|
154
|
-
|
|
138
|
+
print(f"❌ 选择任务失败: {str(val_err)}")
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
from typing import Any, Tuple, TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from jarvis.jarvis_utils.input import user_confirm
|
|
5
|
-
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
6
5
|
|
|
7
6
|
if TYPE_CHECKING:
|
|
8
7
|
from jarvis.jarvis_agent import Agent
|
|
@@ -31,7 +30,7 @@ def execute_tool_call(response: str, agent: "Agent") -> Tuple[bool, Any]:
|
|
|
31
30
|
f"操作失败:检测到多个操作。一次只能执行一个操作。"
|
|
32
31
|
f"尝试执行的操作:{', '.join([handler.name() for handler in tool_list])}"
|
|
33
32
|
)
|
|
34
|
-
|
|
33
|
+
print(f"⚠️ {error_message}")
|
|
35
34
|
return False, error_message
|
|
36
35
|
|
|
37
36
|
if not tool_list:
|
|
@@ -47,7 +46,7 @@ def execute_tool_call(response: str, agent: "Agent") -> Tuple[bool, Any]:
|
|
|
47
46
|
print(f"✅ {tool_to_execute.name()}执行完成")
|
|
48
47
|
return result
|
|
49
48
|
except Exception as e:
|
|
50
|
-
|
|
49
|
+
print(f"❌ 工具执行失败: {str(e)}")
|
|
51
50
|
return False, str(e)
|
|
52
51
|
|
|
53
52
|
return False, ""
|
|
@@ -7,7 +7,7 @@ from typing import List, Dict, Any, Set
|
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
|
-
from jarvis.jarvis_agent import
|
|
10
|
+
from jarvis.jarvis_agent import user_confirm
|
|
11
11
|
from jarvis.jarvis_agent.share_manager import ShareManager
|
|
12
12
|
from jarvis.jarvis_utils.config import get_central_tool_repo, get_data_dir
|
|
13
13
|
|
|
@@ -18,13 +18,8 @@ class ToolShareManager(ShareManager):
|
|
|
18
18
|
def __init__(self):
|
|
19
19
|
central_repo = get_central_tool_repo()
|
|
20
20
|
if not central_repo:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
OutputType.ERROR,
|
|
24
|
-
)
|
|
25
|
-
PrettyOutput.print(
|
|
26
|
-
"请在配置文件中设置中心工具仓库的Git地址", OutputType.INFO
|
|
27
|
-
)
|
|
21
|
+
print("❌ 错误:未配置中心工具仓库(JARVIS_CENTRAL_TOOL_REPO)")
|
|
22
|
+
print("ℹ️ 请在配置文件中设置中心工具仓库的Git地址")
|
|
28
23
|
raise typer.Exit(code=1)
|
|
29
24
|
|
|
30
25
|
super().__init__(central_repo, "central_tool_repo")
|
|
@@ -52,10 +47,7 @@ class ToolShareManager(ShareManager):
|
|
|
52
47
|
# 只从数据目录的tools目录获取工具
|
|
53
48
|
local_tools_dir = os.path.join(get_data_dir(), "tools")
|
|
54
49
|
if not os.path.exists(local_tools_dir):
|
|
55
|
-
|
|
56
|
-
f"本地工具目录不存在: {local_tools_dir}",
|
|
57
|
-
OutputType.WARNING,
|
|
58
|
-
)
|
|
50
|
+
print(f"⚠️ 本地工具目录不存在: {local_tools_dir}")
|
|
59
51
|
return []
|
|
60
52
|
|
|
61
53
|
# 收集本地工具文件(排除已存在的)
|
|
@@ -84,7 +76,8 @@ class ToolShareManager(ShareManager):
|
|
|
84
76
|
share_list = ["\n将要分享以下工具到中心仓库(注意:文件将被移动而非复制):"]
|
|
85
77
|
for tool in resources:
|
|
86
78
|
share_list.append(f"- {tool['tool_name']} ({tool['filename']})")
|
|
87
|
-
|
|
79
|
+
joined_list = '\n'.join(share_list)
|
|
80
|
+
print(f"⚠️ {joined_list}")
|
|
88
81
|
|
|
89
82
|
if not user_confirm("确认移动这些工具到中心仓库吗?(原文件将被删除)"):
|
|
90
83
|
return []
|
|
@@ -108,10 +101,7 @@ class ToolShareManager(ShareManager):
|
|
|
108
101
|
# 获取本地资源
|
|
109
102
|
local_resources = self.get_local_resources()
|
|
110
103
|
if not local_resources:
|
|
111
|
-
|
|
112
|
-
"没有找到新的工具文件(所有工具可能已存在于中心仓库)",
|
|
113
|
-
OutputType.WARNING,
|
|
114
|
-
)
|
|
104
|
+
print("⚠️ 没有找到新的工具文件(所有工具可能已存在于中心仓库)")
|
|
115
105
|
return
|
|
116
106
|
|
|
117
107
|
# 选择要分享的资源
|
|
@@ -123,17 +113,15 @@ class ToolShareManager(ShareManager):
|
|
|
123
113
|
moved_list = self.share_resources(selected_resources)
|
|
124
114
|
if moved_list:
|
|
125
115
|
# 一次性显示所有移动结果
|
|
126
|
-
|
|
116
|
+
joined_moved = '\n'.join(moved_list)
|
|
117
|
+
print(f"✅ {joined_moved}")
|
|
127
118
|
|
|
128
119
|
# 提交并推送
|
|
129
120
|
self.commit_and_push(len(selected_resources))
|
|
130
121
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
f"原文件已从 {os.path.join(get_data_dir(), 'tools')} 移动到中心仓库",
|
|
134
|
-
OutputType.INFO,
|
|
135
|
-
)
|
|
122
|
+
print("✅ 工具已成功分享到中心仓库!")
|
|
123
|
+
print(f"ℹ️ 原文件已从 {os.path.join(get_data_dir(), 'tools')} 移动到中心仓库")
|
|
136
124
|
|
|
137
125
|
except Exception as e:
|
|
138
|
-
|
|
126
|
+
print(f"❌ 分享工具时出错: {str(e)}")
|
|
139
127
|
raise typer.Exit(code=1)
|
jarvis/jarvis_agent/utils.py
CHANGED
|
@@ -16,7 +16,11 @@ def join_prompts(parts: Iterable[str]) -> str:
|
|
|
16
16
|
- 使用两个换行分隔
|
|
17
17
|
- 不进行额外 strip,保持调用方原样语义
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
try:
|
|
20
|
+
non_empty: List[str] = [p for p in parts if isinstance(p, str) and p]
|
|
21
|
+
except Exception:
|
|
22
|
+
# 防御性处理:若 parts 不可迭代或出现异常,直接返回空字符串
|
|
23
|
+
return ""
|
|
20
24
|
return "\n\n".join(non_empty)
|
|
21
25
|
|
|
22
26
|
def is_auto_complete(response: str) -> bool:
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
WebBridge: WebSocket 交互桥
|
|
4
|
+
- 提供线程安全的广播能力(后续由 WebSocket 服务注册发送函数)
|
|
5
|
+
- 提供阻塞式的多行输入与确认请求(通过 request_* 发起请求,等待浏览器端响应)
|
|
6
|
+
- 适配 Agent 的输入注入接口:web_multiline_input / web_user_confirm
|
|
7
|
+
- 事件约定(发往前端,均为 JSON 对象):
|
|
8
|
+
* {"type":"input_request","mode":"multiline","tip": "...","print_on_empty": true/false,"request_id":"..."}
|
|
9
|
+
* {"type":"confirm_request","tip":"...","default": true/false,"request_id":"..."}
|
|
10
|
+
后续输出事件由输出Sink负责(使用 PrettyOutput.add_sink 接入),不在本桥内实现。
|
|
11
|
+
- 事件约定(来自前端):
|
|
12
|
+
* {"type":"user_input","request_id":"...","text":"..."}
|
|
13
|
+
* {"type":"confirm_response","request_id":"...","value": true/false}
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import threading
|
|
18
|
+
import uuid
|
|
19
|
+
from queue import Queue, Empty
|
|
20
|
+
from typing import Callable, Dict, Optional, Set, Any
|
|
21
|
+
|
|
22
|
+
DEFAULT_WAIT_TIMEOUT = None # 阻塞等待直到收到响应(可按需改为秒数)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class WebBridge:
|
|
26
|
+
"""
|
|
27
|
+
线程安全的 WebSocket 交互桥。
|
|
28
|
+
- 维护一组客户端发送函数(由Web服务注册),用于广播事件
|
|
29
|
+
- 维护挂起的输入/确认请求队列,按 request_id 匹配响应
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_instance_lock = threading.Lock()
|
|
33
|
+
_instance: Optional["WebBridge"] = None
|
|
34
|
+
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
self._clients: Set[Callable[[Dict[str, Any]], None]] = set()
|
|
37
|
+
self._clients_lock = threading.Lock()
|
|
38
|
+
|
|
39
|
+
# 按 request_id 等待的阻塞队列
|
|
40
|
+
self._pending_inputs: Dict[str, Queue] = {}
|
|
41
|
+
self._pending_confirms: Dict[str, Queue] = {}
|
|
42
|
+
self._pending_lock = threading.Lock()
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def instance(cls) -> "WebBridge":
|
|
46
|
+
with cls._instance_lock:
|
|
47
|
+
if cls._instance is None:
|
|
48
|
+
cls._instance = WebBridge()
|
|
49
|
+
return cls._instance
|
|
50
|
+
|
|
51
|
+
# ---------------------------
|
|
52
|
+
# 客户端管理与广播
|
|
53
|
+
# ---------------------------
|
|
54
|
+
def add_client(self, sender: Callable[[Dict[str, Any]], None]) -> None:
|
|
55
|
+
"""
|
|
56
|
+
注册一个客户端发送函数。发送函数需接受一个 dict,并自行完成异步发送。
|
|
57
|
+
例如在 FastAPI/WS 中包装成 enqueue 到事件循环的任务。
|
|
58
|
+
"""
|
|
59
|
+
with self._clients_lock:
|
|
60
|
+
self._clients.add(sender)
|
|
61
|
+
|
|
62
|
+
def remove_client(self, sender: Callable[[Dict[str, Any]], None]) -> None:
|
|
63
|
+
with self._clients_lock:
|
|
64
|
+
if sender in self._clients:
|
|
65
|
+
self._clients.remove(sender)
|
|
66
|
+
|
|
67
|
+
def broadcast(self, payload: Dict[str, Any]) -> None:
|
|
68
|
+
"""
|
|
69
|
+
广播一条消息给所有客户端。失败的客户端不影响其他客户端。
|
|
70
|
+
"""
|
|
71
|
+
with self._clients_lock:
|
|
72
|
+
targets = list(self._clients)
|
|
73
|
+
for send in targets:
|
|
74
|
+
try:
|
|
75
|
+
send(payload)
|
|
76
|
+
except Exception:
|
|
77
|
+
# 静默忽略单个客户端的发送异常
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
# ---------------------------
|
|
81
|
+
# 输入/确认 请求-响应 管理
|
|
82
|
+
# ---------------------------
|
|
83
|
+
def request_multiline_input(self, tip: str, print_on_empty: bool = True, timeout: Optional[float] = DEFAULT_WAIT_TIMEOUT) -> str:
|
|
84
|
+
"""
|
|
85
|
+
发起一个多行输入请求并阻塞等待浏览器返回。
|
|
86
|
+
返回用户输入的文本(可能为空字符串,表示取消)。
|
|
87
|
+
"""
|
|
88
|
+
req_id = uuid.uuid4().hex
|
|
89
|
+
q: Queue = Queue(maxsize=1)
|
|
90
|
+
with self._pending_lock:
|
|
91
|
+
self._pending_inputs[req_id] = q
|
|
92
|
+
|
|
93
|
+
self.broadcast({
|
|
94
|
+
"type": "input_request",
|
|
95
|
+
"mode": "multiline",
|
|
96
|
+
"tip": tip,
|
|
97
|
+
"print_on_empty": bool(print_on_empty),
|
|
98
|
+
"request_id": req_id,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
if timeout is None:
|
|
103
|
+
result = q.get() # 阻塞直到有结果
|
|
104
|
+
else:
|
|
105
|
+
result = q.get(timeout=timeout)
|
|
106
|
+
except Empty:
|
|
107
|
+
result = "" # 超时回退为空
|
|
108
|
+
finally:
|
|
109
|
+
with self._pending_lock:
|
|
110
|
+
self._pending_inputs.pop(req_id, None)
|
|
111
|
+
|
|
112
|
+
# 规范化为字符串
|
|
113
|
+
return str(result or "")
|
|
114
|
+
|
|
115
|
+
def request_confirm(self, tip: str, default: bool = True, timeout: Optional[float] = DEFAULT_WAIT_TIMEOUT) -> bool:
|
|
116
|
+
"""
|
|
117
|
+
发起一个确认请求并阻塞等待浏览器返回。
|
|
118
|
+
返回 True/False,若超时则回退为 default。
|
|
119
|
+
"""
|
|
120
|
+
req_id = uuid.uuid4().hex
|
|
121
|
+
q: Queue = Queue(maxsize=1)
|
|
122
|
+
with self._pending_lock:
|
|
123
|
+
self._pending_confirms[req_id] = q
|
|
124
|
+
|
|
125
|
+
self.broadcast({
|
|
126
|
+
"type": "confirm_request",
|
|
127
|
+
"tip": tip,
|
|
128
|
+
"default": bool(default),
|
|
129
|
+
"request_id": req_id,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
if timeout is None:
|
|
134
|
+
result = q.get()
|
|
135
|
+
else:
|
|
136
|
+
result = q.get(timeout=timeout)
|
|
137
|
+
except Empty:
|
|
138
|
+
result = default
|
|
139
|
+
finally:
|
|
140
|
+
with self._pending_lock:
|
|
141
|
+
self._pending_confirms.pop(req_id, None)
|
|
142
|
+
|
|
143
|
+
return bool(result)
|
|
144
|
+
|
|
145
|
+
# ---------------------------
|
|
146
|
+
# 由 Web 服务回调:注入用户响应
|
|
147
|
+
# ---------------------------
|
|
148
|
+
def post_user_input(self, request_id: str, text: str) -> None:
|
|
149
|
+
"""
|
|
150
|
+
注入浏览器端的多行输入响应。
|
|
151
|
+
"""
|
|
152
|
+
with self._pending_lock:
|
|
153
|
+
q = self._pending_inputs.get(request_id)
|
|
154
|
+
if q:
|
|
155
|
+
try:
|
|
156
|
+
q.put_nowait(text)
|
|
157
|
+
except Exception:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
def post_confirm(self, request_id: str, value: bool) -> None:
|
|
161
|
+
"""
|
|
162
|
+
注入浏览器端的确认响应。
|
|
163
|
+
"""
|
|
164
|
+
with self._pending_lock:
|
|
165
|
+
q = self._pending_confirms.get(request_id)
|
|
166
|
+
if q:
|
|
167
|
+
try:
|
|
168
|
+
q.put_nowait(bool(value))
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------
|
|
174
|
+
# 供 Agent 注入的输入函数
|
|
175
|
+
# ---------------------------
|
|
176
|
+
def web_multiline_input(tip: str, print_on_empty: bool = True) -> str:
|
|
177
|
+
"""
|
|
178
|
+
适配 Agent.multiline_inputer 签名的多行输入函数。
|
|
179
|
+
在 Web 模式下被注入到 Agent,转由浏览器端输入。
|
|
180
|
+
"""
|
|
181
|
+
return WebBridge.instance().request_multiline_input(tip, print_on_empty)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def web_user_confirm(tip: str, default: bool = True) -> bool:
|
|
185
|
+
"""
|
|
186
|
+
适配 Agent.confirm_callback 签名的确认函数。
|
|
187
|
+
在 Web 模式下被注入到 Agent,转由浏览器端确认。
|
|
188
|
+
"""
|
|
189
|
+
return WebBridge.instance().request_confirm(tip, default)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
WebSocketOutputSink: 将 PrettyOutput 的输出事件通过 WebBridge 广播给前端(WebSocket 客户端)
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
- 在 Web 模式启动时,注册该 Sink:
|
|
7
|
+
from jarvis.jarvis_utils.output import PrettyOutput
|
|
8
|
+
from jarvis.jarvis_agent.web_output_sink import WebSocketOutputSink
|
|
9
|
+
PrettyOutput.add_sink(WebSocketOutputSink())
|
|
10
|
+
|
|
11
|
+
- Web 端收到的消息结构:
|
|
12
|
+
{
|
|
13
|
+
"type": "output",
|
|
14
|
+
"payload": {
|
|
15
|
+
"text": "...",
|
|
16
|
+
"output_type": "INFO" | "ERROR" | ...,
|
|
17
|
+
"timestamp": true/false,
|
|
18
|
+
"lang": "markdown" | "python" | ... | null,
|
|
19
|
+
"traceback": false,
|
|
20
|
+
"section": null | "标题",
|
|
21
|
+
"context": { ... } | null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any, Dict
|
|
28
|
+
|
|
29
|
+
from jarvis.jarvis_utils.output import OutputSink, OutputEvent
|
|
30
|
+
from jarvis.jarvis_agent.web_bridge import WebBridge
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WebSocketOutputSink(OutputSink):
|
|
34
|
+
"""将输出事件广播到 WebSocket 前端的 OutputSink 实现。"""
|
|
35
|
+
|
|
36
|
+
def emit(self, event: OutputEvent) -> None:
|
|
37
|
+
try:
|
|
38
|
+
payload: Dict[str, Any] = {
|
|
39
|
+
"type": "output",
|
|
40
|
+
"payload": {
|
|
41
|
+
"text": event.text,
|
|
42
|
+
"output_type": event.output_type.value,
|
|
43
|
+
"timestamp": bool(event.timestamp),
|
|
44
|
+
"lang": event.lang,
|
|
45
|
+
"traceback": bool(event.traceback),
|
|
46
|
+
"section": event.section,
|
|
47
|
+
"context": event.context,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
WebBridge.instance().broadcast(payload)
|
|
51
|
+
except Exception:
|
|
52
|
+
# 广播过程中的异常不应影响其他输出后端
|
|
53
|
+
pass
|