jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.0__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 +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -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 +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -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 +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -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 +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
import argparse
|
|
3
2
|
import os
|
|
4
3
|
import re
|
|
5
4
|
import subprocess
|
|
6
|
-
import sys
|
|
7
5
|
import tempfile
|
|
8
6
|
from typing import Any, Dict, Optional
|
|
9
7
|
|
|
8
|
+
import typer
|
|
10
9
|
import yaml # type: ignore
|
|
11
10
|
|
|
12
11
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
@@ -16,10 +15,13 @@ from jarvis.jarvis_utils.git_utils import (
|
|
|
16
15
|
find_git_root_and_cd,
|
|
17
16
|
has_uncommitted_changes,
|
|
18
17
|
)
|
|
18
|
+
from jarvis.jarvis_utils.globals import get_agent, current_agent_name
|
|
19
19
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
20
20
|
from jarvis.jarvis_utils.tag import ct, ot
|
|
21
21
|
from jarvis.jarvis_utils.utils import init_env, is_context_overflow
|
|
22
22
|
|
|
23
|
+
app = typer.Typer(help="Git提交工具")
|
|
24
|
+
|
|
23
25
|
|
|
24
26
|
class GitCommitTool:
|
|
25
27
|
name = "git_commit_agent"
|
|
@@ -79,11 +81,10 @@ class GitCommitTool:
|
|
|
79
81
|
|
|
80
82
|
def _stage_changes(self) -> None:
|
|
81
83
|
"""Stage all changes for commit"""
|
|
82
|
-
|
|
84
|
+
|
|
83
85
|
subprocess.Popen(
|
|
84
86
|
["git", "add", "."], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
85
87
|
).wait()
|
|
86
|
-
print("✅ 添加文件到提交")
|
|
87
88
|
|
|
88
89
|
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
89
90
|
"""Execute automatic commit process with support for multi-line messages and special characters"""
|
|
@@ -104,17 +105,16 @@ class GitCommitTool:
|
|
|
104
105
|
if not has_uncommitted_changes():
|
|
105
106
|
return {"success": True, "stdout": "No changes to commit", "stderr": ""}
|
|
106
107
|
|
|
107
|
-
print("🚀 正在初始化提交流程...")
|
|
108
108
|
self._stage_changes()
|
|
109
109
|
|
|
110
110
|
# 获取差异
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
# 获取文件列表
|
|
113
113
|
files_cmd = ["git", "diff", "--cached", "--name-only"]
|
|
114
114
|
process = subprocess.Popen(
|
|
115
|
-
files_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
115
|
+
files_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
|
116
116
|
)
|
|
117
|
-
files_output = process.communicate()[0]
|
|
117
|
+
files_output = process.communicate()[0]
|
|
118
118
|
files = [f.strip() for f in files_output.split("\n") if f.strip()]
|
|
119
119
|
file_count = len(files)
|
|
120
120
|
|
|
@@ -123,20 +123,21 @@ class GitCommitTool:
|
|
|
123
123
|
["git", "diff", "--cached", "--exit-code"],
|
|
124
124
|
stdout=subprocess.PIPE,
|
|
125
125
|
stderr=subprocess.PIPE,
|
|
126
|
+
text=True,
|
|
126
127
|
)
|
|
127
|
-
diff = process.communicate()[0]
|
|
128
|
-
|
|
128
|
+
diff = process.communicate()[0]
|
|
129
|
+
|
|
129
130
|
try:
|
|
130
131
|
temp_diff_file_path = None
|
|
131
132
|
# 生成提交信息
|
|
132
|
-
print("
|
|
133
|
+
PrettyOutput.print("正在生成提交消息...", OutputType.INFO)
|
|
133
134
|
|
|
134
135
|
# 准备提示信息
|
|
135
136
|
custom_prompt = get_git_commit_prompt()
|
|
136
137
|
base_prompt = (
|
|
137
138
|
custom_prompt
|
|
138
139
|
if custom_prompt
|
|
139
|
-
else
|
|
140
|
+
else """根据代码差异生成提交信息:
|
|
140
141
|
提交信息应使用中文书写
|
|
141
142
|
# 格式模板
|
|
142
143
|
必须使用以下格式:
|
|
@@ -161,22 +162,111 @@ commit信息
|
|
|
161
162
|
{ct("COMMIT_MESSAGE")}
|
|
162
163
|
"""
|
|
163
164
|
|
|
164
|
-
#
|
|
165
|
-
|
|
165
|
+
# 优先从调用方传入的 agent 获取平台与模型
|
|
166
|
+
agent_from_args = args.get("agent")
|
|
167
|
+
|
|
168
|
+
# Get model_group from args
|
|
169
|
+
model_group = args.get("model_group")
|
|
170
|
+
|
|
171
|
+
# Get platform and model based on model_group (thinking mode removed)
|
|
172
|
+
from jarvis.jarvis_utils.config import (
|
|
173
|
+
get_normal_platform_name,
|
|
174
|
+
get_normal_model_name,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
platform_name = None
|
|
178
|
+
model_name = None
|
|
179
|
+
|
|
180
|
+
# 优先根据 model_group 获取(确保配置一致性)
|
|
181
|
+
# 如果 model_group 存在,强制使用它来解析,避免使用 agent.model 中可能不一致的值
|
|
182
|
+
if model_group:
|
|
183
|
+
platform_name = get_normal_platform_name(model_group)
|
|
184
|
+
model_name = get_normal_model_name(model_group)
|
|
185
|
+
else:
|
|
186
|
+
# 如果没有提供 model_group,尝试从传入的 agent 获取
|
|
187
|
+
if (
|
|
188
|
+
agent_from_args
|
|
189
|
+
and hasattr(agent_from_args, "model")
|
|
190
|
+
and getattr(agent_from_args, "model", None)
|
|
191
|
+
):
|
|
192
|
+
try:
|
|
193
|
+
platform_name = agent_from_args.model.platform_name()
|
|
194
|
+
model_name = agent_from_args.model.name()
|
|
195
|
+
if hasattr(agent_from_args.model, "model_group"):
|
|
196
|
+
model_group = agent_from_args.model.model_group
|
|
197
|
+
except Exception:
|
|
198
|
+
# 安全回退到后续逻辑
|
|
199
|
+
platform_name = None
|
|
200
|
+
model_name = None
|
|
201
|
+
|
|
202
|
+
# 如果仍未获取到,使用配置文件中的默认值(传入 None 会读取默认配置)
|
|
203
|
+
if not platform_name:
|
|
204
|
+
platform_name = get_normal_platform_name(None)
|
|
205
|
+
if not model_name:
|
|
206
|
+
model_name = get_normal_model_name(None)
|
|
207
|
+
|
|
208
|
+
# 最后的回退:尝试从全局 agent 获取(仅当仍未获取到时)
|
|
209
|
+
if not platform_name:
|
|
210
|
+
agent = get_agent(current_agent_name)
|
|
211
|
+
if (
|
|
212
|
+
agent
|
|
213
|
+
and hasattr(agent, "model")
|
|
214
|
+
and getattr(agent, "model", None)
|
|
215
|
+
):
|
|
216
|
+
try:
|
|
217
|
+
platform_name = agent.model.platform_name()
|
|
218
|
+
model_name = agent.model.name()
|
|
219
|
+
if not model_group and hasattr(agent.model, "model_group"):
|
|
220
|
+
model_group = agent.model.model_group
|
|
221
|
+
except Exception:
|
|
222
|
+
# 如果全局 agent 也无法获取,使用配置文件默认值
|
|
223
|
+
if not platform_name:
|
|
224
|
+
platform_name = get_normal_platform_name(None)
|
|
225
|
+
if not model_name:
|
|
226
|
+
model_name = get_normal_model_name(None)
|
|
227
|
+
|
|
228
|
+
# Create a new platform instance
|
|
229
|
+
if platform_name:
|
|
230
|
+
platform = PlatformRegistry().create_platform(platform_name)
|
|
231
|
+
if platform and model_name:
|
|
232
|
+
platform.set_model_name(model_name)
|
|
233
|
+
if platform and model_group:
|
|
234
|
+
try:
|
|
235
|
+
platform.set_model_group(model_group)
|
|
236
|
+
except Exception:
|
|
237
|
+
# 兼容早期实现
|
|
238
|
+
platform.model_group = model_group # type: ignore
|
|
239
|
+
else:
|
|
240
|
+
platform = PlatformRegistry().get_normal_platform()
|
|
241
|
+
|
|
242
|
+
# 跳过模型可用性校验:
|
|
243
|
+
# 为避免某些平台/代理不支持 get_model_list 接口导致的噪音日志(如 404),
|
|
244
|
+
# 这里默认不调用 platform.get_model_list() 进行模型可用性校验。
|
|
245
|
+
# 如果未来需要恢复校验,可参考被移除的逻辑。
|
|
246
|
+
# no-op
|
|
247
|
+
|
|
248
|
+
# Ensure platform is not None
|
|
249
|
+
if not platform:
|
|
250
|
+
return {
|
|
251
|
+
"success": False,
|
|
252
|
+
"stdout": "",
|
|
253
|
+
"stderr": "错误:无法创建平台实例",
|
|
254
|
+
}
|
|
255
|
+
|
|
166
256
|
upload_success = False
|
|
167
257
|
|
|
168
258
|
# Check if content is too large
|
|
169
|
-
is_large_content = is_context_overflow(diff)
|
|
259
|
+
is_large_content = is_context_overflow(diff, model_group)
|
|
170
260
|
|
|
171
261
|
if is_large_content:
|
|
172
262
|
if not platform.support_upload_files():
|
|
173
|
-
print("
|
|
263
|
+
PrettyOutput.print("差异文件太大,无法处理", OutputType.ERROR)
|
|
174
264
|
return {
|
|
175
265
|
"success": False,
|
|
176
266
|
"stdout": "",
|
|
177
267
|
"stderr": "错误:差异文件太大,无法处理",
|
|
178
268
|
}
|
|
179
|
-
|
|
269
|
+
|
|
180
270
|
# 创建临时文件并写入差异内容
|
|
181
271
|
with tempfile.NamedTemporaryFile(
|
|
182
272
|
mode="w", suffix=".diff", delete=False
|
|
@@ -184,12 +274,12 @@ commit信息
|
|
|
184
274
|
temp_diff_file_path = temp_diff_file.name
|
|
185
275
|
temp_diff_file.write(diff)
|
|
186
276
|
temp_diff_file.flush()
|
|
187
|
-
|
|
277
|
+
|
|
188
278
|
upload_success = platform.upload_files([temp_diff_file_path])
|
|
189
279
|
if upload_success:
|
|
190
|
-
|
|
280
|
+
pass
|
|
191
281
|
else:
|
|
192
|
-
print("
|
|
282
|
+
PrettyOutput.print("上传代码差异文件失败", OutputType.ERROR)
|
|
193
283
|
return {
|
|
194
284
|
"success": False,
|
|
195
285
|
"stdout": "",
|
|
@@ -198,7 +288,6 @@ commit信息
|
|
|
198
288
|
# 根据上传状态准备完整的提示
|
|
199
289
|
if is_large_content:
|
|
200
290
|
# 尝试生成提交信息
|
|
201
|
-
print("✨ 正在生成提交消息...")
|
|
202
291
|
# 使用上传的文件
|
|
203
292
|
prompt = (
|
|
204
293
|
base_prompt
|
|
@@ -245,24 +334,42 @@ commit信息
|
|
|
245
334
|
{ct("COMMIT_MESSAGE")}
|
|
246
335
|
"""
|
|
247
336
|
commit_message = platform.chat_until_success(prompt)
|
|
248
|
-
print("✅ 生成提交消息")
|
|
249
337
|
|
|
250
338
|
# 执行提交
|
|
251
|
-
|
|
252
|
-
|
|
339
|
+
|
|
340
|
+
# Windows 兼容性:使用 delete=False 避免权限错误
|
|
341
|
+
tmp_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
|
|
342
|
+
tmp_file_path = tmp_file.name
|
|
343
|
+
try:
|
|
253
344
|
tmp_file.write(commit_message)
|
|
254
|
-
tmp_file.
|
|
255
|
-
|
|
256
|
-
commit_cmd = ["git", "commit", "-F",
|
|
257
|
-
subprocess.Popen(
|
|
345
|
+
tmp_file.close() # Windows 需要先关闭文件才能被其他进程读取
|
|
346
|
+
|
|
347
|
+
commit_cmd = ["git", "commit", "-F", tmp_file_path]
|
|
348
|
+
process = subprocess.Popen(
|
|
258
349
|
commit_cmd,
|
|
259
|
-
stdout=subprocess.
|
|
260
|
-
stderr=subprocess.
|
|
261
|
-
|
|
262
|
-
|
|
350
|
+
stdout=subprocess.PIPE,
|
|
351
|
+
stderr=subprocess.PIPE,
|
|
352
|
+
text=True,
|
|
353
|
+
)
|
|
354
|
+
stdout, stderr = process.communicate()
|
|
355
|
+
|
|
356
|
+
if process.returncode != 0:
|
|
357
|
+
# 如果提交失败,重置暂存区
|
|
358
|
+
subprocess.run(["git", "reset", "HEAD"], check=False)
|
|
359
|
+
error_msg = (
|
|
360
|
+
stderr.strip() if stderr else "Unknown git commit error"
|
|
361
|
+
)
|
|
362
|
+
raise Exception(f"Git commit failed: {error_msg}")
|
|
363
|
+
|
|
364
|
+
finally:
|
|
365
|
+
# 手动删除临时文件
|
|
366
|
+
try:
|
|
367
|
+
os.unlink(tmp_file_path)
|
|
368
|
+
except Exception:
|
|
369
|
+
pass
|
|
263
370
|
|
|
264
371
|
commit_hash = self._get_last_commit_hash()
|
|
265
|
-
|
|
372
|
+
|
|
266
373
|
finally:
|
|
267
374
|
# 清理临时差异文件
|
|
268
375
|
if temp_diff_file_path is not None and os.path.exists(
|
|
@@ -271,7 +378,9 @@ commit信息
|
|
|
271
378
|
try:
|
|
272
379
|
os.unlink(temp_diff_file_path)
|
|
273
380
|
except Exception as e:
|
|
274
|
-
print(
|
|
381
|
+
PrettyOutput.print(
|
|
382
|
+
f"无法删除临时文件: {str(e)}", OutputType.WARNING
|
|
383
|
+
)
|
|
275
384
|
|
|
276
385
|
PrettyOutput.print(
|
|
277
386
|
f"提交哈希: {commit_hash}\n提交消息: {commit_message}",
|
|
@@ -298,34 +407,41 @@ commit信息
|
|
|
298
407
|
os.chdir(original_dir)
|
|
299
408
|
|
|
300
409
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
"
|
|
306
|
-
)
|
|
307
|
-
parser.add_argument(
|
|
410
|
+
@app.command()
|
|
411
|
+
def cli(
|
|
412
|
+
root_dir: str = typer.Option(".", "--root-dir", help="Git仓库的根目录路径"),
|
|
413
|
+
prefix: str = typer.Option(
|
|
414
|
+
"",
|
|
308
415
|
"--prefix",
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
parser.add_argument(
|
|
416
|
+
help="提交信息前缀(用空格分隔)",
|
|
417
|
+
),
|
|
418
|
+
suffix: str = typer.Option(
|
|
419
|
+
"",
|
|
314
420
|
"--suffix",
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
421
|
+
help="提交信息后缀(用换行分隔)",
|
|
422
|
+
),
|
|
423
|
+
|
|
424
|
+
model_group: Optional[str] = typer.Option(
|
|
425
|
+
None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
|
|
426
|
+
),
|
|
427
|
+
):
|
|
428
|
+
init_env("欢迎使用 Jarvis-GitCommitTool,您的Git提交助手已准备就绪!")
|
|
320
429
|
tool = GitCommitTool()
|
|
321
430
|
tool.execute(
|
|
322
431
|
{
|
|
323
|
-
"root_dir":
|
|
324
|
-
"prefix":
|
|
325
|
-
"suffix":
|
|
432
|
+
"root_dir": root_dir,
|
|
433
|
+
"prefix": prefix,
|
|
434
|
+
"suffix": suffix,
|
|
435
|
+
|
|
436
|
+
"model_group": model_group,
|
|
326
437
|
}
|
|
327
438
|
)
|
|
328
439
|
|
|
329
440
|
|
|
441
|
+
def main():
|
|
442
|
+
"""Application entry point"""
|
|
443
|
+
app()
|
|
444
|
+
|
|
445
|
+
|
|
330
446
|
if __name__ == "__main__":
|
|
331
|
-
|
|
447
|
+
main()
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import json
|
|
3
3
|
import threading
|
|
4
4
|
import time
|
|
5
|
-
from typing import Any, Callable, Dict, List
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
6
6
|
from urllib.parse import parse_qs, urlencode, urljoin
|
|
7
7
|
|
|
8
|
-
import requests
|
|
8
|
+
import requests # type: ignore[import-untyped]
|
|
9
9
|
|
|
10
10
|
from jarvis.jarvis_mcp import McpClient
|
|
11
11
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
@@ -46,13 +46,17 @@ class SSEMcpClient(McpClient):
|
|
|
46
46
|
self.session.headers.update(extra_headers)
|
|
47
47
|
|
|
48
48
|
# SSE相关属性
|
|
49
|
-
self.sse_response = None
|
|
50
|
-
self.sse_thread = None
|
|
51
|
-
self.messages_endpoint = None
|
|
52
|
-
self.session_id = None
|
|
53
|
-
self.pending_requests
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
self.sse_response: Optional[requests.Response] = None
|
|
50
|
+
self.sse_thread: Optional[threading.Thread] = None
|
|
51
|
+
self.messages_endpoint: Optional[str] = None
|
|
52
|
+
self.session_id: Optional[str] = None
|
|
53
|
+
self.pending_requests: Dict[str, threading.Event] = (
|
|
54
|
+
{}
|
|
55
|
+
) # 存储等待响应的请求 {id: Event}
|
|
56
|
+
self.request_results: Dict[str, Dict[str, Any]] = (
|
|
57
|
+
{}
|
|
58
|
+
) # 存储请求结果 {id: result}
|
|
59
|
+
self.notification_handlers: Dict[str, List[Callable]] = {}
|
|
56
60
|
self.event_lock = threading.Lock()
|
|
57
61
|
self.request_id_counter = 0
|
|
58
62
|
|
|
@@ -123,13 +127,15 @@ class SSEMcpClient(McpClient):
|
|
|
123
127
|
self.sse_response = self.session.get(
|
|
124
128
|
sse_url, stream=True, headers=sse_headers, timeout=30
|
|
125
129
|
)
|
|
126
|
-
self.sse_response
|
|
130
|
+
if self.sse_response:
|
|
131
|
+
self.sse_response.raise_for_status()
|
|
127
132
|
|
|
128
133
|
# 启动事件处理线程
|
|
129
134
|
self.sse_thread = threading.Thread(
|
|
130
135
|
target=self._process_sse_events, daemon=True
|
|
131
136
|
)
|
|
132
|
-
self.sse_thread
|
|
137
|
+
if self.sse_thread:
|
|
138
|
+
self.sse_thread.start()
|
|
133
139
|
|
|
134
140
|
except Exception as e:
|
|
135
141
|
PrettyOutput.print(f"SSE连接失败: {str(e)}", OutputType.ERROR)
|
|
@@ -204,13 +210,14 @@ class SSEMcpClient(McpClient):
|
|
|
204
210
|
|
|
205
211
|
# 调用已注册的处理器
|
|
206
212
|
if method in self.notification_handlers:
|
|
213
|
+
error_lines: List[str] = []
|
|
207
214
|
for handler in self.notification_handlers[method]:
|
|
208
215
|
try:
|
|
209
216
|
handler(params)
|
|
210
217
|
except Exception as e:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
error_lines.append(f"处理通知时出错 ({method}): {e}")
|
|
219
|
+
if error_lines:
|
|
220
|
+
PrettyOutput.print("\n".join(error_lines), OutputType.ERROR)
|
|
214
221
|
except json.JSONDecodeError:
|
|
215
222
|
PrettyOutput.print(f"无法解析SSE事件: {data}", OutputType.WARNING)
|
|
216
223
|
except Exception as e:
|
|
@@ -561,7 +568,7 @@ class SSEMcpClient(McpClient):
|
|
|
561
568
|
if self.sse_response:
|
|
562
569
|
try:
|
|
563
570
|
self.sse_response.close()
|
|
564
|
-
except:
|
|
571
|
+
except Exception:
|
|
565
572
|
pass
|
|
566
573
|
|
|
567
574
|
# 关闭HTTP会话
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
4
|
import subprocess
|
|
5
|
-
from typing import Any, Dict, List
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
6
|
|
|
7
7
|
from jarvis.jarvis_mcp import McpClient
|
|
8
8
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
@@ -17,7 +17,7 @@ class StdioMcpClient(McpClient):
|
|
|
17
17
|
|
|
18
18
|
def __init__(self, config: Dict[str, Any]):
|
|
19
19
|
self.config = config
|
|
20
|
-
self.process = None
|
|
20
|
+
self.process: Optional[subprocess.Popen] = None
|
|
21
21
|
self.protocol_version = "2025-03-26" # MCP协议版本
|
|
22
22
|
self._start_process()
|
|
23
23
|
self._initialize()
|
|
@@ -73,7 +73,7 @@ class StdioMcpClient(McpClient):
|
|
|
73
73
|
f"初始化失败: {response.get('error', 'Unknown error')}"
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
response["result"]
|
|
77
77
|
|
|
78
78
|
# 发送initialized通知 - 使用正确的方法名格式
|
|
79
79
|
self._send_notification("notifications/initialized", {})
|
|
@@ -296,6 +296,6 @@ class StdioMcpClient(McpClient):
|
|
|
296
296
|
self._send_notification("notifications/exit", {})
|
|
297
297
|
# 等待进程结束
|
|
298
298
|
self.process.wait(timeout=1)
|
|
299
|
-
except:
|
|
299
|
+
except Exception:
|
|
300
300
|
# 如果进程没有正常退出,强制终止
|
|
301
301
|
self.process.kill()
|
|
@@ -4,7 +4,7 @@ import threading
|
|
|
4
4
|
from typing import Any, Callable, Dict, List
|
|
5
5
|
from urllib.parse import urljoin
|
|
6
6
|
|
|
7
|
-
import requests
|
|
7
|
+
import requests # type: ignore[import-untyped]
|
|
8
8
|
|
|
9
9
|
from jarvis.jarvis_mcp import McpClient
|
|
10
10
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
@@ -25,6 +25,8 @@ class StreamableMcpClient(McpClient):
|
|
|
25
25
|
self.base_url = config.get("base_url", "")
|
|
26
26
|
if not self.base_url:
|
|
27
27
|
raise ValueError("No base_url specified in config")
|
|
28
|
+
# Normalize base_url to ensure trailing slash for urljoin correctness
|
|
29
|
+
self.base_url = self.base_url.rstrip("/") + "/"
|
|
28
30
|
|
|
29
31
|
# 设置HTTP客户端
|
|
30
32
|
self.session = requests.Session()
|
|
@@ -43,11 +45,17 @@ class StreamableMcpClient(McpClient):
|
|
|
43
45
|
# 添加额外的HTTP头
|
|
44
46
|
extra_headers = config.get("headers", {})
|
|
45
47
|
self.session.headers.update(extra_headers)
|
|
48
|
+
# Request timeouts (connect, read) in seconds; can be overridden via config["timeout"]
|
|
49
|
+
self.timeout = config.get("timeout", (10, 300))
|
|
46
50
|
|
|
47
51
|
# 请求相关属性
|
|
48
|
-
self.pending_requests
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
self.pending_requests: Dict[str, threading.Event] = (
|
|
53
|
+
{}
|
|
54
|
+
) # 存储等待响应的请求 {id: Event}
|
|
55
|
+
self.request_results: Dict[str, Dict[str, Any]] = (
|
|
56
|
+
{}
|
|
57
|
+
) # 存储请求结果 {id: result}
|
|
58
|
+
self.notification_handlers: Dict[str, List[Callable]] = {}
|
|
51
59
|
self.event_lock = threading.Lock()
|
|
52
60
|
self.request_id_counter = 0
|
|
53
61
|
|
|
@@ -144,37 +152,46 @@ class StreamableMcpClient(McpClient):
|
|
|
144
152
|
# 发送请求到Streamable HTTP端点
|
|
145
153
|
mcp_url = urljoin(self.base_url, "mcp")
|
|
146
154
|
response = self.session.post(
|
|
147
|
-
mcp_url, json=request, stream=True
|
|
148
|
-
)
|
|
155
|
+
mcp_url, json=request, stream=True, timeout=self.timeout
|
|
156
|
+
) # 启用流式传输
|
|
149
157
|
response.raise_for_status()
|
|
150
158
|
|
|
151
159
|
# 处理流式响应
|
|
152
160
|
result = None
|
|
161
|
+
warning_lines = []
|
|
162
|
+
error_lines = []
|
|
153
163
|
for line in response.iter_lines(decode_unicode=True):
|
|
154
164
|
if line:
|
|
155
165
|
try:
|
|
156
|
-
|
|
166
|
+
line_data = line
|
|
167
|
+
if isinstance(line_data, str) and line_data.startswith("data:"):
|
|
168
|
+
# Handle SSE-formatted lines like "data: {...}"
|
|
169
|
+
line_data = line_data.split(":", 1)[1].strip()
|
|
170
|
+
data = json.loads(line_data)
|
|
157
171
|
if "id" in data and data["id"] == req_id:
|
|
158
172
|
# 这是我们的请求响应
|
|
159
173
|
result = data
|
|
160
174
|
break
|
|
161
175
|
elif "method" in data:
|
|
162
176
|
# 这是一个通知
|
|
163
|
-
|
|
177
|
+
notify_method = data.get("method", "")
|
|
164
178
|
params = data.get("params", {})
|
|
165
|
-
if
|
|
166
|
-
for handler in self.notification_handlers[
|
|
179
|
+
if notify_method in self.notification_handlers:
|
|
180
|
+
for handler in self.notification_handlers[notify_method]:
|
|
167
181
|
try:
|
|
168
182
|
handler(params)
|
|
169
183
|
except Exception as e:
|
|
170
|
-
|
|
171
|
-
f"处理通知时出错 ({method}): {e}",
|
|
172
|
-
OutputType.ERROR,
|
|
173
|
-
)
|
|
184
|
+
error_lines.append(f"处理通知时出错 ({notify_method}): {e}")
|
|
174
185
|
except json.JSONDecodeError:
|
|
175
|
-
|
|
186
|
+
warning_lines.append(f"无法解析响应: {line}")
|
|
176
187
|
continue
|
|
177
188
|
|
|
189
|
+
if warning_lines:
|
|
190
|
+
PrettyOutput.print("\n".join(warning_lines), OutputType.WARNING)
|
|
191
|
+
if error_lines:
|
|
192
|
+
PrettyOutput.print("\n".join(error_lines), OutputType.ERROR)
|
|
193
|
+
# Ensure response is closed after streaming
|
|
194
|
+
response.close()
|
|
178
195
|
if result is None:
|
|
179
196
|
raise RuntimeError(f"未收到响应: {method}")
|
|
180
197
|
|
|
@@ -202,8 +219,11 @@ class StreamableMcpClient(McpClient):
|
|
|
202
219
|
|
|
203
220
|
# 发送通知到Streamable HTTP端点
|
|
204
221
|
mcp_url = urljoin(self.base_url, "mcp")
|
|
205
|
-
response = self.session.post(
|
|
222
|
+
response = self.session.post(
|
|
223
|
+
mcp_url, json=notification, timeout=self.timeout
|
|
224
|
+
)
|
|
206
225
|
response.raise_for_status()
|
|
226
|
+
response.close()
|
|
207
227
|
|
|
208
228
|
except Exception as e:
|
|
209
229
|
PrettyOutput.print(f"发送通知失败: {str(e)}", OutputType.ERROR)
|