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.
Files changed (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -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, Callable, Dict, List, Optional, Tuple
14
- from datetime import datetime, date
15
-
16
- import yaml # type: ignore
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
- get_data_dir,
23
- get_max_big_content_size,
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, get_interrupt, set_interrupt
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 = None # type: ignore
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(p, "r", encoding="utf-8", errors="ignore") as fp:
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(dir_info.get("editable")):
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
- if res is False:
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(("site-packages" in str(d)) or ("dist-packages" in str(d)) for d in parents)
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
- print(f"⚠️ 检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。\n如果确认不存在正在运行的实例,请删除锁文件后重试:{lock_path}")
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
- print(f"❌ 无法删除旧锁文件:{lock_path},请手动清理后重试。")
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
- print(f"⚠️ 检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。")
346
+ PrettyOutput.auto_print(
347
+ f"⚠️ 检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。"
348
+ )
317
349
  sys.exit(0)
318
- print(f"❌ 锁文件已存在但可能为陈旧状态:{lock_path},请手动删除后重试。")
350
+ PrettyOutput.auto_print(
351
+ f"❌ 锁文件已存在但可能为陈旧状态:{lock_path},请手动删除后重试。"
352
+ )
319
353
  sys.exit(1)
320
354
  except Exception as e:
321
- print(f"❌ 创建实例锁失败: {e}")
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
- print(f"ℹ️ 检测到新版本 v{latest_version} (当前版本: v{__version__})")
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
- print("ℹ️ 正在自动更新 Jarvis,请稍候...")
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
- print("✅ 更新成功,正在重启以应用新版本...")
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
- print(f"⚠️ 自动更新失败,错误信息(已截断): {err[:500]}")
427
- print(f"ℹ️ 请手动执行以下命令更新: {update_cmd}")
465
+ PrettyOutput.auto_print(
466
+ f"⚠️ 自动更新失败,错误信息(已截断): {err[:500]}"
467
+ )
468
+ PrettyOutput.auto_print(f"ℹ️ 请手动执行以下命令更新: {update_cmd}")
428
469
  except Exception:
429
- print("⚠️ 自动更新出现异常,已切换为手动更新方式。")
430
- print(f"ℹ️ 请手动执行以下命令更新: {update_cmd}")
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 Console, Group
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
- ] = f"{adoption_rate:.1f}%"
575
- categorized_stats["adoption"]["metrics"][
576
- "commits_status"
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
- # 2. 创建总结面板
655
- summary_content = []
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
- total_tools = sum(
659
- count
660
- for title, stats, _ in stats_output
661
- if "工具" in title
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
- lines_stats = categorized_stats["lines"]["metrics"]
673
- total_lines_added = lines_stats.get(
674
- "code_lines_inserted", lines_stats.get("code_lines_added", 0)
675
- )
676
- total_lines_deleted = lines_stats.get("code_lines_deleted", 0)
677
- total_lines_modified = total_lines_added + total_lines_deleted
678
-
679
- if total_tools > 0 or total_changes > 0 or total_lines_modified > 0:
680
- parts = []
681
- if total_tools > 0:
682
- parts.append(f"工具调用 {total_tools:,} 次")
683
- if total_changes > 0:
684
- parts.append(f"代码修改 {total_changes:,} 次")
685
- if total_lines_modified > 0:
686
- parts.append(f"修改代码行数 {total_lines_modified:,} 行")
687
-
688
- if parts:
689
- summary_content.append(f"📈 总计: {', '.join(parts)}")
690
-
691
- # 添加代码采纳率显示
692
- adoption_metrics = categorized_stats["adoption"]["metrics"]
693
- if "adoption_rate" in adoption_metrics:
694
- summary_content.append(
695
- f"✅ 代码采纳率: {adoption_metrics['adoption_rate']}"
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
- # commit_stats is already defined above
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
- time_saved_seconds += count * DEFAULT_TOOL_TIME_SAVINGS
689
+ if isinstance(count, (int, float)):
690
+ time_saved_seconds += count * DEFAULT_TOOL_TIME_SAVINGS
712
691
 
713
692
  # 其他类型的时间计算
714
- total_code_agent_calls = sum(code_agent_changes.values())
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 += sum(commit_stats.values()) * 10 * 60
719
- time_saved_seconds += sum(command_stats.values()) * 1 * 60
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
- encouragement = ""
741
- # 计算各级时间单位
742
- total_work_days = hours // 8 # 总工作日数
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 # 每月约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
- # 3. 组合并打印
785
- from rich import box
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
- welcome_panel_content = Group(
801
- Align.center(Text(jarvis_ascii_art_str, style="bold blue")),
802
- Align.center(Text(welcome_str, style="bold")),
803
- "", # for a blank line
804
- Align.center(Text(f"v{__version__}")),
805
- Align.center(Text("https://github.com/skyfireitdiy/Jarvis")),
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
- vision_panel = Panel(
829
- vision_text,
830
- title="🔭 愿景 (Vision) 🔭",
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(vision_panel)
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
- # 使命 Panel
838
- mission_text = Text(
839
- "让灵感高效落地为代码与行动",
840
- justify="center",
841
- style="italic",
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
- mission_panel = Panel(
844
- mission_text,
845
- title="🎯 使命 (Mission) 🎯",
846
- title_align="center",
847
- border_style="magenta",
860
+
861
+ welcome_panel = Panel(
862
+ welcome_content,
863
+ title="🤖 Jarvis AI Assistant",
864
+ border_style="yellow",
848
865
  expand=True,
849
866
  )
850
- right_column_items.append(mission_panel)
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
- right_column_group = Group(*right_column_items)
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
- layout_renderable: RenderableType
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
- if console.width < 200:
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
- if has_content:
877
- # 将总结信息放在左侧,统计表格放在右侧(表格居中显示)
878
- layout_table.add_row(right_column_group, Align.center(table))
879
- else:
880
- # 如果没有统计数据,则总结信息占满
881
- layout_table.add_row(right_column_group)
882
- layout_renderable = layout_table
883
-
884
- # 打印最终的布局
885
- if has_content or summary_content:
886
- # 将整体布局封装在一个最终的Panel中,以提供整体边框
887
- final_panel = Panel(
888
- layout_renderable,
889
- title="Jarvis AI Assistant",
890
- title_align="center",
891
- border_style="blue",
892
- box=box.HEAVY,
893
- padding=(0, 1),
894
- )
895
- console.print(final_panel)
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
- print(f"❌ 统计显示出错: {str(e)}")
901
- print(f"❌ {traceback.format_exc()}")
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("JARVIS_TERMINAL") == "1":
914
- print("⚠️ 检测到当前终端由 Jarvis 打开。再次启动可能导致嵌套。")
977
+ if os.environ.get("terminal") == "1":
978
+ PrettyOutput.auto_print(
979
+ "⚠️ 检测到当前终端由 Jarvis 打开。再次启动可能导致嵌套。"
980
+ )
915
981
  if not user_confirm("是否仍要继续启动 Jarvis?", default=False):
916
- print("ℹ️ 已取消启动以避免终端嵌套。")
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
- get_choice,
975
- get_single_line_input as get_input,
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
- print("ℹ️ 欢迎使用 Jarvis!未找到配置文件,现在开始引导配置。")
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
- print(f"❌ 平台 '{platform_name}' 加载失败。")
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
- print(f"ℹ️ 请输入 {platform_name} 平台所需的配置信息:")
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
- print("ℹ️ " + "\n".join(guide_lines))
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
- platform_instance = registry.create_platform(platform_name)
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
- print(f"❌ 无法创建平台 '{platform_name}'。")
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
- print("❌ 获取模型列表失败")
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
- print("ℹ️ 正在测试配置...")
1134
+ PrettyOutput.auto_print("ℹ️ 正在测试配置...")
1049
1135
  test_passed = False
1050
1136
  try:
1051
- platform_instance = registry.create_platform(platform_name)
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
- print(f"✅ 测试成功,模型响应: {response}")
1146
+ PrettyOutput.auto_print(f"✅ 测试成功,模型响应: {response}")
1058
1147
  test_passed = True
1059
1148
  else:
1060
- print("❌ 测试失败,模型没有响应。")
1149
+ PrettyOutput.auto_print("❌ 测试失败,模型没有响应。")
1061
1150
  else:
1062
- print("❌ 测试失败,无法创建平台实例。")
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
- print("❌ 测试失败")
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
- # 5. 交互式确认并应用配置(不直接生成配置文件)
1176
+ # 7. 交互式确认并应用配置(使用新的引用方式)
1067
1177
  config_data = {
1068
1178
  "ENV": env_vars,
1069
- "JARVIS_PLATFORM": platform_name,
1070
- "JARVIS_MODEL": model_name,
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
- print("ℹ️ 已取消配置。")
1197
+ PrettyOutput.auto_print("ℹ️ 已取消配置。")
1076
1198
  sys.exit(0)
1077
1199
 
1078
- # 6. 选择其他功能开关与可选项(复用统一逻辑)
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
- print(f"✅ 配置文件已生成: {config_file_path}")
1099
- print("ℹ️ 配置完成,请重新启动Jarvis。")
1220
+ PrettyOutput.auto_print(f"✅ 配置文件已生成: {config_file_path}")
1221
+ PrettyOutput.auto_print("ℹ️ 配置完成,请重新启动Jarvis。")
1100
1222
  sys.exit(0)
1101
1223
  except Exception:
1102
- print("❌ 写入配置文件失败")
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(config_data: dict, ask_all: bool, _key: str, _tip: str, _default: bool) -> 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(config_data: dict, ask_all: bool, _key: str, _tip: str, _default: str = "") -> bool:
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(config_data: dict, ask_all: bool, _key: str, _tip: str, _default: str = "") -> bool:
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 is None:
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(config_data: dict, ask_all: bool, _key: str, _tip: str, _default: int) -> bool:
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 is None:
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 = _ask_config_bool(
1287
- config_data, ask_all,
1288
- "JARVIS_ENABLE_GIT_JCA_SWITCH",
1289
- "是否在检测到Git仓库时,提示并可自动切换到代码开发模式(jca)?",
1290
- False,
1291
- ) or changed
1292
- changed = _ask_config_bool(
1293
- config_data, ask_all,
1294
- "JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR",
1295
- "在进入默认通用代理前,是否先列出可用配置(agent/multi_agent/roles)供选择?",
1296
- False,
1297
- ) or changed
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 = _ask_config_bool(
1311
- config_data, ask_all,
1312
- "JARVIS_PRETTY_OUTPUT",
1313
- "是否启用更美观的终端输出(Pretty Output)?",
1314
- _default_pretty,
1315
- ) or changed
1316
- changed = _ask_config_bool(
1317
- config_data, ask_all,
1318
- "JARVIS_PRINT_PROMPT",
1319
- "是否打印发送给模型的提示词(Prompt)?",
1320
- False,
1321
- ) or changed
1322
- changed = _ask_config_bool(
1323
- config_data, ask_all,
1324
- "JARVIS_IMMEDIATE_ABORT",
1325
- "是否启用立即中断?\n- 选择 是/true:在对话输出流的每次迭代中检测到用户中断(例如 Ctrl+C)时,立即返回当前已生成的内容并停止继续输出。\n- 选择 否/false:不会在输出过程中立刻返回,而是按既有流程处理(不中途打断输出)。",
1326
- False,
1327
- ) or changed
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 = _ask_config_bool(
1335
- config_data, ask_all,
1336
- "JARVIS_ENABLE_STATIC_ANALYSIS",
1337
- "是否启用静态代码分析(Static Analysis)?",
1338
- True,
1339
- ) or changed
1340
- changed = _ask_config_bool(
1341
- config_data, ask_all,
1342
- "JARVIS_ENABLE_BUILD_VALIDATION",
1343
- "是否启用构建验证(Build Validation)?在代码编辑后自动验证代码能否成功编译/构建。",
1344
- True,
1345
- ) or changed
1346
- changed = _ask_config_int(
1347
- config_data, ask_all,
1348
- "JARVIS_BUILD_VALIDATION_TIMEOUT",
1349
- "构建验证的超时时间(秒,默认30秒)",
1350
- 30,
1351
- ) or changed
1352
- changed = _ask_config_bool(
1353
- config_data, ask_all,
1354
- "JARVIS_ENABLE_IMPACT_ANALYSIS",
1355
- "是否启用编辑影响范围分析(Impact Analysis)?分析代码编辑的影响范围,识别可能受影响的文件、函数、测试等。",
1356
- True,
1357
- ) or changed
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 = _ask_config_bool(
1365
- config_data, ask_all,
1366
- "JARVIS_USE_METHODOLOGY",
1367
- "是否启用方法论系统(Methodology)?",
1368
- True,
1369
- ) or changed
1370
- changed = _ask_config_bool(
1371
- config_data, ask_all,
1372
- "JARVIS_USE_ANALYSIS",
1373
- "是否启用分析流程(Analysis)?",
1374
- True,
1375
- ) or changed
1376
- changed = _ask_config_bool(
1377
- config_data, ask_all,
1378
- "JARVIS_FORCE_SAVE_MEMORY",
1379
- "是否强制保存会话记忆?",
1380
- False,
1381
- ) or changed
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 = _ask_config_bool(
1389
- config_data, ask_all,
1390
- "JARVIS_SAVE_SESSION_HISTORY",
1391
- "是否保存会话记录?",
1392
- False,
1393
- ) or changed
1394
- changed = _ask_config_bool(
1395
- config_data, ask_all,
1396
- "JARVIS_PRINT_ERROR_TRACEBACK",
1397
- "是否在错误输出时打印回溯调用链?",
1398
- False,
1399
- ) or changed
1400
- changed = _ask_config_bool(
1401
- config_data, ask_all,
1402
- "JARVIS_SKIP_PREDEFINED_TASKS",
1403
- "是否跳过预定义任务加载(不读取 pre-command 列表)?",
1404
- False,
1405
- ) or changed
1406
- changed = _ask_config_int(
1407
- config_data, ask_all,
1408
- "JARVIS_CONVERSATION_TURN_THRESHOLD",
1409
- "对话轮次阈值(达到此轮次时触发总结,建议50-100):",
1410
- 50,
1411
- ) or changed
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 = _ask_config_bool(
1419
- config_data, ask_all,
1420
- "JARVIS_EXECUTE_TOOL_CONFIRM",
1421
- "执行工具前是否需要确认?",
1422
- False,
1423
- ) or changed
1424
- changed = _ask_config_bool(
1425
- config_data, ask_all,
1426
- "JARVIS_CONFIRM_BEFORE_APPLY_PATCH",
1427
- "应用补丁前是否需要确认?",
1428
- False,
1429
- ) or changed
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
- changed = _ask_config_optional_str(
1438
- config_data, ask_all,
1439
- "JARVIS_DATA_PATH",
1440
- f"是否自定义数据目录路径(JARVIS_DATA_PATH)?留空使用默认: {_get_data_dir()}",
1441
- ) or changed
1442
- changed = _ask_config_int(
1443
- config_data, ask_all,
1444
- "JARVIS_MAX_INPUT_TOKEN_COUNT",
1445
- "自定义最大输入Token数量(留空使用默认: 32000)",
1446
- 32000,
1447
- ) or changed
1448
- changed = _ask_config_int(
1449
- config_data, ask_all,
1450
- "JARVIS_TOOL_FILTER_THRESHOLD",
1451
- "设置AI工具筛选阈值 (当可用工具数超过此值时触发AI筛选, 默认30)",
1452
- 30,
1453
- ) or changed
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 = _ask_config_int(
1461
- config_data, ask_all,
1462
- "JARVIS_SCRIPT_EXECUTION_TIMEOUT",
1463
- "脚本执行超时时间(秒,默认300,仅非交互模式生效)",
1464
- 300,
1465
- ) or changed
1466
- changed = _ask_config_int(
1467
- config_data, ask_all,
1468
- "JARVIS_ADDON_PROMPT_THRESHOLD",
1469
- "附加提示的触发阈值(字符数,默认1024)。当消息长度超过此值时,会自动添加默认的附加提示",
1470
- 1024,
1471
- ) or changed
1472
- changed = _ask_config_bool(
1473
- config_data, ask_all,
1474
- "JARVIS_ENABLE_INTENT_RECOGNITION",
1475
- "是否启用意图识别功能?用于智能上下文推荐中的LLM意图提取和语义分析",
1476
- True,
1477
- ) or changed
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 = _ask_config_list(
1485
- config_data, ask_all,
1486
- "JARVIS_TOOL_LOAD_DIRS",
1487
- "指定工具加载目录(逗号分隔,留空跳过):",
1488
- ) or changed
1489
- changed = _ask_config_list(
1490
- config_data, ask_all,
1491
- "JARVIS_METHODOLOGY_DIRS",
1492
- "指定方法论加载目录(逗号分隔,留空跳过):",
1493
- ) or changed
1494
- changed = _ask_config_list(
1495
- config_data, ask_all,
1496
- "JARVIS_AGENT_DEFINITION_DIRS",
1497
- "指定 agent 定义加载目录(逗号分隔,留空跳过):",
1498
- ) or changed
1499
- changed = _ask_config_list(
1500
- config_data, ask_all,
1501
- "JARVIS_MULTI_AGENT_DIRS",
1502
- "指定 multi_agent 加载目录(逗号分隔,留空跳过):",
1503
- ) or changed
1504
- changed = _ask_config_list(
1505
- config_data, ask_all,
1506
- "JARVIS_ROLES_DIRS",
1507
- "指定 roles 加载目录(逗号分隔,留空跳过):",
1508
- ) or changed
1509
- changed = _ask_config_list(
1510
- config_data, ask_all,
1511
- "JARVIS_AFTER_TOOL_CALL_CB_DIRS",
1512
- "指定工具调用后回调实现目录(逗号分隔,留空跳过):",
1513
- ) or changed
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 = _ask_config_optional_str(
1521
- config_data, ask_all,
1522
- "JARVIS_WEB_SEARCH_PLATFORM",
1523
- "配置 Web 搜索平台名称(留空跳过):",
1524
- ) or changed
1525
- changed = _ask_config_optional_str(
1526
- config_data, ask_all,
1527
- "JARVIS_WEB_SEARCH_MODEL",
1528
- "配置 Web 搜索模型名称(留空跳过):",
1529
- ) or changed
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 = _ask_git_check_mode(config_data, ask_all) or changed
1565
- changed = _ask_config_optional_str(
1566
- config_data, ask_all,
1567
- "JARVIS_GIT_COMMIT_PROMPT",
1568
- "自定义 Git 提交提示模板(留空跳过):",
1569
- ) or changed
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 user_confirm as get_yes_no
1582
- from jarvis.jarvis_utils.input import get_single_line_input
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
- get_yes_no = None
1590
- get_single_line_input = None
1591
-
1871
+ get_yes_no_var = None
1872
+ get_single_line_input_var = None
1873
+
1592
1874
  try:
1593
- if "JARVIS_RAG" not in config_data and get_yes_no:
1594
- if get_yes_no("是否配置 RAG 检索增强参数?", default=False):
1595
- rag_conf: Dict[str, Any] = {}
1596
- emb = get_single_line_input(
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
- rerank = get_single_line_input(
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
- use_bm25 = get_yes_no("RAG 是否使用 BM25?", default=True)
1605
- use_rerank = get_yes_no("RAG 是否使用 rerank?", default=True)
1606
- if emb:
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
- rag_conf["embedding_model"] = rag_default_embed
1610
- if rerank:
1611
- rag_conf["rerank_model"] = rerank
1612
- else:
1613
- rag_conf["rerank_model"] = rag_default_rerank
1614
- rag_conf["use_bm25"] = bool(use_bm25)
1615
- rag_conf["use_rerank"] = bool(use_rerank)
1616
- config_data["JARVIS_RAG"] = rag_conf
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 = _ask_config_str(
1627
- config_data, ask_all,
1628
- "JARVIS_CENTRAL_METHODOLOGY_REPO",
1629
- "请输入中心方法论仓库路径或Git地址(可留空跳过):",
1630
- "",
1631
- ) or changed
1632
- changed = _ask_config_str(
1633
- config_data, ask_all,
1634
- "JARVIS_CENTRAL_TOOL_REPO",
1635
- "请输入中心工具仓库路径或Git地址(可留空跳过):",
1636
- "",
1637
- ) or changed
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 = _ask_config_optional_str(
1648
- config_data, ask_all,
1649
- "SHELL",
1650
- f"覆盖 SHELL 路径(留空使用系统默认: {default_shell}):",
1651
- default_shell,
1652
- ) or changed
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
- print("❌ 加载配置文件失败")
2076
+ PrettyOutput.auto_print("❌ 加载配置文件失败")
1743
2077
  if get_yes_no("配置文件格式错误,是否删除并重新配置?"):
1744
2078
  try:
1745
2079
  os.remove(config_file)
1746
- print("✅ 已删除损坏的配置文件,请重启Jarvis以重新配置。")
2080
+ PrettyOutput.auto_print(
2081
+ "✅ 已删除损坏的配置文件,请重启Jarvis以重新配置。"
2082
+ )
1747
2083
  except Exception:
1748
- print("❌ 删除配置文件失败")
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
- value = "\n".join(current_value).strip().strip("'").strip('"')
2213
+ processed_value = (
2214
+ "\n".join(current_value).strip().strip("'").strip('"')
2215
+ )
1878
2216
  # 将字符串"true"/"false"转换为bool类型
1879
- if value.lower() == "true":
1880
- value = True
1881
- elif value.lower() == "false":
1882
- value = False
1883
- config_data[current_key] = value
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
- key, value = line.split("=", 1)
1887
- current_key = key.strip()
1888
- current_value.append(value.strip())
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
- value = "\n".join(current_value).strip().strip("'").strip('"')
2234
+ processed_value = "\n".join(current_value).strip().strip("'").strip('"')
1895
2235
  # 将字符串"true"/"false"转换为bool类型
1896
- if value.lower() == "true":
1897
- value = True
1898
- elif value.lower() == "false":
1899
- value = False
1900
- config_data[current_key] = value
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
- print("⚠️ 检测到旧格式配置文件,旧格式以后将不再支持,请尽快迁移到新格式")
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, 'count'):
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, 'count'):
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
- print(f"⚠️ 发生异常:\n{e}\n重试中 ({retry_count}/{MAX_RETRIES}),等待 {sleep_time}s...")
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
- print(f"⚠️ 发生异常:\n{e}\n已达到最大重试次数 ({retry_count}/{MAX_RETRIES})")
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
- print(f"⚠️ 返回空值,重试中 ({retry_count}/{MAX_RETRIES}),等待 {sleep_time}s...")
2351
+ PrettyOutput.auto_print(
2352
+ f"⚠️ 返回空值,重试中 ({retry_count}/{MAX_RETRIES}),等待 {sleep_time}s..."
2353
+ )
2004
2354
  time.sleep(sleep_time)
2005
2355
  else:
2006
- print(f"⚠️ 返回空值,已达到最大重试次数 ({retry_count}/{MAX_RETRIES})")
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 # 8MB缓冲
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
- print(f"❌ 放弃 '{repo_path.name}' 的更改失败: {str(e)}")
2546
+ PrettyOutput.auto_print(
2547
+ f"❌ 放弃 '{repo_path.name}' 的更改失败: {str(e)}"
2548
+ )
2192
2549
  return
2193
2550
  else:
2194
- print(f"ℹ️ 跳过更新 '{repo_path.name}' 以保留未提交的更改。")
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
- print(f"✅ {repo_type}库 '{repo_path.name}' 已更新。")
2606
+ PrettyOutput.auto_print(f"✅ {repo_type}库 '{repo_path.name}' 已更新。")
2248
2607
 
2249
2608
  except FileNotFoundError:
2250
- print(f"⚠️ git 命令未找到,跳过更新 '{repo_path.name}'。")
2609
+ PrettyOutput.auto_print(f"⚠️ git 命令未找到,跳过更新 '{repo_path.name}'。")
2251
2610
  except subprocess.TimeoutExpired:
2252
- print(f"❌ 更新 '{repo_path.name}' 超时。")
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
- print(f"❌ 更新 '{repo_path.name}' 失败: {error_message}")
2614
+ PrettyOutput.auto_print(f"❌ 更新 '{repo_path.name}' 失败: {error_message}")
2256
2615
  except Exception as e:
2257
- print(f"❌ 更新 '{repo_path.name}' 时发生未知错误: {str(e)}")
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
- print(f"⚠️ 无法写入git更新检查时间戳: {e}")
2648
+ PrettyOutput.auto_print(f"⚠️ 无法写入git更新检查时间戳: {e}")