jarvis-ai-assistant 0.7.8__py3-none-any.whl → 1.0.2__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 +567 -222
- jarvis/jarvis_agent/agent_manager.py +19 -12
- jarvis/jarvis_agent/builtin_input_handler.py +79 -11
- jarvis/jarvis_agent/config_editor.py +7 -2
- jarvis/jarvis_agent/event_bus.py +24 -13
- jarvis/jarvis_agent/events.py +19 -1
- jarvis/jarvis_agent/file_context_handler.py +67 -64
- jarvis/jarvis_agent/file_methodology_manager.py +38 -24
- jarvis/jarvis_agent/jarvis.py +186 -114
- jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
- jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
- jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
- jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
- jarvis/jarvis_agent/language_support_info.py +250 -219
- jarvis/jarvis_agent/main.py +19 -23
- jarvis/jarvis_agent/memory_manager.py +9 -6
- jarvis/jarvis_agent/methodology_share_manager.py +21 -15
- jarvis/jarvis_agent/output_handler.py +4 -2
- jarvis/jarvis_agent/prompt_builder.py +7 -6
- jarvis/jarvis_agent/prompt_manager.py +113 -8
- jarvis/jarvis_agent/prompts.py +317 -85
- jarvis/jarvis_agent/protocols.py +5 -2
- jarvis/jarvis_agent/run_loop.py +192 -32
- jarvis/jarvis_agent/session_manager.py +7 -3
- jarvis/jarvis_agent/share_manager.py +23 -13
- jarvis/jarvis_agent/shell_input_handler.py +12 -8
- jarvis/jarvis_agent/stdio_redirect.py +25 -26
- jarvis/jarvis_agent/task_analyzer.py +29 -23
- jarvis/jarvis_agent/task_list.py +869 -0
- jarvis/jarvis_agent/task_manager.py +26 -23
- jarvis/jarvis_agent/tool_executor.py +6 -5
- jarvis/jarvis_agent/tool_share_manager.py +24 -14
- jarvis/jarvis_agent/user_interaction.py +3 -3
- jarvis/jarvis_agent/utils.py +9 -1
- jarvis/jarvis_agent/web_bridge.py +37 -17
- jarvis/jarvis_agent/web_output_sink.py +5 -2
- jarvis/jarvis_agent/web_server.py +165 -36
- jarvis/jarvis_c2rust/__init__.py +1 -1
- jarvis/jarvis_c2rust/cli.py +260 -141
- jarvis/jarvis_c2rust/collector.py +37 -18
- jarvis/jarvis_c2rust/constants.py +60 -0
- jarvis/jarvis_c2rust/library_replacer.py +242 -1010
- jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
- jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
- jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
- jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
- jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
- jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
- jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
- jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
- jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
- jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
- jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
- jarvis/jarvis_c2rust/loaders.py +28 -10
- jarvis/jarvis_c2rust/models.py +5 -2
- jarvis/jarvis_c2rust/optimizer.py +192 -1974
- jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
- jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
- jarvis/jarvis_c2rust/optimizer_config.py +49 -0
- jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
- jarvis/jarvis_c2rust/optimizer_options.py +48 -0
- jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
- jarvis/jarvis_c2rust/optimizer_report.py +52 -0
- jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
- jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
- jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
- jarvis/jarvis_c2rust/scanner.py +229 -166
- jarvis/jarvis_c2rust/transpiler.py +531 -2732
- jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
- jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
- jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
- jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
- jarvis/jarvis_c2rust/transpiler_config.py +178 -0
- jarvis/jarvis_c2rust/transpiler_context.py +122 -0
- jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
- jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
- jarvis/jarvis_c2rust/transpiler_git.py +163 -0
- jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
- jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
- jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
- jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
- jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
- jarvis/jarvis_c2rust/utils.py +269 -79
- jarvis/jarvis_code_agent/after_change.py +233 -0
- jarvis/jarvis_code_agent/build_validation_config.py +37 -30
- jarvis/jarvis_code_agent/builtin_rules.py +68 -0
- jarvis/jarvis_code_agent/code_agent.py +976 -1517
- jarvis/jarvis_code_agent/code_agent_build.py +227 -0
- jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
- jarvis/jarvis_code_agent/code_agent_git.py +525 -0
- jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
- jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
- jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
- jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
- jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
- jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
- jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
- jarvis/jarvis_code_agent/lint.py +223 -524
- jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
- jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
- jarvis/jarvis_code_agent/rules/code_review.md +115 -0
- jarvis/jarvis_code_agent/rules/documentation.md +165 -0
- jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
- jarvis/jarvis_code_agent/rules/performance.md +158 -0
- jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
- jarvis/jarvis_code_agent/rules/security.md +160 -0
- jarvis/jarvis_code_agent/rules/tdd.md +78 -0
- jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
- jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
- jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
- jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
- jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
- jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
- jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
- jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
- jarvis/jarvis_code_agent/utils.py +36 -26
- jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
- jarvis/jarvis_code_analysis/code_review.py +64 -33
- jarvis/jarvis_data/config_schema.json +285 -192
- jarvis/jarvis_git_squash/main.py +8 -6
- jarvis/jarvis_git_utils/git_commiter.py +53 -76
- jarvis/jarvis_mcp/__init__.py +5 -2
- jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
- jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
- jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
- jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
- jarvis/jarvis_methodology/main.py +48 -39
- jarvis/jarvis_multi_agent/__init__.py +56 -23
- jarvis/jarvis_multi_agent/main.py +15 -18
- jarvis/jarvis_platform/base.py +179 -111
- jarvis/jarvis_platform/human.py +27 -16
- jarvis/jarvis_platform/kimi.py +52 -45
- jarvis/jarvis_platform/openai.py +101 -40
- jarvis/jarvis_platform/registry.py +51 -33
- jarvis/jarvis_platform/tongyi.py +68 -38
- jarvis/jarvis_platform/yuanbao.py +59 -43
- jarvis/jarvis_platform_manager/main.py +68 -76
- jarvis/jarvis_platform_manager/service.py +24 -14
- jarvis/jarvis_rag/README_CONFIG.md +314 -0
- jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
- jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
- jarvis/jarvis_rag/__init__.py +57 -4
- jarvis/jarvis_rag/cache.py +3 -1
- jarvis/jarvis_rag/cli.py +48 -68
- jarvis/jarvis_rag/embedding_interface.py +39 -0
- jarvis/jarvis_rag/embedding_manager.py +7 -230
- jarvis/jarvis_rag/embeddings/__init__.py +41 -0
- jarvis/jarvis_rag/embeddings/base.py +114 -0
- jarvis/jarvis_rag/embeddings/cohere.py +66 -0
- jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
- jarvis/jarvis_rag/embeddings/local.py +260 -0
- jarvis/jarvis_rag/embeddings/openai.py +62 -0
- jarvis/jarvis_rag/embeddings/registry.py +293 -0
- jarvis/jarvis_rag/llm_interface.py +8 -6
- jarvis/jarvis_rag/query_rewriter.py +8 -9
- jarvis/jarvis_rag/rag_pipeline.py +61 -52
- jarvis/jarvis_rag/reranker.py +7 -75
- jarvis/jarvis_rag/reranker_interface.py +32 -0
- jarvis/jarvis_rag/rerankers/__init__.py +41 -0
- jarvis/jarvis_rag/rerankers/base.py +109 -0
- jarvis/jarvis_rag/rerankers/cohere.py +67 -0
- jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
- jarvis/jarvis_rag/rerankers/jina.py +79 -0
- jarvis/jarvis_rag/rerankers/local.py +89 -0
- jarvis/jarvis_rag/rerankers/registry.py +293 -0
- jarvis/jarvis_rag/retriever.py +58 -43
- jarvis/jarvis_sec/__init__.py +66 -141
- jarvis/jarvis_sec/agents.py +21 -17
- jarvis/jarvis_sec/analysis.py +80 -33
- jarvis/jarvis_sec/checkers/__init__.py +7 -13
- jarvis/jarvis_sec/checkers/c_checker.py +356 -164
- jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
- jarvis/jarvis_sec/cli.py +43 -21
- jarvis/jarvis_sec/clustering.py +430 -272
- jarvis/jarvis_sec/file_manager.py +99 -55
- jarvis/jarvis_sec/parsers.py +9 -6
- jarvis/jarvis_sec/prompts.py +4 -3
- jarvis/jarvis_sec/report.py +44 -22
- jarvis/jarvis_sec/review.py +180 -107
- jarvis/jarvis_sec/status.py +50 -41
- jarvis/jarvis_sec/types.py +3 -0
- jarvis/jarvis_sec/utils.py +160 -83
- jarvis/jarvis_sec/verification.py +411 -181
- jarvis/jarvis_sec/workflow.py +132 -21
- jarvis/jarvis_smart_shell/main.py +28 -41
- jarvis/jarvis_stats/cli.py +14 -12
- jarvis/jarvis_stats/stats.py +28 -19
- jarvis/jarvis_stats/storage.py +14 -8
- jarvis/jarvis_stats/visualizer.py +12 -7
- jarvis/jarvis_tools/base.py +5 -2
- jarvis/jarvis_tools/clear_memory.py +13 -9
- jarvis/jarvis_tools/cli/main.py +23 -18
- jarvis/jarvis_tools/edit_file.py +572 -873
- jarvis/jarvis_tools/execute_script.py +10 -7
- jarvis/jarvis_tools/file_analyzer.py +7 -8
- jarvis/jarvis_tools/meta_agent.py +287 -0
- jarvis/jarvis_tools/methodology.py +5 -3
- jarvis/jarvis_tools/read_code.py +305 -1438
- jarvis/jarvis_tools/read_symbols.py +50 -17
- jarvis/jarvis_tools/read_webpage.py +19 -18
- jarvis/jarvis_tools/registry.py +435 -156
- jarvis/jarvis_tools/retrieve_memory.py +16 -11
- jarvis/jarvis_tools/save_memory.py +8 -6
- jarvis/jarvis_tools/search_web.py +31 -31
- jarvis/jarvis_tools/sub_agent.py +32 -28
- jarvis/jarvis_tools/sub_code_agent.py +44 -60
- jarvis/jarvis_tools/task_list_manager.py +1811 -0
- jarvis/jarvis_tools/virtual_tty.py +29 -19
- jarvis/jarvis_utils/__init__.py +4 -0
- jarvis/jarvis_utils/builtin_replace_map.py +2 -1
- jarvis/jarvis_utils/clipboard.py +9 -8
- jarvis/jarvis_utils/collections.py +331 -0
- jarvis/jarvis_utils/config.py +699 -194
- jarvis/jarvis_utils/dialogue_recorder.py +294 -0
- jarvis/jarvis_utils/embedding.py +6 -3
- jarvis/jarvis_utils/file_processors.py +7 -1
- jarvis/jarvis_utils/fzf.py +9 -3
- jarvis/jarvis_utils/git_utils.py +71 -42
- jarvis/jarvis_utils/globals.py +116 -32
- jarvis/jarvis_utils/http.py +6 -2
- jarvis/jarvis_utils/input.py +318 -83
- jarvis/jarvis_utils/jsonnet_compat.py +119 -104
- jarvis/jarvis_utils/methodology.py +37 -28
- jarvis/jarvis_utils/output.py +201 -44
- jarvis/jarvis_utils/utils.py +986 -628
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
- jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
- jarvis/jarvis_tools/generate_new_tool.py +0 -205
- jarvis/jarvis_tools/lsp_client.py +0 -1552
- jarvis/jarvis_tools/rewrite_file.py +0 -105
- jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
jarvis/jarvis_utils/utils.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import errno
|
|
3
|
+
|
|
1
4
|
# -*- coding: utf-8 -*-
|
|
2
5
|
import hashlib
|
|
3
6
|
import json
|
|
@@ -5,27 +8,32 @@ import os
|
|
|
5
8
|
import signal
|
|
6
9
|
import subprocess
|
|
7
10
|
import sys
|
|
8
|
-
import time
|
|
9
|
-
import atexit
|
|
10
|
-
import errno
|
|
11
11
|
import threading
|
|
12
|
+
import time
|
|
13
|
+
from datetime import date
|
|
14
|
+
from datetime import datetime
|
|
12
15
|
from pathlib import Path
|
|
13
|
-
from typing import Any
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
from typing import Any
|
|
17
|
+
from typing import Callable
|
|
18
|
+
from typing import Dict
|
|
19
|
+
from typing import List
|
|
20
|
+
from typing import Optional
|
|
21
|
+
from typing import Tuple
|
|
22
|
+
|
|
23
|
+
import yaml
|
|
17
24
|
from rich.align import Align
|
|
18
25
|
from rich.console import RenderableType
|
|
19
26
|
|
|
20
27
|
from jarvis import __version__
|
|
21
|
-
from jarvis.jarvis_utils.config import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
set_global_env_data,
|
|
25
|
-
)
|
|
28
|
+
from jarvis.jarvis_utils.config import get_data_dir
|
|
29
|
+
from jarvis.jarvis_utils.config import get_max_big_content_size
|
|
30
|
+
from jarvis.jarvis_utils.config import set_global_env_data
|
|
26
31
|
from jarvis.jarvis_utils.embedding import get_context_token_count
|
|
27
|
-
from jarvis.jarvis_utils.globals import get_in_chat
|
|
32
|
+
from jarvis.jarvis_utils.globals import get_in_chat
|
|
33
|
+
from jarvis.jarvis_utils.globals import get_interrupt
|
|
34
|
+
from jarvis.jarvis_utils.globals import set_interrupt
|
|
28
35
|
from jarvis.jarvis_utils.input import user_confirm
|
|
36
|
+
from jarvis.jarvis_utils.output import PrettyOutput
|
|
29
37
|
|
|
30
38
|
# 向后兼容:导出 get_yes_no 供外部模块引用
|
|
31
39
|
get_yes_no = user_confirm
|
|
@@ -107,20 +115,19 @@ def is_editable_install() -> bool:
|
|
|
107
115
|
"""
|
|
108
116
|
检测当前 Jarvis 是否以可编辑模式安装(pip/uv install -e .)。
|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
判断顺序(多策略并行,任意命中即认为是可编辑安装):
|
|
111
119
|
1. 读取 PEP 610 的 direct_url.json(dir_info.editable)
|
|
112
|
-
2. 兼容旧式 .egg-link
|
|
120
|
+
2. 兼容旧式 .egg-link / .pth 可编辑安装
|
|
113
121
|
3. 启发式回退:源码路径上游存在 .git 且不在 site-packages/dist-packages
|
|
114
122
|
"""
|
|
115
123
|
# 优先使用 importlib.metadata 读取 distribution 的 direct_url.json
|
|
116
124
|
try:
|
|
117
125
|
import importlib.metadata as metadata # Python 3.8+
|
|
118
126
|
except Exception:
|
|
119
|
-
metadata
|
|
127
|
+
# 如果importlib.metadata不可用,直接返回None,表示无法检查
|
|
128
|
+
return False
|
|
120
129
|
|
|
121
130
|
def _check_direct_url() -> Optional[bool]:
|
|
122
|
-
if metadata is None:
|
|
123
|
-
return None
|
|
124
131
|
candidates = ["jarvis-ai-assistant", "jarvis_ai_assistant"]
|
|
125
132
|
for name in candidates:
|
|
126
133
|
try:
|
|
@@ -134,10 +141,14 @@ def is_editable_install() -> bool:
|
|
|
134
141
|
if f.name == "direct_url.json":
|
|
135
142
|
p = Path(str(dist.locate_file(f)))
|
|
136
143
|
if p.exists():
|
|
137
|
-
with open(
|
|
144
|
+
with open(
|
|
145
|
+
p, "r", encoding="utf-8", errors="ignore"
|
|
146
|
+
) as fp:
|
|
138
147
|
info = json.load(fp)
|
|
139
148
|
dir_info = info.get("dir_info") or {}
|
|
140
|
-
if isinstance(dir_info, dict) and bool(
|
|
149
|
+
if isinstance(dir_info, dict) and bool(
|
|
150
|
+
dir_info.get("editable")
|
|
151
|
+
):
|
|
141
152
|
return True
|
|
142
153
|
# 兼容部分工具可能写入顶层 editable 字段
|
|
143
154
|
if bool(info.get("editable")):
|
|
@@ -151,15 +162,28 @@ def is_editable_install() -> bool:
|
|
|
151
162
|
|
|
152
163
|
res = _check_direct_url()
|
|
153
164
|
if res is True:
|
|
165
|
+
# 明确标记为 editable,直接返回 True
|
|
154
166
|
return True
|
|
155
|
-
|
|
156
|
-
# 明确不是可编辑安装
|
|
157
|
-
return False
|
|
167
|
+
# 对于 res 为 False/None 的情况,不直接下结论,继续使用后续多种兼容策略进行判断
|
|
158
168
|
|
|
159
|
-
# 兼容旧式 .egg-link 可编辑安装
|
|
169
|
+
# 兼容旧式 .egg-link / .pth 可编辑安装
|
|
160
170
|
try:
|
|
161
171
|
module_path = Path(__file__).resolve()
|
|
162
172
|
pkg_root = module_path.parent.parent # jarvis 包根目录
|
|
173
|
+
|
|
174
|
+
# 1) 基于 sys.path 的 .egg-link / .pth 检测(更贴近测试场景,依赖 os.path.exists)
|
|
175
|
+
import os as _os
|
|
176
|
+
|
|
177
|
+
for entry in sys.path:
|
|
178
|
+
try:
|
|
179
|
+
egg_link = Path(entry) / f"{pkg_root.name}.egg-link"
|
|
180
|
+
pth_file = Path(entry) / f"{pkg_root.name}.pth"
|
|
181
|
+
if _os.path.exists(str(egg_link)) or _os.path.exists(str(pth_file)):
|
|
182
|
+
return True
|
|
183
|
+
except Exception:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
# 2) 兼容更通用的 .egg-link 形式(读取指向源码路径)
|
|
163
187
|
for entry in sys.path:
|
|
164
188
|
try:
|
|
165
189
|
p = Path(entry)
|
|
@@ -186,7 +210,9 @@ def is_editable_install() -> bool:
|
|
|
186
210
|
try:
|
|
187
211
|
parents = list(Path(__file__).resolve().parents)
|
|
188
212
|
has_git = any((d / ".git").exists() for d in parents)
|
|
189
|
-
in_site = any(
|
|
213
|
+
in_site = any(
|
|
214
|
+
("site-packages" in str(d)) or ("dist-packages" in str(d)) for d in parents
|
|
215
|
+
)
|
|
190
216
|
if has_git and not in_site:
|
|
191
217
|
return True
|
|
192
218
|
except Exception:
|
|
@@ -284,13 +310,17 @@ def _acquire_single_instance_lock(lock_name: str = "instance.lock") -> None:
|
|
|
284
310
|
if lock_path.exists():
|
|
285
311
|
pid = _read_lock_owner_pid(lock_path)
|
|
286
312
|
if pid and _is_process_alive(pid):
|
|
287
|
-
|
|
313
|
+
PrettyOutput.auto_print(
|
|
314
|
+
f"⚠️ 检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。\n如果确认不存在正在运行的实例,请删除锁文件后重试:{lock_path}"
|
|
315
|
+
)
|
|
288
316
|
sys.exit(0)
|
|
289
317
|
# 尝试移除陈旧锁
|
|
290
318
|
try:
|
|
291
319
|
lock_path.unlink()
|
|
292
320
|
except Exception:
|
|
293
|
-
|
|
321
|
+
PrettyOutput.auto_print(
|
|
322
|
+
f"❌ 无法删除旧锁文件:{lock_path},请手动清理后重试。"
|
|
323
|
+
)
|
|
294
324
|
sys.exit(1)
|
|
295
325
|
|
|
296
326
|
# 原子创建锁文件,避免并发竞争
|
|
@@ -313,12 +343,16 @@ def _acquire_single_instance_lock(lock_name: str = "instance.lock") -> None:
|
|
|
313
343
|
# 极端并发下再次校验
|
|
314
344
|
pid = _read_lock_owner_pid(lock_path)
|
|
315
345
|
if pid and _is_process_alive(pid):
|
|
316
|
-
|
|
346
|
+
PrettyOutput.auto_print(
|
|
347
|
+
f"⚠️ 检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。"
|
|
348
|
+
)
|
|
317
349
|
sys.exit(0)
|
|
318
|
-
|
|
350
|
+
PrettyOutput.auto_print(
|
|
351
|
+
f"❌ 锁文件已存在但可能为陈旧状态:{lock_path},请手动删除后重试。"
|
|
352
|
+
)
|
|
319
353
|
sys.exit(1)
|
|
320
354
|
except Exception as e:
|
|
321
|
-
|
|
355
|
+
PrettyOutput.auto_print(f"❌ 创建实例锁失败: {e}")
|
|
322
356
|
sys.exit(1)
|
|
323
357
|
|
|
324
358
|
|
|
@@ -328,8 +362,9 @@ def _check_pip_updates() -> bool:
|
|
|
328
362
|
返回:
|
|
329
363
|
bool: 是否执行了更新(成功更新返回True以触发重启)
|
|
330
364
|
"""
|
|
331
|
-
import urllib.request
|
|
332
365
|
import urllib.error
|
|
366
|
+
import urllib.request
|
|
367
|
+
|
|
333
368
|
from packaging import version
|
|
334
369
|
|
|
335
370
|
# 检查上次检查日期
|
|
@@ -359,7 +394,9 @@ def _check_pip_updates() -> bool:
|
|
|
359
394
|
latest_ver = version.parse(latest_version)
|
|
360
395
|
|
|
361
396
|
if latest_ver > current_ver:
|
|
362
|
-
|
|
397
|
+
PrettyOutput.auto_print(
|
|
398
|
+
f"ℹ️ 检测到新版本 v{latest_version} (当前版本: v{__version__})"
|
|
399
|
+
)
|
|
363
400
|
|
|
364
401
|
# 检测是否在虚拟环境中
|
|
365
402
|
hasattr(sys, "real_prefix") or (
|
|
@@ -368,6 +405,7 @@ def _check_pip_updates() -> bool:
|
|
|
368
405
|
|
|
369
406
|
# 检测是否可用 uv(优先使用虚拟环境内的uv,其次PATH中的uv)
|
|
370
407
|
from shutil import which as _which
|
|
408
|
+
|
|
371
409
|
uv_executable: Optional[str] = None
|
|
372
410
|
if sys.platform == "win32":
|
|
373
411
|
venv_uv = Path(sys.prefix) / "Scripts" / "uv.exe"
|
|
@@ -384,6 +422,7 @@ def _check_pip_updates() -> bool:
|
|
|
384
422
|
from jarvis.jarvis_utils.utils import (
|
|
385
423
|
is_rag_installed as _is_rag_installed,
|
|
386
424
|
) # 延迟导入避免潜在循环依赖
|
|
425
|
+
|
|
387
426
|
rag_installed = _is_rag_installed()
|
|
388
427
|
|
|
389
428
|
# 更新命令
|
|
@@ -406,7 +445,7 @@ def _check_pip_updates() -> bool:
|
|
|
406
445
|
|
|
407
446
|
# 自动尝试升级(失败时提供手动命令)
|
|
408
447
|
try:
|
|
409
|
-
|
|
448
|
+
PrettyOutput.auto_print("ℹ️ 正在自动更新 Jarvis,请稍候...")
|
|
410
449
|
result = subprocess.run(
|
|
411
450
|
cmd_list,
|
|
412
451
|
capture_output=True,
|
|
@@ -416,18 +455,20 @@ def _check_pip_updates() -> bool:
|
|
|
416
455
|
timeout=600,
|
|
417
456
|
)
|
|
418
457
|
if result.returncode == 0:
|
|
419
|
-
|
|
458
|
+
PrettyOutput.auto_print("✅ 更新成功,正在重启以应用新版本...")
|
|
420
459
|
# 更新检查日期,避免重复触发
|
|
421
460
|
last_check_file.write_text(today_str)
|
|
422
461
|
return True
|
|
423
462
|
else:
|
|
424
463
|
err = (result.stderr or result.stdout or "").strip()
|
|
425
464
|
if err:
|
|
426
|
-
|
|
427
|
-
|
|
465
|
+
PrettyOutput.auto_print(
|
|
466
|
+
f"⚠️ 自动更新失败,错误信息(已截断): {err[:500]}"
|
|
467
|
+
)
|
|
468
|
+
PrettyOutput.auto_print(f"ℹ️ 请手动执行以下命令更新: {update_cmd}")
|
|
428
469
|
except Exception:
|
|
429
|
-
|
|
430
|
-
|
|
470
|
+
PrettyOutput.auto_print("⚠️ 自动更新出现异常,已切换为手动更新方式。")
|
|
471
|
+
PrettyOutput.auto_print(f"ℹ️ 请手动执行以下命令更新: {update_cmd}")
|
|
431
472
|
|
|
432
473
|
# 更新检查日期
|
|
433
474
|
last_check_file.write_text(today_str)
|
|
@@ -469,8 +510,8 @@ def _check_jarvis_updates() -> bool:
|
|
|
469
510
|
def _show_usage_stats(welcome_str: str) -> None:
|
|
470
511
|
"""显示Jarvis使用统计信息"""
|
|
471
512
|
try:
|
|
472
|
-
|
|
473
|
-
from rich.console import
|
|
513
|
+
from rich.console import Console
|
|
514
|
+
from rich.console import Group
|
|
474
515
|
from rich.panel import Panel
|
|
475
516
|
from rich.table import Table
|
|
476
517
|
from rich.text import Text
|
|
@@ -496,7 +537,7 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
|
496
537
|
|
|
497
538
|
# 复用存储实例,避免重复创建
|
|
498
539
|
storage = StatsStorage()
|
|
499
|
-
|
|
540
|
+
|
|
500
541
|
# 一次性读取元数据,避免重复读取
|
|
501
542
|
try:
|
|
502
543
|
meta = storage._load_json(storage.meta_file)
|
|
@@ -525,7 +566,7 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
|
525
566
|
for metric in all_metrics:
|
|
526
567
|
# 从批量读取的数据中获取总量
|
|
527
568
|
total = metric_totals.get(metric, 0.0)
|
|
528
|
-
|
|
569
|
+
|
|
529
570
|
if not total or total <= 0:
|
|
530
571
|
continue
|
|
531
572
|
|
|
@@ -569,138 +610,75 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
|
569
610
|
# 如果有 generated,则计算采纳率
|
|
570
611
|
if generated_commits > 0:
|
|
571
612
|
adoption_rate = (accepted_commits / generated_commits) * 100
|
|
572
|
-
categorized_stats["adoption"]["metrics"][
|
|
573
|
-
"adoption_rate"
|
|
574
|
-
|
|
575
|
-
categorized_stats["adoption"]["metrics"][
|
|
576
|
-
"
|
|
577
|
-
] = f"{accepted_commits}/{generated_commits}"
|
|
578
|
-
|
|
579
|
-
# 构建输出
|
|
580
|
-
has_data = False
|
|
581
|
-
stats_output = []
|
|
582
|
-
|
|
583
|
-
for category, data in categorized_stats.items():
|
|
584
|
-
if data["metrics"]:
|
|
585
|
-
has_data = True
|
|
586
|
-
stats_output.append((data["title"], data["metrics"], data["suffix"]))
|
|
587
|
-
|
|
588
|
-
# 显示统计信息
|
|
589
|
-
if has_data:
|
|
590
|
-
# 1. 创建统计表格
|
|
591
|
-
from rich import box
|
|
592
|
-
|
|
593
|
-
table = Table(
|
|
594
|
-
show_header=True,
|
|
595
|
-
header_style="bold magenta",
|
|
596
|
-
title_justify="center",
|
|
597
|
-
box=box.ROUNDED,
|
|
598
|
-
padding=(0, 1),
|
|
613
|
+
categorized_stats["adoption"]["metrics"]["adoption_rate"] = (
|
|
614
|
+
f"{adoption_rate:.1f}%"
|
|
615
|
+
)
|
|
616
|
+
categorized_stats["adoption"]["metrics"]["commits_status"] = (
|
|
617
|
+
f"{accepted_commits}/{generated_commits}"
|
|
599
618
|
)
|
|
600
|
-
table.add_column("分类", style="cyan", no_wrap=True, width=12)
|
|
601
|
-
table.add_column("指标", style="white", width=20)
|
|
602
|
-
table.add_column("数量", style="green", justify="right", width=10)
|
|
603
|
-
table.add_column("分类", style="cyan", no_wrap=True, width=12)
|
|
604
|
-
table.add_column("指标", style="white", width=20)
|
|
605
|
-
table.add_column("数量", style="green", justify="right", width=10)
|
|
606
|
-
|
|
607
|
-
# 收集所有要显示的数据
|
|
608
|
-
all_rows = []
|
|
609
|
-
for title, stats, suffix in stats_output:
|
|
610
|
-
if stats:
|
|
611
|
-
sorted_stats = sorted(
|
|
612
|
-
stats.items(), key=lambda item: item[1], reverse=True
|
|
613
|
-
)
|
|
614
|
-
for i, (metric, count) in enumerate(sorted_stats):
|
|
615
|
-
display_name = metric.replace("_", " ").title()
|
|
616
|
-
category_title = title if i == 0 else ""
|
|
617
|
-
# 处理不同类型的count值
|
|
618
|
-
if isinstance(count, (int, float)):
|
|
619
|
-
count_str = f"{count:,} {suffix}"
|
|
620
|
-
else:
|
|
621
|
-
# 对于字符串类型的count(如百分比或比率),直接使用
|
|
622
|
-
count_str = str(count)
|
|
623
|
-
all_rows.append((category_title, display_name, count_str))
|
|
624
|
-
|
|
625
|
-
# 以3行2列的方式添加数据
|
|
626
|
-
has_content = len(all_rows) > 0
|
|
627
|
-
# 计算需要多少行来显示所有数据
|
|
628
|
-
total_rows = len(all_rows)
|
|
629
|
-
rows_needed = (total_rows + 1) // 2 # 向上取整,因为是2列布局
|
|
630
|
-
|
|
631
|
-
for i in range(rows_needed):
|
|
632
|
-
left_idx = i
|
|
633
|
-
right_idx = i + rows_needed
|
|
634
|
-
|
|
635
|
-
if left_idx < len(all_rows):
|
|
636
|
-
left_row = all_rows[left_idx]
|
|
637
|
-
else:
|
|
638
|
-
left_row = ("", "", "")
|
|
639
|
-
|
|
640
|
-
if right_idx < len(all_rows):
|
|
641
|
-
right_row = all_rows[right_idx]
|
|
642
|
-
else:
|
|
643
|
-
right_row = ("", "", "")
|
|
644
|
-
|
|
645
|
-
table.add_row(
|
|
646
|
-
left_row[0],
|
|
647
|
-
left_row[1],
|
|
648
|
-
left_row[2],
|
|
649
|
-
right_row[0],
|
|
650
|
-
right_row[1],
|
|
651
|
-
right_row[2],
|
|
652
|
-
)
|
|
653
619
|
|
|
654
|
-
|
|
655
|
-
|
|
620
|
+
# 右侧内容:总体表现 + 使命与愿景
|
|
621
|
+
right_column_items = []
|
|
622
|
+
summary_content: list[str] = []
|
|
623
|
+
from rich import box
|
|
624
|
+
|
|
625
|
+
# 计算总体表现的摘要数据
|
|
626
|
+
# 总结统计
|
|
627
|
+
total_tools = sum(
|
|
628
|
+
count
|
|
629
|
+
for _, stats in categorized_stats["tool"]["metrics"].items()
|
|
630
|
+
for metric, count in {
|
|
631
|
+
k: v
|
|
632
|
+
for k, v in categorized_stats["tool"]["metrics"].items()
|
|
633
|
+
if isinstance(v, (int, float))
|
|
634
|
+
}.items()
|
|
635
|
+
)
|
|
636
|
+
total_tools = sum(
|
|
637
|
+
count
|
|
638
|
+
for metric, count in categorized_stats["tool"]["metrics"].items()
|
|
639
|
+
if isinstance(count, (int, float))
|
|
640
|
+
)
|
|
656
641
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
for metric, count in stats.items()
|
|
663
|
-
)
|
|
664
|
-
total_changes = sum(
|
|
665
|
-
count
|
|
666
|
-
for title, stats, _ in stats_output
|
|
667
|
-
if "代码修改" in title
|
|
668
|
-
for metric, count in stats.items()
|
|
669
|
-
)
|
|
642
|
+
total_changes = sum(
|
|
643
|
+
count
|
|
644
|
+
for metric, count in categorized_stats["code"]["metrics"].items()
|
|
645
|
+
if isinstance(count, (int, float))
|
|
646
|
+
)
|
|
670
647
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
648
|
+
# 统计代码行数
|
|
649
|
+
lines_stats = categorized_stats["lines"]["metrics"]
|
|
650
|
+
total_lines_added = lines_stats.get(
|
|
651
|
+
"code_lines_inserted", lines_stats.get("code_lines_added", 0)
|
|
652
|
+
)
|
|
653
|
+
total_lines_deleted = lines_stats.get("code_lines_deleted", 0)
|
|
654
|
+
total_lines_modified = total_lines_added + total_lines_deleted
|
|
655
|
+
|
|
656
|
+
# 构建总体表现内容
|
|
657
|
+
if total_tools > 0 or total_changes > 0 or total_lines_modified > 0:
|
|
658
|
+
parts = []
|
|
659
|
+
if total_tools > 0:
|
|
660
|
+
parts.append(f"工具调用 {total_tools:,} 次")
|
|
661
|
+
if total_changes > 0:
|
|
662
|
+
parts.append(f"代码修改 {total_changes:,} 次")
|
|
663
|
+
if total_lines_modified > 0:
|
|
664
|
+
parts.append(f"修改代码行数 {total_lines_modified:,} 行")
|
|
665
|
+
|
|
666
|
+
if parts:
|
|
667
|
+
summary_content.append(f"📈 总计: {', '.join(parts)}")
|
|
668
|
+
|
|
669
|
+
# 添加代码采纳率显示
|
|
670
|
+
adoption_metrics = categorized_stats["adoption"]["metrics"]
|
|
671
|
+
if "adoption_rate" in adoption_metrics:
|
|
672
|
+
summary_content.append(
|
|
673
|
+
f"✅ 代码采纳率: {adoption_metrics['adoption_rate']}"
|
|
674
|
+
)
|
|
697
675
|
|
|
698
676
|
# 计算节省的时间
|
|
699
|
-
time_saved_seconds = 0
|
|
677
|
+
time_saved_seconds: float = 0.0
|
|
700
678
|
tool_stats = categorized_stats["tool"]["metrics"]
|
|
701
679
|
code_agent_changes = categorized_stats["code"]["metrics"]
|
|
702
680
|
lines_stats = categorized_stats["lines"]["metrics"]
|
|
703
|
-
|
|
681
|
+
commit_stats = categorized_stats["commit"]["metrics"]
|
|
704
682
|
command_stats = categorized_stats["command"]["metrics"]
|
|
705
683
|
|
|
706
684
|
# 统一的工具使用时间估算(每次调用节省2分钟)
|
|
@@ -708,23 +686,37 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
|
708
686
|
|
|
709
687
|
# 计算所有工具的时间节省
|
|
710
688
|
for tool_name, count in tool_stats.items():
|
|
711
|
-
|
|
689
|
+
if isinstance(count, (int, float)):
|
|
690
|
+
time_saved_seconds += count * DEFAULT_TOOL_TIME_SAVINGS
|
|
712
691
|
|
|
713
692
|
# 其他类型的时间计算
|
|
714
|
-
total_code_agent_calls =
|
|
693
|
+
total_code_agent_calls: float = float(
|
|
694
|
+
sum(
|
|
695
|
+
v
|
|
696
|
+
for v in code_agent_changes.values()
|
|
697
|
+
if isinstance(v, (int, float))
|
|
698
|
+
)
|
|
699
|
+
)
|
|
715
700
|
time_saved_seconds += total_code_agent_calls * 10 * 60
|
|
716
701
|
time_saved_seconds += lines_stats.get("code_lines_added", 0) * 0.8 * 60
|
|
717
702
|
time_saved_seconds += lines_stats.get("code_lines_deleted", 0) * 0.2 * 60
|
|
718
|
-
time_saved_seconds +=
|
|
719
|
-
|
|
703
|
+
time_saved_seconds += (
|
|
704
|
+
sum(v for v in commit_stats.values() if isinstance(v, (int, float)))
|
|
705
|
+
* 10
|
|
706
|
+
* 60
|
|
707
|
+
)
|
|
708
|
+
time_saved_seconds += (
|
|
709
|
+
sum(v for v in command_stats.values() if isinstance(v, (int, float)))
|
|
710
|
+
* 1
|
|
711
|
+
* 60
|
|
712
|
+
)
|
|
720
713
|
|
|
721
|
-
time_str = ""
|
|
722
|
-
hours = 0
|
|
723
714
|
if time_saved_seconds > 0:
|
|
724
715
|
total_minutes = int(time_saved_seconds / 60)
|
|
725
716
|
seconds = int(time_saved_seconds % 60)
|
|
726
717
|
hours = total_minutes // 60
|
|
727
718
|
minutes = total_minutes % 60
|
|
719
|
+
|
|
728
720
|
# 只显示小时和分钟
|
|
729
721
|
if hours > 0:
|
|
730
722
|
time_str = f"{hours} 小时 {minutes} 分钟"
|
|
@@ -733,21 +725,17 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
|
733
725
|
else:
|
|
734
726
|
time_str = f"{seconds} 秒"
|
|
735
727
|
|
|
736
|
-
if summary_content:
|
|
737
|
-
summary_content.append("") # Add a separator line
|
|
738
728
|
summary_content.append(f"⏱️ 节省时间: 约 {time_str}")
|
|
739
729
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
work_years = total_work_days // 240 # 每年约240个工作日
|
|
730
|
+
# 计算时间节省的鼓励信息
|
|
731
|
+
total_work_days = hours // 8
|
|
732
|
+
work_years = total_work_days // 240
|
|
744
733
|
remaining_days_after_years = total_work_days % 240
|
|
745
|
-
work_months = remaining_days_after_years // 20
|
|
734
|
+
work_months = remaining_days_after_years // 20
|
|
746
735
|
remaining_days_after_months = remaining_days_after_years % 20
|
|
747
736
|
work_days = remaining_days_after_months
|
|
748
|
-
remaining_hours = int(hours % 8)
|
|
737
|
+
remaining_hours = int(hours % 8)
|
|
749
738
|
|
|
750
|
-
# 构建时间描述
|
|
751
739
|
time_parts = []
|
|
752
740
|
if work_years > 0:
|
|
753
741
|
time_parts.append(f"{work_years} 年")
|
|
@@ -778,18 +766,13 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
|
778
766
|
)
|
|
779
767
|
elif hours >= 1:
|
|
780
768
|
encouragement = f"⭐ 相当于节省了 {int(hours)} 小时的工作时间,积少成多,继续保持!"
|
|
769
|
+
|
|
781
770
|
if encouragement:
|
|
782
771
|
summary_content.append(encouragement)
|
|
783
772
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
# 右侧内容:总体表现 + 使命与愿景
|
|
788
|
-
right_column_items = []
|
|
789
|
-
|
|
790
|
-
# 欢迎信息 Panel
|
|
791
|
-
if welcome_str:
|
|
792
|
-
jarvis_ascii_art_str = """
|
|
773
|
+
# 欢迎信息 Panel
|
|
774
|
+
if welcome_str:
|
|
775
|
+
jarvis_ascii_art_str = """
|
|
793
776
|
██╗ █████╗ ██████╗ ██╗ ██╗██╗███████╗
|
|
794
777
|
██║██╔══██╗██╔══██╗██║ ██║██║██╔════╝
|
|
795
778
|
██║███████║██████╔╝██║ ██║██║███████╗
|
|
@@ -797,108 +780,189 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
|
797
780
|
╚████║██║ ██║██║ ██║ ╚████╔╝ ██║███████║
|
|
798
781
|
╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝╚══════╝"""
|
|
799
782
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
)
|
|
807
|
-
|
|
808
|
-
welcome_panel = Panel(
|
|
809
|
-
welcome_panel_content, border_style="yellow", expand=True
|
|
810
|
-
)
|
|
811
|
-
right_column_items.append(welcome_panel)
|
|
812
|
-
if summary_content:
|
|
813
|
-
summary_panel = Panel(
|
|
814
|
-
Text("\n".join(summary_content), justify="left"),
|
|
815
|
-
title="✨ 总体表现 ✨",
|
|
816
|
-
title_align="center",
|
|
817
|
-
border_style="green",
|
|
818
|
-
expand=True,
|
|
819
|
-
)
|
|
820
|
-
right_column_items.append(summary_panel)
|
|
821
|
-
|
|
822
|
-
# 愿景 Panel
|
|
823
|
-
vision_text = Text(
|
|
824
|
-
"让开发者与AI成为共生伙伴",
|
|
825
|
-
justify="center",
|
|
826
|
-
style="italic",
|
|
783
|
+
welcome_panel_content = Group(
|
|
784
|
+
Align.center(Text(jarvis_ascii_art_str, style="bold blue")),
|
|
785
|
+
Align.center(Text(welcome_str, style="bold")),
|
|
786
|
+
"", # for a blank line
|
|
787
|
+
Align.center(Text(f"v{__version__}")),
|
|
788
|
+
Align.center(Text("https://github.com/skyfireitdiy/Jarvis")),
|
|
827
789
|
)
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
title_align="center",
|
|
832
|
-
border_style="cyan",
|
|
833
|
-
expand=True,
|
|
790
|
+
|
|
791
|
+
welcome_panel = Panel(
|
|
792
|
+
welcome_panel_content, border_style="yellow", expand=True
|
|
834
793
|
)
|
|
835
|
-
right_column_items.append(
|
|
794
|
+
right_column_items.append(welcome_panel)
|
|
795
|
+
|
|
796
|
+
# 总体表现 Panel
|
|
797
|
+
summary_panel = Panel(
|
|
798
|
+
Text(
|
|
799
|
+
"\n".join(summary_content) if summary_content else "暂无数据",
|
|
800
|
+
justify="left",
|
|
801
|
+
),
|
|
802
|
+
title="✨ 总体表现 ✨",
|
|
803
|
+
title_align="center",
|
|
804
|
+
border_style="green",
|
|
805
|
+
expand=True,
|
|
806
|
+
)
|
|
807
|
+
right_column_items.append(summary_panel)
|
|
836
808
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
809
|
+
# 愿景 Panel
|
|
810
|
+
vision_text = Text(
|
|
811
|
+
"让开发者与AI成为共生伙伴",
|
|
812
|
+
justify="center",
|
|
813
|
+
style="italic",
|
|
814
|
+
)
|
|
815
|
+
vision_panel = Panel(
|
|
816
|
+
vision_text,
|
|
817
|
+
title="🔭 愿景 (Vision) 🔭",
|
|
818
|
+
title_align="center",
|
|
819
|
+
border_style="cyan",
|
|
820
|
+
expand=True,
|
|
821
|
+
)
|
|
822
|
+
right_column_items.append(vision_panel)
|
|
823
|
+
|
|
824
|
+
# 使命 Panel
|
|
825
|
+
mission_text = Text(
|
|
826
|
+
"让灵感高效落地为代码与行动",
|
|
827
|
+
justify="center",
|
|
828
|
+
style="italic",
|
|
829
|
+
)
|
|
830
|
+
mission_panel = Panel(
|
|
831
|
+
mission_text,
|
|
832
|
+
title="🎯 使命 (Mission) 🎯",
|
|
833
|
+
title_align="center",
|
|
834
|
+
border_style="magenta",
|
|
835
|
+
expand=True,
|
|
836
|
+
)
|
|
837
|
+
right_column_items.append(mission_panel)
|
|
838
|
+
|
|
839
|
+
# 创建左右两列的内容组
|
|
840
|
+
left_column_items = []
|
|
841
|
+
right_column_items = []
|
|
842
|
+
|
|
843
|
+
# 左侧:欢迎Logo和基本信息
|
|
844
|
+
if welcome_str:
|
|
845
|
+
jarvis_ascii_art_str = """
|
|
846
|
+
██╗ █████╗ ██████╗ ██╗ ██╗██╗███████╗
|
|
847
|
+
██║██╔══██╗██╔══██╗██║ ██║██║██╔════╝
|
|
848
|
+
██║███████║██████╔╝██║ ██║██║███████╗
|
|
849
|
+
██╗██║██╔══██║██╔══██╗╚██╗ ██╔╝██║╚════██║
|
|
850
|
+
╚████║██║ ██║██║ ██║ ╚████╔╝ ██║███████║
|
|
851
|
+
╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝╚══════╝"""
|
|
852
|
+
|
|
853
|
+
welcome_content = Group(
|
|
854
|
+
Align.center(Text(jarvis_ascii_art_str, style="bold blue")),
|
|
855
|
+
Align.center(Text(welcome_str, style="bold")),
|
|
856
|
+
"", # for a blank line
|
|
857
|
+
Align.center(Text(f"v{__version__}")),
|
|
858
|
+
Align.center(Text("https://github.com/skyfireitdiy/Jarvis")),
|
|
842
859
|
)
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
border_style="
|
|
860
|
+
|
|
861
|
+
welcome_panel = Panel(
|
|
862
|
+
welcome_content,
|
|
863
|
+
title="🤖 Jarvis AI Assistant",
|
|
864
|
+
border_style="yellow",
|
|
848
865
|
expand=True,
|
|
849
866
|
)
|
|
850
|
-
|
|
867
|
+
left_column_items.append(welcome_panel)
|
|
868
|
+
|
|
869
|
+
# 右侧:总体表现、愿景和使命
|
|
870
|
+
# 总体表现 Panel
|
|
871
|
+
summary_panel = Panel(
|
|
872
|
+
Text(
|
|
873
|
+
"\n".join(summary_content) if summary_content else "暂无数据",
|
|
874
|
+
justify="left",
|
|
875
|
+
),
|
|
876
|
+
title="✨ 总体表现 ✨",
|
|
877
|
+
title_align="center",
|
|
878
|
+
border_style="green",
|
|
879
|
+
expand=True,
|
|
880
|
+
)
|
|
881
|
+
right_column_items.append(summary_panel)
|
|
851
882
|
|
|
852
|
-
|
|
883
|
+
# 愿景 Panel
|
|
884
|
+
vision_text = Text(
|
|
885
|
+
"让开发者与AI成为共生伙伴",
|
|
886
|
+
justify="center",
|
|
887
|
+
style="italic",
|
|
888
|
+
)
|
|
889
|
+
vision_panel = Panel(
|
|
890
|
+
vision_text,
|
|
891
|
+
title="🔭 愿景 (Vision) 🔭",
|
|
892
|
+
title_align="center",
|
|
893
|
+
border_style="cyan",
|
|
894
|
+
expand=True,
|
|
895
|
+
)
|
|
896
|
+
right_column_items.append(vision_panel)
|
|
853
897
|
|
|
854
|
-
|
|
898
|
+
# 使命 Panel
|
|
899
|
+
mission_text = Text(
|
|
900
|
+
"让灵感高效落地为代码与行动",
|
|
901
|
+
justify="center",
|
|
902
|
+
style="italic",
|
|
903
|
+
)
|
|
904
|
+
mission_panel = Panel(
|
|
905
|
+
mission_text,
|
|
906
|
+
title="🎯 使命 (Mission) 🎯",
|
|
907
|
+
title_align="center",
|
|
908
|
+
border_style="magenta",
|
|
909
|
+
expand=True,
|
|
910
|
+
)
|
|
911
|
+
right_column_items.append(mission_panel)
|
|
855
912
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
layout_items: List[RenderableType] = []
|
|
859
|
-
layout_items.append(right_column_group)
|
|
860
|
-
if has_content:
|
|
861
|
-
layout_items.append(Align.center(table))
|
|
862
|
-
layout_renderable = Group(*layout_items)
|
|
863
|
-
else:
|
|
864
|
-
# 左右布局(当前)
|
|
865
|
-
layout_table = Table(
|
|
866
|
-
show_header=False,
|
|
867
|
-
box=None,
|
|
868
|
-
padding=0,
|
|
869
|
-
expand=True,
|
|
870
|
-
pad_edge=False,
|
|
871
|
-
)
|
|
872
|
-
# 左右布局,左侧为总结信息,右侧为统计表格
|
|
873
|
-
layout_table.add_column(ratio=5) # 左侧
|
|
874
|
-
layout_table.add_column(ratio=5) # 右侧
|
|
913
|
+
left_column_group = Group(*left_column_items) if left_column_items else None
|
|
914
|
+
right_column_group = Group(*right_column_items)
|
|
875
915
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
916
|
+
layout_renderable: RenderableType
|
|
917
|
+
|
|
918
|
+
if console.width < 200:
|
|
919
|
+
# 上下布局(窄屏)
|
|
920
|
+
layout_items: List[RenderableType] = []
|
|
921
|
+
if left_column_group:
|
|
922
|
+
layout_items.append(left_column_group)
|
|
923
|
+
layout_items.append(right_column_group)
|
|
924
|
+
layout_renderable = Group(*layout_items)
|
|
925
|
+
else:
|
|
926
|
+
# 左右布局(宽屏)
|
|
927
|
+
layout_table = Table(
|
|
928
|
+
show_header=False,
|
|
929
|
+
box=None,
|
|
930
|
+
padding=(0, 2), # 上下0,左右2字符的内边距
|
|
931
|
+
expand=True,
|
|
932
|
+
pad_edge=False,
|
|
933
|
+
)
|
|
934
|
+
# 左右布局,优化比例:左侧更紧凑,右侧更宽敞
|
|
935
|
+
if left_column_group:
|
|
936
|
+
layout_table.add_column(
|
|
937
|
+
ratio=35, min_width=40
|
|
938
|
+
) # 左侧欢迎信息,最小宽度40
|
|
939
|
+
layout_table.add_column(
|
|
940
|
+
ratio=65, min_width=80
|
|
941
|
+
) # 右侧统计信息,最小宽度80
|
|
942
|
+
layout_table.add_row(left_column_group, right_column_group)
|
|
943
|
+
else:
|
|
944
|
+
# 如果没有欢迎信息,右侧占满
|
|
945
|
+
layout_table.add_column(ratio=100)
|
|
946
|
+
layout_table.add_row(right_column_group)
|
|
947
|
+
layout_renderable = layout_table
|
|
948
|
+
|
|
949
|
+
# 打印最终的布局
|
|
950
|
+
# 将整体布局封装在一个最终的Panel中,以提供整体边框
|
|
951
|
+
final_panel = Panel(
|
|
952
|
+
layout_renderable,
|
|
953
|
+
title="Jarvis AI Assistant",
|
|
954
|
+
title_align="center",
|
|
955
|
+
border_style="blue",
|
|
956
|
+
box=box.HEAVY,
|
|
957
|
+
padding=(0, 1),
|
|
958
|
+
)
|
|
959
|
+
console.print(final_panel)
|
|
896
960
|
except Exception as e:
|
|
897
961
|
# 输出错误信息以便调试
|
|
898
962
|
import traceback
|
|
899
963
|
|
|
900
|
-
|
|
901
|
-
|
|
964
|
+
PrettyOutput.auto_print(f"❌ 统计显示出错: {str(e)}")
|
|
965
|
+
PrettyOutput.auto_print(f"❌ {traceback.format_exc()}")
|
|
902
966
|
|
|
903
967
|
|
|
904
968
|
def init_env(welcome_str: str = "", config_file: Optional[str] = None) -> None:
|
|
@@ -910,10 +974,12 @@ def init_env(welcome_str: str = "", config_file: Optional[str] = None) -> None:
|
|
|
910
974
|
"""
|
|
911
975
|
# 0. 检查是否处于Jarvis打开的终端环境,避免嵌套
|
|
912
976
|
try:
|
|
913
|
-
if os.environ.get("
|
|
914
|
-
|
|
977
|
+
if os.environ.get("terminal") == "1":
|
|
978
|
+
PrettyOutput.auto_print(
|
|
979
|
+
"⚠️ 检测到当前终端由 Jarvis 打开。再次启动可能导致嵌套。"
|
|
980
|
+
)
|
|
915
981
|
if not user_confirm("是否仍要继续启动 Jarvis?", default=False):
|
|
916
|
-
|
|
982
|
+
PrettyOutput.auto_print("ℹ️ 已取消启动以避免终端嵌套。")
|
|
917
983
|
sys.exit(0)
|
|
918
984
|
except Exception:
|
|
919
985
|
pass
|
|
@@ -946,11 +1012,13 @@ def init_env(welcome_str: str = "", config_file: Optional[str] = None) -> None:
|
|
|
946
1012
|
try:
|
|
947
1013
|
# 在后台线程中显示统计,避免阻塞主流程
|
|
948
1014
|
import threading
|
|
1015
|
+
|
|
949
1016
|
def show_stats_async():
|
|
950
1017
|
try:
|
|
951
1018
|
_show_usage_stats(welcome_str)
|
|
952
1019
|
except Exception:
|
|
953
1020
|
pass
|
|
1021
|
+
|
|
954
1022
|
stats_thread = threading.Thread(target=show_stats_async, daemon=True)
|
|
955
1023
|
stats_thread.start()
|
|
956
1024
|
except Exception:
|
|
@@ -970,31 +1038,41 @@ def init_env(welcome_str: str = "", config_file: Optional[str] = None) -> None:
|
|
|
970
1038
|
def _interactive_config_setup(config_file_path: Path):
|
|
971
1039
|
"""交互式配置引导"""
|
|
972
1040
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
973
|
-
from jarvis.jarvis_utils.input import
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
user_confirm as get_yes_no,
|
|
977
|
-
)
|
|
1041
|
+
from jarvis.jarvis_utils.input import get_choice
|
|
1042
|
+
from jarvis.jarvis_utils.input import get_single_line_input as get_input
|
|
1043
|
+
from jarvis.jarvis_utils.input import user_confirm as get_yes_no
|
|
978
1044
|
|
|
979
|
-
|
|
1045
|
+
PrettyOutput.auto_print("ℹ️ 欢迎使用 Jarvis!未找到配置文件,现在开始引导配置。")
|
|
980
1046
|
|
|
981
1047
|
# 1. 选择平台
|
|
982
1048
|
registry = PlatformRegistry.get_global_platform_registry()
|
|
983
1049
|
platforms = registry.get_available_platforms()
|
|
984
1050
|
platform_name = get_choice("请选择您要使用的AI平台", platforms)
|
|
985
1051
|
|
|
986
|
-
# 2.
|
|
1052
|
+
# 2. 配置 API 密钥等信息(用于 llm_config)
|
|
987
1053
|
platform_class = registry.platforms.get(platform_name)
|
|
988
1054
|
if not platform_class:
|
|
989
|
-
|
|
1055
|
+
PrettyOutput.auto_print(f"❌ 平台 '{platform_name}' 加载失败。")
|
|
990
1056
|
sys.exit(1)
|
|
991
1057
|
|
|
992
1058
|
env_vars = {}
|
|
1059
|
+
llm_config = {}
|
|
993
1060
|
required_keys = platform_class.get_required_env_keys()
|
|
994
1061
|
defaults = platform_class.get_env_defaults()
|
|
995
1062
|
config_guide = platform_class.get_env_config_guide()
|
|
1063
|
+
|
|
1064
|
+
# 环境变量到 llm_config 键名的映射
|
|
1065
|
+
env_to_llm_config_map = {
|
|
1066
|
+
"OPENAI_API_KEY": "openai_api_key",
|
|
1067
|
+
"OPENAI_API_BASE": "openai_api_base",
|
|
1068
|
+
"OPENAI_EXTRA_HEADERS": "openai_extra_headers",
|
|
1069
|
+
"KIMI_API_KEY": "kimi_api_key",
|
|
1070
|
+
"TONGYI_COOKIES": "tongyi_cookies",
|
|
1071
|
+
"YUANBAO_COOKIES": "yuanbao_cookies",
|
|
1072
|
+
}
|
|
1073
|
+
|
|
996
1074
|
if required_keys:
|
|
997
|
-
|
|
1075
|
+
PrettyOutput.auto_print(f"ℹ️ 请输入 {platform_name} 平台所需的配置信息:")
|
|
998
1076
|
|
|
999
1077
|
# 如果有配置指导,先显示总体说明
|
|
1000
1078
|
if config_guide:
|
|
@@ -1005,7 +1083,7 @@ def _interactive_config_setup(config_file_path: Path):
|
|
|
1005
1083
|
guide_lines.append("")
|
|
1006
1084
|
guide_lines.append(f"{key} 获取方法:")
|
|
1007
1085
|
guide_lines.append(str(config_guide[key]))
|
|
1008
|
-
|
|
1086
|
+
PrettyOutput.auto_print("ℹ️ " + "\n".join(guide_lines))
|
|
1009
1087
|
else:
|
|
1010
1088
|
# 若无指导,仍需遍历以保持后续逻辑一致
|
|
1011
1089
|
pass
|
|
@@ -1023,11 +1101,19 @@ def _interactive_config_setup(config_file_path: Path):
|
|
|
1023
1101
|
env_vars[key] = value
|
|
1024
1102
|
os.environ[key] = value # 立即设置环境变量以便后续测试
|
|
1025
1103
|
|
|
1104
|
+
# 同时添加到 llm_config(如果存在映射)
|
|
1105
|
+
llm_config_key = env_to_llm_config_map.get(key)
|
|
1106
|
+
if llm_config_key:
|
|
1107
|
+
llm_config[llm_config_key] = value
|
|
1108
|
+
|
|
1026
1109
|
# 3. 选择模型
|
|
1027
1110
|
try:
|
|
1028
|
-
|
|
1111
|
+
# 创建平台实例时传递 llm_config(如果已收集)
|
|
1112
|
+
platform_instance = registry.create_platform(
|
|
1113
|
+
platform_name, llm_config=llm_config if llm_config else None
|
|
1114
|
+
)
|
|
1029
1115
|
if not platform_instance:
|
|
1030
|
-
|
|
1116
|
+
PrettyOutput.auto_print(f"❌ 无法创建平台 '{platform_name}'。")
|
|
1031
1117
|
sys.exit(1)
|
|
1032
1118
|
|
|
1033
1119
|
model_list_tuples = platform_instance.get_model_list()
|
|
@@ -1039,43 +1125,79 @@ def _interactive_config_setup(config_file_path: Path):
|
|
|
1039
1125
|
model_name, _ = model_list_tuples[selected_index]
|
|
1040
1126
|
|
|
1041
1127
|
except Exception:
|
|
1042
|
-
|
|
1128
|
+
PrettyOutput.auto_print("❌ 获取模型列表失败")
|
|
1043
1129
|
if not get_yes_no("无法获取模型列表,是否继续配置?"):
|
|
1044
1130
|
sys.exit(1)
|
|
1045
1131
|
model_name = get_input("请输入模型名称:")
|
|
1046
1132
|
|
|
1047
1133
|
# 4. 测试配置
|
|
1048
|
-
|
|
1134
|
+
PrettyOutput.auto_print("ℹ️ 正在测试配置...")
|
|
1049
1135
|
test_passed = False
|
|
1050
1136
|
try:
|
|
1051
|
-
|
|
1137
|
+
# 创建平台实例时传递 llm_config(如果已收集)
|
|
1138
|
+
platform_instance = registry.create_platform(
|
|
1139
|
+
platform_name, llm_config=llm_config if llm_config else None
|
|
1140
|
+
)
|
|
1052
1141
|
if platform_instance:
|
|
1053
1142
|
platform_instance.set_model_name(model_name)
|
|
1054
1143
|
response_generator = platform_instance.chat("hello")
|
|
1055
1144
|
response = "".join(response_generator)
|
|
1056
1145
|
if response:
|
|
1057
|
-
|
|
1146
|
+
PrettyOutput.auto_print(f"✅ 测试成功,模型响应: {response}")
|
|
1058
1147
|
test_passed = True
|
|
1059
1148
|
else:
|
|
1060
|
-
|
|
1149
|
+
PrettyOutput.auto_print("❌ 测试失败,模型没有响应。")
|
|
1061
1150
|
else:
|
|
1062
|
-
|
|
1151
|
+
PrettyOutput.auto_print("❌ 测试失败,无法创建平台实例。")
|
|
1152
|
+
except Exception:
|
|
1153
|
+
PrettyOutput.auto_print("❌ 测试失败")
|
|
1154
|
+
|
|
1155
|
+
# 5. 询问最大输入 token 数量
|
|
1156
|
+
max_input_token_count = 32000
|
|
1157
|
+
try:
|
|
1158
|
+
max_input_token_str = get_input(
|
|
1159
|
+
"请输入最大输入 token 数量(留空使用默认: 32000):",
|
|
1160
|
+
default="32000",
|
|
1161
|
+
)
|
|
1162
|
+
if max_input_token_str and max_input_token_str.strip():
|
|
1163
|
+
max_input_token_count = int(max_input_token_str.strip())
|
|
1063
1164
|
except Exception:
|
|
1064
|
-
|
|
1165
|
+
pass
|
|
1166
|
+
|
|
1167
|
+
# 6. 生成 LLM 配置名称
|
|
1168
|
+
llm_name = f"{platform_name}-{model_name}".replace(" ", "-").lower()
|
|
1169
|
+
# 清理名称,只保留字母、数字和连字符
|
|
1170
|
+
import re
|
|
1171
|
+
|
|
1172
|
+
llm_name = re.sub(r"[^a-z0-9-]", "", llm_name)
|
|
1173
|
+
if not llm_name:
|
|
1174
|
+
llm_name = "default-llm"
|
|
1065
1175
|
|
|
1066
|
-
#
|
|
1176
|
+
# 7. 交互式确认并应用配置(使用新的引用方式)
|
|
1067
1177
|
config_data = {
|
|
1068
1178
|
"ENV": env_vars,
|
|
1069
|
-
"
|
|
1070
|
-
|
|
1179
|
+
"llms": {
|
|
1180
|
+
llm_name: {
|
|
1181
|
+
"platform": platform_name,
|
|
1182
|
+
"model": model_name,
|
|
1183
|
+
"max_input_token_count": max_input_token_count,
|
|
1184
|
+
"llm_config": llm_config if llm_config else {},
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1187
|
+
"llm_groups": {
|
|
1188
|
+
"default": {
|
|
1189
|
+
"normal_llm": llm_name,
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
"llm_group": "default",
|
|
1071
1193
|
}
|
|
1072
1194
|
|
|
1073
1195
|
if not test_passed:
|
|
1074
1196
|
if not get_yes_no("配置测试失败,是否仍要应用该配置并继续?", default=False):
|
|
1075
|
-
|
|
1197
|
+
PrettyOutput.auto_print("ℹ️ 已取消配置。")
|
|
1076
1198
|
sys.exit(0)
|
|
1077
1199
|
|
|
1078
|
-
#
|
|
1200
|
+
# 8. 选择其他功能开关与可选项(复用统一逻辑)
|
|
1079
1201
|
_collect_optional_config_interactively(config_data)
|
|
1080
1202
|
|
|
1081
1203
|
# 7. 应用到当前会话并写入配置文件(基于交互结果,不从默认值生成)
|
|
@@ -1095,11 +1217,11 @@ def _interactive_config_setup(config_file_path: Path):
|
|
|
1095
1217
|
if header:
|
|
1096
1218
|
f.write(header)
|
|
1097
1219
|
f.write(yaml_str)
|
|
1098
|
-
|
|
1099
|
-
|
|
1220
|
+
PrettyOutput.auto_print(f"✅ 配置文件已生成: {config_file_path}")
|
|
1221
|
+
PrettyOutput.auto_print("ℹ️ 配置完成,请重新启动Jarvis。")
|
|
1100
1222
|
sys.exit(0)
|
|
1101
1223
|
except Exception:
|
|
1102
|
-
|
|
1224
|
+
PrettyOutput.auto_print("❌ 写入配置文件失败")
|
|
1103
1225
|
sys.exit(1)
|
|
1104
1226
|
|
|
1105
1227
|
|
|
@@ -1122,8 +1244,6 @@ def load_config():
|
|
|
1122
1244
|
_load_and_process_config(str(config_file_path.parent), str(config_file_path))
|
|
1123
1245
|
|
|
1124
1246
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
1247
|
def _load_config_file(config_file: str) -> Tuple[str, dict]:
|
|
1128
1248
|
"""读取并解析YAML格式的配置文件
|
|
1129
1249
|
|
|
@@ -1177,12 +1297,15 @@ def _process_env_variables(config_data: dict) -> None:
|
|
|
1177
1297
|
)
|
|
1178
1298
|
|
|
1179
1299
|
|
|
1180
|
-
def _ask_config_bool(
|
|
1300
|
+
def _ask_config_bool(
|
|
1301
|
+
config_data: dict, ask_all: bool, _key: str, _tip: str, _default: bool
|
|
1302
|
+
) -> bool:
|
|
1181
1303
|
"""询问并设置布尔类型配置项"""
|
|
1182
1304
|
try:
|
|
1183
1305
|
if not ask_all and _key in config_data:
|
|
1184
1306
|
return False
|
|
1185
1307
|
from jarvis.jarvis_utils.input import user_confirm as get_yes_no
|
|
1308
|
+
|
|
1186
1309
|
cur = bool(config_data.get(_key, _default))
|
|
1187
1310
|
val = get_yes_no(_tip, default=cur)
|
|
1188
1311
|
if bool(val) == cur:
|
|
@@ -1193,12 +1316,15 @@ def _ask_config_bool(config_data: dict, ask_all: bool, _key: str, _tip: str, _de
|
|
|
1193
1316
|
return False
|
|
1194
1317
|
|
|
1195
1318
|
|
|
1196
|
-
def _ask_config_str(
|
|
1319
|
+
def _ask_config_str(
|
|
1320
|
+
config_data: dict, ask_all: bool, _key: str, _tip: str, _default: str = ""
|
|
1321
|
+
) -> bool:
|
|
1197
1322
|
"""询问并设置字符串类型配置项"""
|
|
1198
1323
|
try:
|
|
1199
1324
|
if not ask_all and _key in config_data:
|
|
1200
1325
|
return False
|
|
1201
1326
|
from jarvis.jarvis_utils.input import get_single_line_input
|
|
1327
|
+
|
|
1202
1328
|
cur = str(config_data.get(_key, _default or ""))
|
|
1203
1329
|
val = get_single_line_input(f"{_tip}", default=cur)
|
|
1204
1330
|
v = ("" if val is None else str(val)).strip()
|
|
@@ -1210,15 +1336,18 @@ def _ask_config_str(config_data: dict, ask_all: bool, _key: str, _tip: str, _def
|
|
|
1210
1336
|
return False
|
|
1211
1337
|
|
|
1212
1338
|
|
|
1213
|
-
def _ask_config_optional_str(
|
|
1339
|
+
def _ask_config_optional_str(
|
|
1340
|
+
config_data: dict, ask_all: bool, _key: str, _tip: str, _default: str = ""
|
|
1341
|
+
) -> bool:
|
|
1214
1342
|
"""询问并设置可选字符串类型配置项(空输入表示不改变)"""
|
|
1215
1343
|
try:
|
|
1216
1344
|
if not ask_all and _key in config_data:
|
|
1217
1345
|
return False
|
|
1218
1346
|
from jarvis.jarvis_utils.input import get_single_line_input
|
|
1347
|
+
|
|
1219
1348
|
cur = str(config_data.get(_key, _default or ""))
|
|
1220
1349
|
val = get_single_line_input(f"{_tip}", default=cur)
|
|
1221
|
-
if val
|
|
1350
|
+
if not val:
|
|
1222
1351
|
return False
|
|
1223
1352
|
s = str(val).strip()
|
|
1224
1353
|
if s == "" or s == cur:
|
|
@@ -1229,12 +1358,15 @@ def _ask_config_optional_str(config_data: dict, ask_all: bool, _key: str, _tip:
|
|
|
1229
1358
|
return False
|
|
1230
1359
|
|
|
1231
1360
|
|
|
1232
|
-
def _ask_config_int(
|
|
1361
|
+
def _ask_config_int(
|
|
1362
|
+
config_data: dict, ask_all: bool, _key: str, _tip: str, _default: int
|
|
1363
|
+
) -> bool:
|
|
1233
1364
|
"""询问并设置整数类型配置项"""
|
|
1234
1365
|
try:
|
|
1235
1366
|
if not ask_all and _key in config_data:
|
|
1236
1367
|
return False
|
|
1237
1368
|
from jarvis.jarvis_utils.input import get_single_line_input
|
|
1369
|
+
|
|
1238
1370
|
cur = str(config_data.get(_key, _default))
|
|
1239
1371
|
val_str = get_single_line_input(f"{_tip}", default=cur)
|
|
1240
1372
|
s = "" if val_str is None else str(val_str).strip()
|
|
@@ -1258,13 +1390,14 @@ def _ask_config_list(config_data: dict, ask_all: bool, _key: str, _tip: str) ->
|
|
|
1258
1390
|
if not ask_all and _key in config_data:
|
|
1259
1391
|
return False
|
|
1260
1392
|
from jarvis.jarvis_utils.input import get_single_line_input
|
|
1393
|
+
|
|
1261
1394
|
cur_val = config_data.get(_key, [])
|
|
1262
1395
|
if isinstance(cur_val, list):
|
|
1263
1396
|
cur_display = ", ".join([str(x) for x in cur_val])
|
|
1264
1397
|
else:
|
|
1265
1398
|
cur_display = str(cur_val or "")
|
|
1266
1399
|
val = get_single_line_input(f"{_tip}", default=cur_display)
|
|
1267
|
-
if val
|
|
1400
|
+
if not val:
|
|
1268
1401
|
return False
|
|
1269
1402
|
s = str(val).strip()
|
|
1270
1403
|
if s == cur_display.strip():
|
|
@@ -1283,18 +1416,26 @@ def _ask_config_list(config_data: dict, ask_all: bool, _key: str, _tip: str) ->
|
|
|
1283
1416
|
def _collect_basic_switches(config_data: dict, ask_all: bool) -> bool:
|
|
1284
1417
|
"""收集基础开关配置"""
|
|
1285
1418
|
changed = False
|
|
1286
|
-
changed =
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1419
|
+
changed = (
|
|
1420
|
+
_ask_config_bool(
|
|
1421
|
+
config_data,
|
|
1422
|
+
ask_all,
|
|
1423
|
+
"enable_git_jca_switch",
|
|
1424
|
+
"是否在检测到Git仓库时,提示并可自动切换到代码开发模式(jca)?",
|
|
1425
|
+
True,
|
|
1426
|
+
)
|
|
1427
|
+
or changed
|
|
1428
|
+
)
|
|
1429
|
+
changed = (
|
|
1430
|
+
_ask_config_bool(
|
|
1431
|
+
config_data,
|
|
1432
|
+
ask_all,
|
|
1433
|
+
"enable_startup_config_selector",
|
|
1434
|
+
"在进入默认通用代理前,是否先列出可用配置(agent/multi_agent/roles)供选择?",
|
|
1435
|
+
True,
|
|
1436
|
+
)
|
|
1437
|
+
or changed
|
|
1438
|
+
)
|
|
1298
1439
|
return changed
|
|
1299
1440
|
|
|
1300
1441
|
|
|
@@ -1303,130 +1444,213 @@ def _collect_ui_experience_config(config_data: dict, ask_all: bool) -> bool:
|
|
|
1303
1444
|
changed = False
|
|
1304
1445
|
try:
|
|
1305
1446
|
import platform as _platform_mod
|
|
1447
|
+
|
|
1306
1448
|
_default_pretty = False if _platform_mod.system() == "Windows" else True
|
|
1307
1449
|
except Exception:
|
|
1308
1450
|
_default_pretty = True
|
|
1309
|
-
|
|
1310
|
-
changed =
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1451
|
+
|
|
1452
|
+
changed = (
|
|
1453
|
+
_ask_config_bool(
|
|
1454
|
+
config_data,
|
|
1455
|
+
ask_all,
|
|
1456
|
+
"pretty_output",
|
|
1457
|
+
"是否启用更美观的终端输出(Pretty Output)?",
|
|
1458
|
+
_default_pretty,
|
|
1459
|
+
)
|
|
1460
|
+
or changed
|
|
1461
|
+
)
|
|
1462
|
+
changed = (
|
|
1463
|
+
_ask_config_bool(
|
|
1464
|
+
config_data,
|
|
1465
|
+
ask_all,
|
|
1466
|
+
"print_prompt",
|
|
1467
|
+
"是否打印发送给模型的提示词(Prompt)?",
|
|
1468
|
+
False,
|
|
1469
|
+
)
|
|
1470
|
+
or changed
|
|
1471
|
+
)
|
|
1472
|
+
changed = (
|
|
1473
|
+
_ask_config_bool(
|
|
1474
|
+
config_data,
|
|
1475
|
+
ask_all,
|
|
1476
|
+
"immediate_abort",
|
|
1477
|
+
"是否启用立即中断?\n- 选择 是/true:在对话输出流的每次迭代中检测到用户中断(例如 Ctrl+C)时,立即返回当前已生成的内容并停止继续输出。\n- 选择 否/false:不会在输出过程中立刻返回,而是按既有流程处理(不中途打断输出)。",
|
|
1478
|
+
False,
|
|
1479
|
+
)
|
|
1480
|
+
or changed
|
|
1481
|
+
)
|
|
1482
|
+
|
|
1483
|
+
# Diff 可视化模式配置
|
|
1484
|
+
if ask_all or "diff_visualization_mode" not in config_data:
|
|
1485
|
+
from jarvis.jarvis_utils.input import get_choice
|
|
1486
|
+
|
|
1487
|
+
current_mode = config_data.get("diff_visualization_mode", "side_by_side")
|
|
1488
|
+
diff_mode_choices = [
|
|
1489
|
+
f"side_by_side - 左右分栏对比显示{'(当前)' if current_mode == 'side_by_side' else ''}",
|
|
1490
|
+
f"unified - 统一diff格式{'(当前)' if current_mode == 'unified' else ''}",
|
|
1491
|
+
f"syntax - 语法高亮模式{'(当前)' if current_mode == 'syntax' else ''}",
|
|
1492
|
+
f"compact - 紧凑模式{'(当前)' if current_mode == 'compact' else ''}",
|
|
1493
|
+
]
|
|
1494
|
+
selected_display = get_choice("选择 Diff 可视化模式", diff_mode_choices)
|
|
1495
|
+
selected_mode = selected_display.split(" - ")[0]
|
|
1496
|
+
if selected_mode != current_mode:
|
|
1497
|
+
config_data["diff_visualization_mode"] = selected_mode
|
|
1498
|
+
changed = True
|
|
1499
|
+
|
|
1328
1500
|
return changed
|
|
1329
1501
|
|
|
1330
1502
|
|
|
1331
1503
|
def _collect_analysis_config(config_data: dict, ask_all: bool) -> bool:
|
|
1332
1504
|
"""收集代码分析相关配置"""
|
|
1333
1505
|
changed = False
|
|
1334
|
-
changed =
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1506
|
+
changed = (
|
|
1507
|
+
_ask_config_bool(
|
|
1508
|
+
config_data,
|
|
1509
|
+
ask_all,
|
|
1510
|
+
"enable_static_analysis",
|
|
1511
|
+
"是否启用静态代码分析(Static Analysis)?",
|
|
1512
|
+
True,
|
|
1513
|
+
)
|
|
1514
|
+
or changed
|
|
1515
|
+
)
|
|
1516
|
+
changed = (
|
|
1517
|
+
_ask_config_bool(
|
|
1518
|
+
config_data,
|
|
1519
|
+
ask_all,
|
|
1520
|
+
"enable_build_validation",
|
|
1521
|
+
"是否启用构建验证(Build Validation)?在代码编辑后自动验证代码能否成功编译/构建。",
|
|
1522
|
+
True,
|
|
1523
|
+
)
|
|
1524
|
+
or changed
|
|
1525
|
+
)
|
|
1526
|
+
changed = (
|
|
1527
|
+
_ask_config_int(
|
|
1528
|
+
config_data,
|
|
1529
|
+
ask_all,
|
|
1530
|
+
"build_validation_timeout",
|
|
1531
|
+
"构建验证的超时时间(秒,默认30秒)",
|
|
1532
|
+
30,
|
|
1533
|
+
)
|
|
1534
|
+
or changed
|
|
1535
|
+
)
|
|
1536
|
+
changed = (
|
|
1537
|
+
_ask_config_bool(
|
|
1538
|
+
config_data,
|
|
1539
|
+
ask_all,
|
|
1540
|
+
"enable_impact_analysis",
|
|
1541
|
+
"是否启用编辑影响范围分析(Impact Analysis)?分析代码编辑的影响范围,识别可能受影响的文件、函数、测试等。",
|
|
1542
|
+
True,
|
|
1543
|
+
)
|
|
1544
|
+
or changed
|
|
1545
|
+
)
|
|
1358
1546
|
return changed
|
|
1359
1547
|
|
|
1360
1548
|
|
|
1361
1549
|
def _collect_agent_features_config(config_data: dict, ask_all: bool) -> bool:
|
|
1362
1550
|
"""收集Agent功能相关配置"""
|
|
1363
1551
|
changed = False
|
|
1364
|
-
changed =
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1552
|
+
changed = (
|
|
1553
|
+
_ask_config_bool(
|
|
1554
|
+
config_data,
|
|
1555
|
+
ask_all,
|
|
1556
|
+
"use_methodology",
|
|
1557
|
+
"是否启用方法论系统(Methodology)?",
|
|
1558
|
+
True,
|
|
1559
|
+
)
|
|
1560
|
+
or changed
|
|
1561
|
+
)
|
|
1562
|
+
changed = (
|
|
1563
|
+
_ask_config_bool(
|
|
1564
|
+
config_data,
|
|
1565
|
+
ask_all,
|
|
1566
|
+
"use_analysis",
|
|
1567
|
+
"是否启用分析流程(Analysis)?",
|
|
1568
|
+
True,
|
|
1569
|
+
)
|
|
1570
|
+
or changed
|
|
1571
|
+
)
|
|
1572
|
+
changed = (
|
|
1573
|
+
_ask_config_bool(
|
|
1574
|
+
config_data,
|
|
1575
|
+
ask_all,
|
|
1576
|
+
"force_save_memory",
|
|
1577
|
+
"是否强制保存会话记忆?",
|
|
1578
|
+
False,
|
|
1579
|
+
)
|
|
1580
|
+
or changed
|
|
1581
|
+
)
|
|
1382
1582
|
return changed
|
|
1383
1583
|
|
|
1384
1584
|
|
|
1385
1585
|
def _collect_session_config(config_data: dict, ask_all: bool) -> bool:
|
|
1386
1586
|
"""收集会话与调试相关配置"""
|
|
1387
1587
|
changed = False
|
|
1388
|
-
changed =
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1588
|
+
changed = (
|
|
1589
|
+
_ask_config_bool(
|
|
1590
|
+
config_data,
|
|
1591
|
+
ask_all,
|
|
1592
|
+
"save_session_history",
|
|
1593
|
+
"是否保存会话记录?",
|
|
1594
|
+
False,
|
|
1595
|
+
)
|
|
1596
|
+
or changed
|
|
1597
|
+
)
|
|
1598
|
+
changed = (
|
|
1599
|
+
_ask_config_bool(
|
|
1600
|
+
config_data,
|
|
1601
|
+
ask_all,
|
|
1602
|
+
"print_error_traceback",
|
|
1603
|
+
"是否在错误输出时打印回溯调用链?",
|
|
1604
|
+
False,
|
|
1605
|
+
)
|
|
1606
|
+
or changed
|
|
1607
|
+
)
|
|
1608
|
+
changed = (
|
|
1609
|
+
_ask_config_bool(
|
|
1610
|
+
config_data,
|
|
1611
|
+
ask_all,
|
|
1612
|
+
"skip_predefined_tasks",
|
|
1613
|
+
"是否跳过预定义任务加载(不读取 pre-command 列表)?",
|
|
1614
|
+
False,
|
|
1615
|
+
)
|
|
1616
|
+
or changed
|
|
1617
|
+
)
|
|
1618
|
+
changed = (
|
|
1619
|
+
_ask_config_int(
|
|
1620
|
+
config_data,
|
|
1621
|
+
ask_all,
|
|
1622
|
+
"conversation_turn_threshold",
|
|
1623
|
+
"对话轮次阈值(达到此轮次时触发总结,建议50-100):",
|
|
1624
|
+
50,
|
|
1625
|
+
)
|
|
1626
|
+
or changed
|
|
1627
|
+
)
|
|
1412
1628
|
return changed
|
|
1413
1629
|
|
|
1414
1630
|
|
|
1415
1631
|
def _collect_safety_config(config_data: dict, ask_all: bool) -> bool:
|
|
1416
1632
|
"""收集代码与工具操作安全提示配置"""
|
|
1417
1633
|
changed = False
|
|
1418
|
-
changed =
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1634
|
+
changed = (
|
|
1635
|
+
_ask_config_bool(
|
|
1636
|
+
config_data,
|
|
1637
|
+
ask_all,
|
|
1638
|
+
"execute_tool_confirm",
|
|
1639
|
+
"执行工具前是否需要确认?",
|
|
1640
|
+
False,
|
|
1641
|
+
)
|
|
1642
|
+
or changed
|
|
1643
|
+
)
|
|
1644
|
+
changed = (
|
|
1645
|
+
_ask_config_bool(
|
|
1646
|
+
config_data,
|
|
1647
|
+
ask_all,
|
|
1648
|
+
"confirm_before_apply_patch",
|
|
1649
|
+
"应用补丁前是否需要确认?",
|
|
1650
|
+
False,
|
|
1651
|
+
)
|
|
1652
|
+
or changed
|
|
1653
|
+
)
|
|
1430
1654
|
return changed
|
|
1431
1655
|
|
|
1432
1656
|
|
|
@@ -1434,186 +1658,283 @@ def _collect_data_and_token_config(config_data: dict, ask_all: bool) -> bool:
|
|
|
1434
1658
|
"""收集数据目录与最大输入Token配置"""
|
|
1435
1659
|
changed = False
|
|
1436
1660
|
from jarvis.jarvis_utils.config import get_data_dir as _get_data_dir
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1661
|
+
|
|
1662
|
+
changed = (
|
|
1663
|
+
_ask_config_optional_str(
|
|
1664
|
+
config_data,
|
|
1665
|
+
ask_all,
|
|
1666
|
+
"data_path",
|
|
1667
|
+
f"是否自定义数据目录路径(data_path)?留空使用默认: {_get_data_dir()}",
|
|
1668
|
+
)
|
|
1669
|
+
or changed
|
|
1670
|
+
)
|
|
1671
|
+
changed = (
|
|
1672
|
+
_ask_config_int(
|
|
1673
|
+
config_data,
|
|
1674
|
+
ask_all,
|
|
1675
|
+
"max_input_token_count",
|
|
1676
|
+
"自定义最大输入Token数量(留空使用默认: 128000)",
|
|
1677
|
+
128000,
|
|
1678
|
+
)
|
|
1679
|
+
or changed
|
|
1680
|
+
)
|
|
1681
|
+
changed = (
|
|
1682
|
+
_ask_config_int(
|
|
1683
|
+
config_data,
|
|
1684
|
+
ask_all,
|
|
1685
|
+
"cheap_max_input_token_count",
|
|
1686
|
+
"廉价模型的最大输入Token数量(留空或0表示使用max_input_token_count)",
|
|
1687
|
+
0,
|
|
1688
|
+
)
|
|
1689
|
+
or changed
|
|
1690
|
+
)
|
|
1691
|
+
changed = (
|
|
1692
|
+
_ask_config_int(
|
|
1693
|
+
config_data,
|
|
1694
|
+
ask_all,
|
|
1695
|
+
"smart_max_input_token_count",
|
|
1696
|
+
"智能模型的最大输入Token数量(留空或0表示使用max_input_token_count)",
|
|
1697
|
+
0,
|
|
1698
|
+
)
|
|
1699
|
+
or changed
|
|
1700
|
+
)
|
|
1701
|
+
changed = (
|
|
1702
|
+
_ask_config_int(
|
|
1703
|
+
config_data,
|
|
1704
|
+
ask_all,
|
|
1705
|
+
"tool_filter_threshold",
|
|
1706
|
+
"设置AI工具筛选阈值 (当可用工具数超过此值时触发AI筛选, 默认30)",
|
|
1707
|
+
30,
|
|
1708
|
+
)
|
|
1709
|
+
or changed
|
|
1710
|
+
)
|
|
1454
1711
|
return changed
|
|
1455
1712
|
|
|
1456
1713
|
|
|
1457
1714
|
def _collect_advanced_config(config_data: dict, ask_all: bool) -> bool:
|
|
1458
1715
|
"""收集高级配置(自动总结、脚本超时等)"""
|
|
1459
1716
|
changed = False
|
|
1460
|
-
changed =
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1717
|
+
changed = (
|
|
1718
|
+
_ask_config_int(
|
|
1719
|
+
config_data,
|
|
1720
|
+
ask_all,
|
|
1721
|
+
"script_execution_timeout",
|
|
1722
|
+
"脚本执行超时时间(秒,默认300,仅非交互模式生效)",
|
|
1723
|
+
300,
|
|
1724
|
+
)
|
|
1725
|
+
or changed
|
|
1726
|
+
)
|
|
1727
|
+
changed = (
|
|
1728
|
+
_ask_config_int(
|
|
1729
|
+
config_data,
|
|
1730
|
+
ask_all,
|
|
1731
|
+
"addon_prompt_threshold",
|
|
1732
|
+
"附加提示的触发阈值(字符数,默认1024)。当消息长度超过此值时,会自动添加默认的附加提示",
|
|
1733
|
+
1024,
|
|
1734
|
+
)
|
|
1735
|
+
or changed
|
|
1736
|
+
)
|
|
1737
|
+
changed = (
|
|
1738
|
+
_ask_config_bool(
|
|
1739
|
+
config_data,
|
|
1740
|
+
ask_all,
|
|
1741
|
+
"enable_intent_recognition",
|
|
1742
|
+
"是否启用意图识别功能?用于智能上下文推荐中的LLM意图提取和语义分析",
|
|
1743
|
+
True,
|
|
1744
|
+
)
|
|
1745
|
+
or changed
|
|
1746
|
+
)
|
|
1478
1747
|
return changed
|
|
1479
1748
|
|
|
1480
1749
|
|
|
1481
1750
|
def _collect_directory_config(config_data: dict, ask_all: bool) -> bool:
|
|
1482
1751
|
"""收集目录类配置(逗号分隔)"""
|
|
1483
1752
|
changed = False
|
|
1484
|
-
changed =
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1753
|
+
changed = (
|
|
1754
|
+
_ask_config_list(
|
|
1755
|
+
config_data,
|
|
1756
|
+
ask_all,
|
|
1757
|
+
"tool_load_dirs",
|
|
1758
|
+
"指定工具加载目录(逗号分隔,留空跳过):",
|
|
1759
|
+
)
|
|
1760
|
+
or changed
|
|
1761
|
+
)
|
|
1762
|
+
changed = (
|
|
1763
|
+
_ask_config_list(
|
|
1764
|
+
config_data,
|
|
1765
|
+
ask_all,
|
|
1766
|
+
"methodology_dirs",
|
|
1767
|
+
"指定方法论加载目录(逗号分隔,留空跳过):",
|
|
1768
|
+
)
|
|
1769
|
+
or changed
|
|
1770
|
+
)
|
|
1771
|
+
changed = (
|
|
1772
|
+
_ask_config_list(
|
|
1773
|
+
config_data,
|
|
1774
|
+
ask_all,
|
|
1775
|
+
"agent_definition_dirs",
|
|
1776
|
+
"指定 agent 定义加载目录(逗号分隔,留空跳过):",
|
|
1777
|
+
)
|
|
1778
|
+
or changed
|
|
1779
|
+
)
|
|
1780
|
+
changed = (
|
|
1781
|
+
_ask_config_list(
|
|
1782
|
+
config_data,
|
|
1783
|
+
ask_all,
|
|
1784
|
+
"multi_agent_dirs",
|
|
1785
|
+
"指定 multi_agent 加载目录(逗号分隔,留空跳过):",
|
|
1786
|
+
)
|
|
1787
|
+
or changed
|
|
1788
|
+
)
|
|
1789
|
+
changed = (
|
|
1790
|
+
_ask_config_list(
|
|
1791
|
+
config_data,
|
|
1792
|
+
ask_all,
|
|
1793
|
+
"roles_dirs",
|
|
1794
|
+
"指定 roles 加载目录(逗号分隔,留空跳过):",
|
|
1795
|
+
)
|
|
1796
|
+
or changed
|
|
1797
|
+
)
|
|
1798
|
+
changed = (
|
|
1799
|
+
_ask_config_list(
|
|
1800
|
+
config_data,
|
|
1801
|
+
ask_all,
|
|
1802
|
+
"after_tool_call_cb_dirs",
|
|
1803
|
+
"指定工具调用后回调实现目录(逗号分隔,留空跳过):",
|
|
1804
|
+
)
|
|
1805
|
+
or changed
|
|
1806
|
+
)
|
|
1514
1807
|
return changed
|
|
1515
1808
|
|
|
1516
1809
|
|
|
1517
1810
|
def _collect_web_search_config(config_data: dict, ask_all: bool) -> bool:
|
|
1518
1811
|
"""收集Web搜索配置"""
|
|
1519
1812
|
changed = False
|
|
1520
|
-
changed =
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1813
|
+
changed = (
|
|
1814
|
+
_ask_config_optional_str(
|
|
1815
|
+
config_data,
|
|
1816
|
+
ask_all,
|
|
1817
|
+
"web_search_platform",
|
|
1818
|
+
"配置 Web 搜索平台名称(留空跳过):",
|
|
1819
|
+
)
|
|
1820
|
+
or changed
|
|
1821
|
+
)
|
|
1822
|
+
changed = (
|
|
1823
|
+
_ask_config_optional_str(
|
|
1824
|
+
config_data,
|
|
1825
|
+
ask_all,
|
|
1826
|
+
"web_search_model",
|
|
1827
|
+
"配置 Web 搜索模型名称(留空跳过):",
|
|
1828
|
+
)
|
|
1829
|
+
or changed
|
|
1830
|
+
)
|
|
1530
1831
|
return changed
|
|
1531
1832
|
|
|
1532
1833
|
|
|
1533
|
-
def _ask_git_check_mode(config_data: dict, ask_all: bool) -> bool:
|
|
1534
|
-
"""询问Git校验模式"""
|
|
1535
|
-
try:
|
|
1536
|
-
_key = "JARVIS_GIT_CHECK_MODE"
|
|
1537
|
-
if not ask_all and _key in config_data:
|
|
1538
|
-
return False
|
|
1539
|
-
from jarvis.jarvis_utils.input import get_choice
|
|
1540
|
-
from jarvis.jarvis_utils.config import get_git_check_mode
|
|
1541
|
-
current_mode = config_data.get(_key, get_git_check_mode())
|
|
1542
|
-
choices = ["strict", "warn"]
|
|
1543
|
-
tip = (
|
|
1544
|
-
"请选择 Git 仓库检查模式 (JARVIS_GIT_CHECK_MODE):\n"
|
|
1545
|
-
"此设置决定了当在 Git 仓库中检测到未提交的更改时,Jarvis应如何处理。\n"
|
|
1546
|
-
"这对于确保代码修改和提交操作在干净的工作区上进行至关重要。\n"
|
|
1547
|
-
" - strict: (推荐) 如果存在未提交的更改,则中断相关操作(如代码修改、自动提交)。\n"
|
|
1548
|
-
" 这可以防止意外覆盖或丢失本地工作。\n"
|
|
1549
|
-
" - warn: 如果存在未提交的更改,仅显示警告信息,然后继续执行操作。\n"
|
|
1550
|
-
" 适用于您希望绕过检查并自行管理仓库状态的场景。"
|
|
1551
|
-
)
|
|
1552
|
-
new_mode = get_choice(tip, choices)
|
|
1553
|
-
if new_mode == current_mode:
|
|
1554
|
-
return False
|
|
1555
|
-
config_data[_key] = new_mode
|
|
1556
|
-
return True
|
|
1557
|
-
except Exception:
|
|
1558
|
-
return False
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
1834
|
def _collect_git_config(config_data: dict, ask_all: bool) -> bool:
|
|
1562
1835
|
"""收集Git相关配置"""
|
|
1563
1836
|
changed = False
|
|
1564
|
-
changed =
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1837
|
+
changed = (
|
|
1838
|
+
_ask_config_optional_str(
|
|
1839
|
+
config_data,
|
|
1840
|
+
ask_all,
|
|
1841
|
+
"git_commit_prompt",
|
|
1842
|
+
"自定义 Git 提交提示模板(留空跳过):",
|
|
1843
|
+
)
|
|
1844
|
+
or changed
|
|
1845
|
+
)
|
|
1570
1846
|
return changed
|
|
1571
1847
|
|
|
1572
1848
|
|
|
1573
1849
|
def _collect_rag_config(config_data: dict, ask_all: bool) -> bool:
|
|
1574
|
-
"""收集RAG
|
|
1850
|
+
"""收集RAG配置(使用新的引用方式)"""
|
|
1575
1851
|
changed = False
|
|
1576
1852
|
try:
|
|
1577
1853
|
from jarvis.jarvis_utils.config import (
|
|
1578
1854
|
get_rag_embedding_model as _get_rag_embedding_model,
|
|
1855
|
+
)
|
|
1856
|
+
from jarvis.jarvis_utils.config import (
|
|
1579
1857
|
get_rag_rerank_model as _get_rag_rerank_model,
|
|
1580
1858
|
)
|
|
1581
|
-
from jarvis.jarvis_utils.input import
|
|
1582
|
-
|
|
1583
|
-
|
|
1859
|
+
from jarvis.jarvis_utils.input import (
|
|
1860
|
+
get_single_line_input as get_single_line_input_func,
|
|
1861
|
+
)
|
|
1862
|
+
from jarvis.jarvis_utils.input import user_confirm as get_yes_no_func
|
|
1863
|
+
|
|
1584
1864
|
rag_default_embed = _get_rag_embedding_model()
|
|
1585
1865
|
rag_default_rerank = _get_rag_rerank_model()
|
|
1866
|
+
get_yes_no_var: Optional[Any] = get_yes_no_func
|
|
1867
|
+
get_single_line_input_var: Optional[Any] = get_single_line_input_func
|
|
1586
1868
|
except Exception:
|
|
1587
1869
|
rag_default_embed = "BAAI/bge-m3"
|
|
1588
1870
|
rag_default_rerank = "BAAI/bge-reranker-v2-m3"
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1871
|
+
get_yes_no_var = None
|
|
1872
|
+
get_single_line_input_var = None
|
|
1873
|
+
|
|
1592
1874
|
try:
|
|
1593
|
-
if
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1875
|
+
if (
|
|
1876
|
+
"rag_groups" not in config_data
|
|
1877
|
+
and get_yes_no_var is not None
|
|
1878
|
+
and get_single_line_input_var is not None
|
|
1879
|
+
):
|
|
1880
|
+
if get_yes_no_var("是否配置 RAG 检索增强参数?", default=False):
|
|
1881
|
+
# 初始化 embeddings 和 rerankers(如果不存在)
|
|
1882
|
+
if "embeddings" not in config_data:
|
|
1883
|
+
config_data["embeddings"] = {}
|
|
1884
|
+
if "rerankers" not in config_data:
|
|
1885
|
+
config_data["rerankers"] = {}
|
|
1886
|
+
if "rag_groups" not in config_data:
|
|
1887
|
+
config_data["rag_groups"] = {}
|
|
1888
|
+
|
|
1889
|
+
# 收集嵌入模型配置
|
|
1890
|
+
emb = get_single_line_input_var(
|
|
1597
1891
|
f"RAG 嵌入模型(留空使用默认: {rag_default_embed}):",
|
|
1598
1892
|
default="",
|
|
1599
1893
|
).strip()
|
|
1600
|
-
|
|
1894
|
+
if not emb:
|
|
1895
|
+
emb = rag_default_embed
|
|
1896
|
+
|
|
1897
|
+
# 创建嵌入模型配置
|
|
1898
|
+
embedding_name = "default-rag-embedding"
|
|
1899
|
+
config_data["embeddings"][embedding_name] = {
|
|
1900
|
+
"embedding_model": emb,
|
|
1901
|
+
"embedding_type": "LocalEmbeddingModel",
|
|
1902
|
+
"embedding_max_length": 512,
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
# 收集重排模型配置
|
|
1906
|
+
rerank = get_single_line_input_var(
|
|
1601
1907
|
f"RAG rerank 模型(留空使用默认: {rag_default_rerank}):",
|
|
1602
1908
|
default="",
|
|
1603
1909
|
).strip()
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
rag_conf["embedding_model"] = emb
|
|
1910
|
+
if get_yes_no_var is not None:
|
|
1911
|
+
use_bm25 = get_yes_no_var("RAG 是否使用 BM25?", default=True)
|
|
1912
|
+
use_rerank = get_yes_no_var("RAG 是否使用 rerank?", default=True)
|
|
1608
1913
|
else:
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1914
|
+
use_bm25 = True
|
|
1915
|
+
use_rerank = True
|
|
1916
|
+
|
|
1917
|
+
# 创建重排模型配置(如果使用 rerank)
|
|
1918
|
+
rag_group_config = {
|
|
1919
|
+
"embedding": embedding_name,
|
|
1920
|
+
"use_bm25": bool(use_bm25),
|
|
1921
|
+
"use_rerank": bool(use_rerank),
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
if use_rerank:
|
|
1925
|
+
if not rerank:
|
|
1926
|
+
rerank = rag_default_rerank
|
|
1927
|
+
reranker_name = "default-rag-reranker"
|
|
1928
|
+
config_data["rerankers"][reranker_name] = {
|
|
1929
|
+
"rerank_model": rerank,
|
|
1930
|
+
"reranker_type": "LocalReranker",
|
|
1931
|
+
"reranker_max_length": 512,
|
|
1932
|
+
}
|
|
1933
|
+
rag_group_config["reranker"] = reranker_name
|
|
1934
|
+
|
|
1935
|
+
# 创建 rag_groups 配置(对象格式)
|
|
1936
|
+
config_data["rag_groups"]["default"] = rag_group_config
|
|
1937
|
+
config_data["rag_group"] = "default"
|
|
1617
1938
|
changed = True
|
|
1618
1939
|
except Exception:
|
|
1619
1940
|
pass
|
|
@@ -1623,18 +1944,26 @@ def _collect_rag_config(config_data: dict, ask_all: bool) -> bool:
|
|
|
1623
1944
|
def _collect_central_repo_config(config_data: dict, ask_all: bool) -> bool:
|
|
1624
1945
|
"""收集中心仓库配置"""
|
|
1625
1946
|
changed = False
|
|
1626
|
-
changed =
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1947
|
+
changed = (
|
|
1948
|
+
_ask_config_str(
|
|
1949
|
+
config_data,
|
|
1950
|
+
ask_all,
|
|
1951
|
+
"central_methodology_repo",
|
|
1952
|
+
"请输入中心方法论仓库路径或Git地址(可留空跳过):",
|
|
1953
|
+
"",
|
|
1954
|
+
)
|
|
1955
|
+
or changed
|
|
1956
|
+
)
|
|
1957
|
+
changed = (
|
|
1958
|
+
_ask_config_str(
|
|
1959
|
+
config_data,
|
|
1960
|
+
ask_all,
|
|
1961
|
+
"central_tool_repo",
|
|
1962
|
+
"请输入中心工具仓库路径或Git地址(可留空跳过):",
|
|
1963
|
+
"",
|
|
1964
|
+
)
|
|
1965
|
+
or changed
|
|
1966
|
+
)
|
|
1638
1967
|
return changed
|
|
1639
1968
|
|
|
1640
1969
|
|
|
@@ -1643,13 +1972,18 @@ def _collect_shell_config(config_data: dict, ask_all: bool) -> bool:
|
|
|
1643
1972
|
changed = False
|
|
1644
1973
|
try:
|
|
1645
1974
|
import os
|
|
1975
|
+
|
|
1646
1976
|
default_shell = os.getenv("SHELL", "/bin/bash")
|
|
1647
|
-
changed =
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1977
|
+
changed = (
|
|
1978
|
+
_ask_config_optional_str(
|
|
1979
|
+
config_data,
|
|
1980
|
+
ask_all,
|
|
1981
|
+
"SHELL",
|
|
1982
|
+
f"覆盖 SHELL 路径(留空使用系统默认: {default_shell}):",
|
|
1983
|
+
default_shell,
|
|
1984
|
+
)
|
|
1985
|
+
or changed
|
|
1986
|
+
)
|
|
1653
1987
|
except Exception:
|
|
1654
1988
|
pass
|
|
1655
1989
|
return changed
|
|
@@ -1668,7 +2002,7 @@ def _collect_optional_config_interactively(
|
|
|
1668
2002
|
bool: 是否有变更
|
|
1669
2003
|
"""
|
|
1670
2004
|
changed = False
|
|
1671
|
-
|
|
2005
|
+
|
|
1672
2006
|
# 收集各类配置
|
|
1673
2007
|
changed = _collect_basic_switches(config_data, ask_all) or changed
|
|
1674
2008
|
changed = _collect_ui_experience_config(config_data, ask_all) or changed
|
|
@@ -1684,7 +2018,7 @@ def _collect_optional_config_interactively(
|
|
|
1684
2018
|
changed = _collect_rag_config(config_data, ask_all) or changed
|
|
1685
2019
|
changed = _collect_central_repo_config(config_data, ask_all) or changed
|
|
1686
2020
|
changed = _collect_shell_config(config_data, ask_all) or changed
|
|
1687
|
-
|
|
2021
|
+
|
|
1688
2022
|
return changed
|
|
1689
2023
|
|
|
1690
2024
|
|
|
@@ -1739,13 +2073,15 @@ def _load_and_process_config(jarvis_dir: str, config_file: str) -> None:
|
|
|
1739
2073
|
# 更新全局配置
|
|
1740
2074
|
set_global_env_data(config_data)
|
|
1741
2075
|
except Exception:
|
|
1742
|
-
|
|
2076
|
+
PrettyOutput.auto_print("❌ 加载配置文件失败")
|
|
1743
2077
|
if get_yes_no("配置文件格式错误,是否删除并重新配置?"):
|
|
1744
2078
|
try:
|
|
1745
2079
|
os.remove(config_file)
|
|
1746
|
-
|
|
2080
|
+
PrettyOutput.auto_print(
|
|
2081
|
+
"✅ 已删除损坏的配置文件,请重启Jarvis以重新配置。"
|
|
2082
|
+
)
|
|
1747
2083
|
except Exception:
|
|
1748
|
-
|
|
2084
|
+
PrettyOutput.auto_print("❌ 删除配置文件失败")
|
|
1749
2085
|
sys.exit(1)
|
|
1750
2086
|
|
|
1751
2087
|
|
|
@@ -1874,35 +2210,43 @@ def _read_old_config_file(config_file):
|
|
|
1874
2210
|
if "=" in line and not line.startswith((" ", "\t")):
|
|
1875
2211
|
# 处理之前收集的多行值
|
|
1876
2212
|
if current_key is not None:
|
|
1877
|
-
|
|
2213
|
+
processed_value = (
|
|
2214
|
+
"\n".join(current_value).strip().strip("'").strip('"')
|
|
2215
|
+
)
|
|
1878
2216
|
# 将字符串"true"/"false"转换为bool类型
|
|
1879
|
-
if
|
|
1880
|
-
|
|
1881
|
-
elif
|
|
1882
|
-
|
|
1883
|
-
|
|
2217
|
+
if processed_value.lower() == "true":
|
|
2218
|
+
final_value = True
|
|
2219
|
+
elif processed_value.lower() == "false":
|
|
2220
|
+
final_value = False
|
|
2221
|
+
else:
|
|
2222
|
+
final_value = processed_value # type: ignore[assignment]
|
|
2223
|
+
config_data[current_key] = final_value
|
|
1884
2224
|
current_value = []
|
|
1885
2225
|
# 解析新的键值对
|
|
1886
|
-
|
|
1887
|
-
current_key =
|
|
1888
|
-
current_value.append(
|
|
2226
|
+
key_part, value_part = line.split("=", 1)
|
|
2227
|
+
current_key = key_part.strip()
|
|
2228
|
+
current_value.append(value_part.strip())
|
|
1889
2229
|
elif current_key is not None:
|
|
1890
2230
|
# 多行值的后续行
|
|
1891
2231
|
current_value.append(line.strip())
|
|
1892
2232
|
# 处理最后一个键值对
|
|
1893
2233
|
if current_key is not None:
|
|
1894
|
-
|
|
2234
|
+
processed_value = "\n".join(current_value).strip().strip("'").strip('"')
|
|
1895
2235
|
# 将字符串"true"/"false"转换为bool类型
|
|
1896
|
-
if
|
|
1897
|
-
|
|
1898
|
-
elif
|
|
1899
|
-
|
|
1900
|
-
|
|
2236
|
+
if processed_value.lower() == "true":
|
|
2237
|
+
final_value = True
|
|
2238
|
+
elif processed_value.lower() == "false":
|
|
2239
|
+
final_value = False
|
|
2240
|
+
else:
|
|
2241
|
+
final_value = processed_value # type: ignore[assignment]
|
|
2242
|
+
config_data[current_key] = final_value
|
|
1901
2243
|
os.environ.update(
|
|
1902
2244
|
{str(k): str(v) for k, v in config_data.items() if v is not None}
|
|
1903
2245
|
)
|
|
1904
2246
|
set_global_env_data(config_data)
|
|
1905
|
-
|
|
2247
|
+
PrettyOutput.auto_print(
|
|
2248
|
+
"⚠️ 检测到旧格式配置文件,旧格式以后将不再支持,请尽快迁移到新格式"
|
|
2249
|
+
)
|
|
1906
2250
|
|
|
1907
2251
|
|
|
1908
2252
|
# 线程本地存储,用于共享重试计数器
|
|
@@ -1911,17 +2255,17 @@ _retry_context = threading.local()
|
|
|
1911
2255
|
|
|
1912
2256
|
def _get_retry_count() -> int:
|
|
1913
2257
|
"""获取当前线程的重试计数"""
|
|
1914
|
-
if not hasattr(_retry_context,
|
|
2258
|
+
if not hasattr(_retry_context, "count"):
|
|
1915
2259
|
_retry_context.count = 0
|
|
1916
|
-
return _retry_context.count
|
|
2260
|
+
return int(_retry_context.count)
|
|
1917
2261
|
|
|
1918
2262
|
|
|
1919
2263
|
def _increment_retry_count() -> int:
|
|
1920
2264
|
"""增加重试计数并返回新的计数值"""
|
|
1921
|
-
if not hasattr(_retry_context,
|
|
2265
|
+
if not hasattr(_retry_context, "count"):
|
|
1922
2266
|
_retry_context.count = 0
|
|
1923
2267
|
_retry_context.count += 1
|
|
1924
|
-
return _retry_context.count
|
|
2268
|
+
return int(_retry_context.count)
|
|
1925
2269
|
|
|
1926
2270
|
|
|
1927
2271
|
def _reset_retry_count():
|
|
@@ -1943,7 +2287,7 @@ def while_success(func: Callable[[], Any]) -> Any:
|
|
|
1943
2287
|
"""
|
|
1944
2288
|
MAX_RETRIES = 6
|
|
1945
2289
|
result: Any = None
|
|
1946
|
-
|
|
2290
|
+
|
|
1947
2291
|
while True:
|
|
1948
2292
|
try:
|
|
1949
2293
|
result = func()
|
|
@@ -1955,10 +2299,14 @@ def while_success(func: Callable[[], Any]) -> Any:
|
|
|
1955
2299
|
# 指数退避:第1次等待1s (2^0),第2次等待2s (2^1),第3次等待4s (2^2),第4次等待8s (2^3),第6次等待32s (2^5)
|
|
1956
2300
|
sleep_time = 2 ** (retry_count - 1)
|
|
1957
2301
|
if retry_count < MAX_RETRIES:
|
|
1958
|
-
|
|
2302
|
+
PrettyOutput.auto_print(
|
|
2303
|
+
f"⚠️ 发生异常:\n{e}\n重试中 ({retry_count}/{MAX_RETRIES}),等待 {sleep_time}s..."
|
|
2304
|
+
)
|
|
1959
2305
|
time.sleep(sleep_time)
|
|
1960
2306
|
else:
|
|
1961
|
-
|
|
2307
|
+
PrettyOutput.auto_print(
|
|
2308
|
+
f"⚠️ 发生异常:\n{e}\n已达到最大重试次数 ({retry_count}/{MAX_RETRIES})"
|
|
2309
|
+
)
|
|
1962
2310
|
_reset_retry_count()
|
|
1963
2311
|
raise
|
|
1964
2312
|
else:
|
|
@@ -1983,7 +2331,7 @@ def while_true(func: Callable[[], bool]) -> Any:
|
|
|
1983
2331
|
"""
|
|
1984
2332
|
MAX_RETRIES = 6
|
|
1985
2333
|
ret: bool = False
|
|
1986
|
-
|
|
2334
|
+
|
|
1987
2335
|
while True:
|
|
1988
2336
|
try:
|
|
1989
2337
|
ret = func()
|
|
@@ -1994,16 +2342,20 @@ def while_true(func: Callable[[], bool]) -> Any:
|
|
|
1994
2342
|
# 异常直接抛出,不捕获
|
|
1995
2343
|
_reset_retry_count()
|
|
1996
2344
|
raise
|
|
1997
|
-
|
|
2345
|
+
|
|
1998
2346
|
retry_count = _increment_retry_count()
|
|
1999
2347
|
if retry_count <= MAX_RETRIES:
|
|
2000
2348
|
# 指数退避:第1次等待1s (2^0),第2次等待2s (2^1),第3次等待4s (2^2),第4次等待8s (2^3),第6次等待32s (2^5)
|
|
2001
2349
|
sleep_time = 2 ** (retry_count - 1)
|
|
2002
2350
|
if retry_count < MAX_RETRIES:
|
|
2003
|
-
|
|
2351
|
+
PrettyOutput.auto_print(
|
|
2352
|
+
f"⚠️ 返回空值,重试中 ({retry_count}/{MAX_RETRIES}),等待 {sleep_time}s..."
|
|
2353
|
+
)
|
|
2004
2354
|
time.sleep(sleep_time)
|
|
2005
2355
|
else:
|
|
2006
|
-
|
|
2356
|
+
PrettyOutput.auto_print(
|
|
2357
|
+
f"⚠️ 返回空值,已达到最大重试次数 ({retry_count}/{MAX_RETRIES})"
|
|
2358
|
+
)
|
|
2007
2359
|
_reset_retry_count()
|
|
2008
2360
|
break
|
|
2009
2361
|
else:
|
|
@@ -2024,7 +2376,7 @@ def get_file_md5(filepath: str) -> str:
|
|
|
2024
2376
|
# 采用流式读取,避免一次性加载100MB到内存
|
|
2025
2377
|
h = hashlib.md5()
|
|
2026
2378
|
max_bytes = 100 * 1024 * 1024 # 与原实现保持一致:仅读取前100MB
|
|
2027
|
-
buf_size = 8 * 1024 * 1024
|
|
2379
|
+
buf_size = 8 * 1024 * 1024 # 8MB缓冲
|
|
2028
2380
|
read_bytes = 0
|
|
2029
2381
|
with open(filepath, "rb") as f:
|
|
2030
2382
|
while read_bytes < max_bytes:
|
|
@@ -2056,8 +2408,9 @@ def get_file_line_count(filename: str) -> int:
|
|
|
2056
2408
|
|
|
2057
2409
|
def count_cmd_usage() -> None:
|
|
2058
2410
|
"""统计当前命令的使用次数"""
|
|
2059
|
-
import sys
|
|
2060
2411
|
import os
|
|
2412
|
+
import sys
|
|
2413
|
+
|
|
2061
2414
|
from jarvis.jarvis_stats.stats import StatsManager
|
|
2062
2415
|
|
|
2063
2416
|
# 从完整路径中提取命令名称
|
|
@@ -2075,17 +2428,17 @@ def count_cmd_usage() -> None:
|
|
|
2075
2428
|
|
|
2076
2429
|
|
|
2077
2430
|
def is_context_overflow(
|
|
2078
|
-
content: str,
|
|
2431
|
+
content: str,
|
|
2079
2432
|
model_group_override: Optional[str] = None,
|
|
2080
|
-
platform: Optional[Any] = None
|
|
2433
|
+
platform: Optional[Any] = None,
|
|
2081
2434
|
) -> bool:
|
|
2082
2435
|
"""判断文件内容是否超出上下文限制
|
|
2083
|
-
|
|
2436
|
+
|
|
2084
2437
|
参数:
|
|
2085
2438
|
content: 要检查的内容
|
|
2086
2439
|
model_group_override: 模型组覆盖(可选)
|
|
2087
2440
|
platform: 平台实例(可选),如果提供则使用剩余token数量判断
|
|
2088
|
-
|
|
2441
|
+
|
|
2089
2442
|
返回:
|
|
2090
2443
|
bool: 如果内容超出上下文限制返回True
|
|
2091
2444
|
"""
|
|
@@ -2093,21 +2446,21 @@ def is_context_overflow(
|
|
|
2093
2446
|
if content:
|
|
2094
2447
|
# 粗略估算:假设平均每个token约4个字符,保守估计使用3.5个字符/token
|
|
2095
2448
|
estimated_tokens = len(content) // 3.5
|
|
2096
|
-
|
|
2449
|
+
|
|
2097
2450
|
# 获取最大token限制
|
|
2098
2451
|
max_tokens = get_max_big_content_size(model_group_override)
|
|
2099
|
-
|
|
2452
|
+
|
|
2100
2453
|
# 如果预估token数超过限制的150%,直接认为超出(避免精确计算)
|
|
2101
2454
|
if estimated_tokens > max_tokens * 1.5:
|
|
2102
2455
|
return True
|
|
2103
|
-
|
|
2456
|
+
|
|
2104
2457
|
# 如果预估token数小于限制的50%,直接认为安全
|
|
2105
2458
|
if estimated_tokens < max_tokens * 0.5:
|
|
2106
2459
|
return False
|
|
2107
|
-
|
|
2460
|
+
|
|
2108
2461
|
# 只有在预估结果不明确时,才进行精确的token计算
|
|
2109
2462
|
content_tokens = get_context_token_count(content)
|
|
2110
|
-
|
|
2463
|
+
|
|
2111
2464
|
# 优先使用剩余token数量
|
|
2112
2465
|
if platform is not None:
|
|
2113
2466
|
try:
|
|
@@ -2118,9 +2471,11 @@ def is_context_overflow(
|
|
|
2118
2471
|
return content_tokens > threshold
|
|
2119
2472
|
except Exception:
|
|
2120
2473
|
pass
|
|
2121
|
-
|
|
2474
|
+
|
|
2122
2475
|
# 回退方案:使用输入窗口限制
|
|
2123
2476
|
return content_tokens > get_max_big_content_size(model_group_override)
|
|
2477
|
+
|
|
2478
|
+
|
|
2124
2479
|
def get_loc_stats() -> str:
|
|
2125
2480
|
"""使用loc命令获取当前目录的代码统计信息
|
|
2126
2481
|
|
|
@@ -2188,10 +2543,14 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
|
|
|
2188
2543
|
subprocess.TimeoutExpired,
|
|
2189
2544
|
FileNotFoundError,
|
|
2190
2545
|
) as e:
|
|
2191
|
-
|
|
2546
|
+
PrettyOutput.auto_print(
|
|
2547
|
+
f"❌ 放弃 '{repo_path.name}' 的更改失败: {str(e)}"
|
|
2548
|
+
)
|
|
2192
2549
|
return
|
|
2193
2550
|
else:
|
|
2194
|
-
|
|
2551
|
+
PrettyOutput.auto_print(
|
|
2552
|
+
f"ℹ️ 跳过更新 '{repo_path.name}' 以保留未提交的更改。"
|
|
2553
|
+
)
|
|
2195
2554
|
return
|
|
2196
2555
|
|
|
2197
2556
|
# 获取更新前的commit hash
|
|
@@ -2244,17 +2603,17 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
|
|
|
2244
2603
|
after_hash = after_hash_result.stdout.strip()
|
|
2245
2604
|
|
|
2246
2605
|
if before_hash != after_hash:
|
|
2247
|
-
|
|
2606
|
+
PrettyOutput.auto_print(f"✅ {repo_type}库 '{repo_path.name}' 已更新。")
|
|
2248
2607
|
|
|
2249
2608
|
except FileNotFoundError:
|
|
2250
|
-
|
|
2609
|
+
PrettyOutput.auto_print(f"⚠️ git 命令未找到,跳过更新 '{repo_path.name}'。")
|
|
2251
2610
|
except subprocess.TimeoutExpired:
|
|
2252
|
-
|
|
2611
|
+
PrettyOutput.auto_print(f"❌ 更新 '{repo_path.name}' 超时。")
|
|
2253
2612
|
except subprocess.CalledProcessError as e:
|
|
2254
2613
|
error_message = e.stderr.strip() if e.stderr else str(e)
|
|
2255
|
-
|
|
2614
|
+
PrettyOutput.auto_print(f"❌ 更新 '{repo_path.name}' 失败: {error_message}")
|
|
2256
2615
|
except Exception as e:
|
|
2257
|
-
|
|
2616
|
+
PrettyOutput.auto_print(f"❌ 更新 '{repo_path.name}' 时发生未知错误: {str(e)}")
|
|
2258
2617
|
|
|
2259
2618
|
|
|
2260
2619
|
def daily_check_git_updates(repo_dirs: List[str], repo_type: str):
|
|
@@ -2279,7 +2638,6 @@ def daily_check_git_updates(repo_dirs: List[str], repo_type: str):
|
|
|
2279
2638
|
pass
|
|
2280
2639
|
|
|
2281
2640
|
if should_check_for_updates:
|
|
2282
|
-
|
|
2283
2641
|
for repo_dir in repo_dirs:
|
|
2284
2642
|
p_repo_dir = Path(repo_dir)
|
|
2285
2643
|
if p_repo_dir.exists() and p_repo_dir.is_dir():
|
|
@@ -2287,4 +2645,4 @@ def daily_check_git_updates(repo_dirs: List[str], repo_type: str):
|
|
|
2287
2645
|
try:
|
|
2288
2646
|
last_check_file.write_text(str(time.time()))
|
|
2289
2647
|
except IOError as e:
|
|
2290
|
-
|
|
2648
|
+
PrettyOutput.auto_print(f"⚠️ 无法写入git更新检查时间戳: {e}")
|