jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +289 -87
  3. jarvis/jarvis_agent/agent_manager.py +17 -8
  4. jarvis/jarvis_agent/edit_file_handler.py +374 -86
  5. jarvis/jarvis_agent/event_bus.py +1 -1
  6. jarvis/jarvis_agent/file_context_handler.py +79 -0
  7. jarvis/jarvis_agent/jarvis.py +601 -43
  8. jarvis/jarvis_agent/main.py +32 -2
  9. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  10. jarvis/jarvis_agent/run_loop.py +38 -5
  11. jarvis/jarvis_agent/share_manager.py +8 -1
  12. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  13. jarvis/jarvis_agent/task_analyzer.py +5 -2
  14. jarvis/jarvis_agent/task_planner.py +496 -0
  15. jarvis/jarvis_agent/utils.py +5 -1
  16. jarvis/jarvis_agent/web_bridge.py +189 -0
  17. jarvis/jarvis_agent/web_output_sink.py +53 -0
  18. jarvis/jarvis_agent/web_server.py +751 -0
  19. jarvis/jarvis_c2rust/__init__.py +26 -0
  20. jarvis/jarvis_c2rust/cli.py +613 -0
  21. jarvis/jarvis_c2rust/collector.py +258 -0
  22. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  23. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  24. jarvis/jarvis_c2rust/optimizer.py +960 -0
  25. jarvis/jarvis_c2rust/scanner.py +1681 -0
  26. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  27. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  28. jarvis/jarvis_code_agent/code_agent.py +1171 -94
  29. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  30. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  31. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  32. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  33. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  34. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  35. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  36. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  37. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  38. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  39. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  40. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  41. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  42. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  43. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  44. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  45. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  46. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  47. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  48. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  49. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  50. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  51. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  52. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  53. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  54. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  55. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  56. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  57. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  58. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  59. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  60. jarvis/jarvis_code_agent/lint.py +270 -8
  61. jarvis/jarvis_code_agent/utils.py +142 -0
  62. jarvis/jarvis_code_analysis/code_review.py +483 -569
  63. jarvis/jarvis_data/config_schema.json +97 -8
  64. jarvis/jarvis_git_utils/git_commiter.py +38 -26
  65. jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
  66. jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
  67. jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
  68. jarvis/jarvis_multi_agent/__init__.py +239 -25
  69. jarvis/jarvis_multi_agent/main.py +37 -1
  70. jarvis/jarvis_platform/base.py +103 -51
  71. jarvis/jarvis_platform/openai.py +26 -1
  72. jarvis/jarvis_platform/yuanbao.py +1 -1
  73. jarvis/jarvis_platform_manager/service.py +2 -2
  74. jarvis/jarvis_rag/cli.py +4 -4
  75. jarvis/jarvis_sec/__init__.py +3605 -0
  76. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  77. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  78. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  79. jarvis/jarvis_sec/cli.py +116 -0
  80. jarvis/jarvis_sec/report.py +257 -0
  81. jarvis/jarvis_sec/status.py +264 -0
  82. jarvis/jarvis_sec/types.py +20 -0
  83. jarvis/jarvis_sec/workflow.py +219 -0
  84. jarvis/jarvis_stats/cli.py +1 -1
  85. jarvis/jarvis_stats/stats.py +1 -1
  86. jarvis/jarvis_stats/visualizer.py +1 -1
  87. jarvis/jarvis_tools/cli/main.py +1 -0
  88. jarvis/jarvis_tools/execute_script.py +46 -9
  89. jarvis/jarvis_tools/generate_new_tool.py +3 -1
  90. jarvis/jarvis_tools/read_code.py +275 -12
  91. jarvis/jarvis_tools/read_symbols.py +141 -0
  92. jarvis/jarvis_tools/read_webpage.py +5 -3
  93. jarvis/jarvis_tools/registry.py +73 -35
  94. jarvis/jarvis_tools/search_web.py +15 -11
  95. jarvis/jarvis_tools/sub_agent.py +24 -42
  96. jarvis/jarvis_tools/sub_code_agent.py +14 -13
  97. jarvis/jarvis_tools/virtual_tty.py +1 -1
  98. jarvis/jarvis_utils/config.py +187 -35
  99. jarvis/jarvis_utils/embedding.py +3 -0
  100. jarvis/jarvis_utils/git_utils.py +181 -6
  101. jarvis/jarvis_utils/globals.py +3 -3
  102. jarvis/jarvis_utils/http.py +1 -1
  103. jarvis/jarvis_utils/input.py +78 -2
  104. jarvis/jarvis_utils/methodology.py +25 -19
  105. jarvis/jarvis_utils/utils.py +644 -359
  106. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
  107. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  108. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
  109. jarvis/jarvis_agent/config.py +0 -92
  110. jarvis/jarvis_tools/edit_file.py +0 -179
  111. jarvis/jarvis_tools/rewrite_file.py +0 -191
  112. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  113. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  114. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  115. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,8 @@ import signal
6
6
  import subprocess
7
7
  import sys
8
8
  import time
9
+ import atexit
10
+ import errno
9
11
  from pathlib import Path
10
12
  from typing import Any, Callable, Dict, List, Optional, Tuple
11
13
  from datetime import datetime, date
@@ -59,6 +61,10 @@ COMMAND_MAPPING = {
59
61
  "jst": "jarvis-stats",
60
62
  # 记忆整理
61
63
  "jmo": "jarvis-memory-organizer",
64
+ # 安全分析
65
+ "jsec": "jarvis-sec",
66
+ # C2Rust迁移
67
+ "jc2r": "jarvis-c2rust",
62
68
  }
63
69
 
64
70
  # RAG 依赖检测工具函数(更精确)
@@ -205,6 +211,130 @@ def _setup_signal_handler() -> None:
205
211
  signal.signal(signal.SIGINT, sigint_handler)
206
212
 
207
213
 
214
+ # ----------------------------
215
+ # 单实例文件锁(放置于初始化早期使用)
216
+ # ----------------------------
217
+ _INSTANCE_LOCK_PATH: Optional[Path] = None
218
+
219
+
220
+ def _get_instance_lock_path(lock_name: str = "instance.lock") -> Path:
221
+ try:
222
+ data_dir = Path(str(get_data_dir()))
223
+ except Exception:
224
+ data_dir = Path(os.path.expanduser("~/.jarvis"))
225
+ data_dir.mkdir(parents=True, exist_ok=True)
226
+ return data_dir / lock_name
227
+
228
+
229
+ def _read_lock_owner_pid(lock_path: Path) -> Optional[int]:
230
+ try:
231
+ txt = lock_path.read_text(encoding="utf-8", errors="ignore").strip()
232
+ if not txt:
233
+ return None
234
+ try:
235
+ info = json.loads(txt)
236
+ pid = info.get("pid")
237
+ return int(pid) if pid is not None else None
238
+ except Exception:
239
+ # 兼容纯数字PID
240
+ return int(txt)
241
+ except Exception:
242
+ return None
243
+
244
+
245
+ def _is_process_alive(pid: int) -> bool:
246
+ if pid is None or pid <= 0:
247
+ return False
248
+ try:
249
+ os.kill(pid, 0)
250
+ except ProcessLookupError:
251
+ return False
252
+ except PermissionError:
253
+ # 无权限但进程存在
254
+ return True
255
+ except OSError as e:
256
+ # 某些平台上,EPERM 表示进程存在但无权限
257
+ if getattr(e, "errno", None) == errno.EPERM:
258
+ return True
259
+ return False
260
+ else:
261
+ return True
262
+
263
+
264
+ def _release_instance_lock() -> None:
265
+ global _INSTANCE_LOCK_PATH
266
+ try:
267
+ if _INSTANCE_LOCK_PATH and _INSTANCE_LOCK_PATH.exists():
268
+ _INSTANCE_LOCK_PATH.unlink()
269
+ except Exception:
270
+ # 清理失败不影响退出
271
+ pass
272
+ _INSTANCE_LOCK_PATH = None
273
+
274
+
275
+ def _acquire_single_instance_lock(lock_name: str = "instance.lock") -> None:
276
+ """
277
+ 在数据目录(~/.jarvis 或配置的数据目录)下创建实例锁,防止重复启动。
278
+ 如果检测到已有存活实例,提示后退出。
279
+ """
280
+ global _INSTANCE_LOCK_PATH
281
+ lock_path = _get_instance_lock_path(lock_name)
282
+
283
+ # 已存在锁:检查是否为有效存活实例
284
+ if lock_path.exists():
285
+ pid = _read_lock_owner_pid(lock_path)
286
+ if pid and _is_process_alive(pid):
287
+ PrettyOutput.print(
288
+ f"检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。\n"
289
+ f"如果确认不存在正在运行的实例,请删除锁文件后重试:{lock_path}",
290
+ OutputType.WARNING,
291
+ )
292
+ sys.exit(0)
293
+ # 尝试移除陈旧锁
294
+ try:
295
+ lock_path.unlink()
296
+ except Exception:
297
+ PrettyOutput.print(
298
+ f"无法删除旧锁文件:{lock_path},请手动清理后重试。",
299
+ OutputType.ERROR,
300
+ )
301
+ sys.exit(1)
302
+
303
+ # 原子创建锁文件,避免并发竞争
304
+ flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
305
+ try:
306
+ fd = os.open(str(lock_path), flags)
307
+ with os.fdopen(fd, "w", encoding="utf-8") as fp:
308
+ payload = {
309
+ "pid": os.getpid(),
310
+ "time": int(time.time()),
311
+ "argv": sys.argv[:10],
312
+ }
313
+ try:
314
+ fp.write(json.dumps(payload, ensure_ascii=False))
315
+ except Exception:
316
+ fp.write(str(os.getpid()))
317
+ _INSTANCE_LOCK_PATH = lock_path
318
+ atexit.register(_release_instance_lock)
319
+ except FileExistsError:
320
+ # 极端并发下再次校验
321
+ pid = _read_lock_owner_pid(lock_path)
322
+ if pid and _is_process_alive(pid):
323
+ PrettyOutput.print(
324
+ f"检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。",
325
+ OutputType.WARNING,
326
+ )
327
+ sys.exit(0)
328
+ PrettyOutput.print(
329
+ f"锁文件已存在但可能为陈旧状态:{lock_path},请手动删除后重试。",
330
+ OutputType.ERROR,
331
+ )
332
+ sys.exit(1)
333
+ except Exception as e:
334
+ PrettyOutput.print(f"创建实例锁失败: {e}", OutputType.ERROR)
335
+ sys.exit(1)
336
+
337
+
208
338
  def _check_pip_updates() -> bool:
209
339
  """检查pip安装的Jarvis是否有更新
210
340
 
@@ -248,7 +378,7 @@ def _check_pip_updates() -> bool:
248
378
  )
249
379
 
250
380
  # 检测是否在虚拟环境中
251
- in_venv = hasattr(sys, "real_prefix") or (
381
+ hasattr(sys, "real_prefix") or (
252
382
  hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
253
383
  )
254
384
 
@@ -338,14 +468,22 @@ def _check_jarvis_updates() -> bool:
338
468
  返回:
339
469
  bool: 是否需要重启进程
340
470
  """
341
- script_dir = Path(os.path.dirname(os.path.dirname(__file__)))
471
+ # 从当前文件目录向上查找包含 .git 的仓库根目录,修复原先只检查 src/jarvis 的问题
472
+ try:
473
+ script_path = Path(__file__).resolve()
474
+ repo_root: Optional[Path] = None
475
+ for d in [script_path.parent] + list(script_path.parents):
476
+ if (d / ".git").exists():
477
+ repo_root = d
478
+ break
479
+ except Exception:
480
+ repo_root = None
342
481
 
343
- # 先检查是否是git源码安装
344
- git_dir = script_dir / ".git"
345
- if git_dir.exists():
482
+ # 先检查是否是git源码安装(找到仓库根目录即认为是源码安装)
483
+ if repo_root and (repo_root / ".git").exists():
346
484
  from jarvis.jarvis_utils.git_utils import check_and_update_git_repo
347
485
 
348
- return check_and_update_git_repo(str(script_dir))
486
+ return check_and_update_git_repo(str(repo_root))
349
487
 
350
488
  # 检查是否是pip/uv pip安装的版本
351
489
  return _check_pip_updates()
@@ -679,7 +817,7 @@ def _show_usage_stats(welcome_str: str) -> None:
679
817
 
680
818
  # 愿景 Panel
681
819
  vision_text = Text(
682
- "重新定义开发者体验,打破人与工具的界限,构建开发者与AI之间真正的共生伙伴关系。",
820
+ "让开发者与AI成为共生伙伴",
683
821
  justify="center",
684
822
  style="italic",
685
823
  )
@@ -694,7 +832,7 @@ def _show_usage_stats(welcome_str: str) -> None:
694
832
 
695
833
  # 使命 Panel
696
834
  mission_text = Text(
697
- "通过深度人机协作,将开发者的灵感(Vibe)高效落地为代码与行动,释放创造之力。",
835
+ "让灵感高效落地为代码与行动",
698
836
  justify="center",
699
837
  style="italic",
700
838
  )
@@ -1016,359 +1154,465 @@ def _process_env_variables(config_data: dict) -> None:
1016
1154
  )
1017
1155
 
1018
1156
 
1019
- def _collect_optional_config_interactively(
1020
- config_data: dict, ask_all: bool = False
1021
- ) -> bool:
1022
- """
1023
- 复用的交互式配置收集逻辑:
1024
- - ask_all=False(默认):仅对缺省的新功能开关/可选项逐项询问,已存在项跳过
1025
- - ask_all=True:对所有项进行询问,默认值取自当前配置文件,可覆盖现有设置
1026
- - 修改传入的 config_data
1027
- - 包含更多来自 config.py 的可选项
1028
- 返回:
1029
- bool: 是否有变更
1030
- """
1031
- from jarvis.jarvis_utils.input import user_confirm as get_yes_no
1032
- from jarvis.jarvis_utils.input import get_single_line_input
1157
+ def _ask_config_bool(config_data: dict, ask_all: bool, _key: str, _tip: str, _default: bool) -> bool:
1158
+ """询问并设置布尔类型配置项"""
1159
+ try:
1160
+ if not ask_all and _key in config_data:
1161
+ return False
1162
+ from jarvis.jarvis_utils.input import user_confirm as get_yes_no
1163
+ cur = bool(config_data.get(_key, _default))
1164
+ val = get_yes_no(_tip, default=cur)
1165
+ if bool(val) == cur:
1166
+ return False
1167
+ config_data[_key] = bool(val)
1168
+ return True
1169
+ except Exception:
1170
+ return False
1033
1171
 
1034
- def _ask_and_set(_key, _tip, _default, _type="bool"):
1035
- try:
1036
- if not ask_all and _key in config_data:
1037
- return False
1038
- if _type == "bool":
1039
- cur = bool(config_data.get(_key, _default))
1040
- val = get_yes_no(_tip, default=cur)
1041
- # 与当前值相同则不写入,避免冗余
1042
- if bool(val) == cur:
1043
- return False
1044
- config_data[_key] = bool(val)
1045
- else:
1046
- cur = str(config_data.get(_key, _default or ""))
1047
- val = get_single_line_input(f"{_tip}", default=cur)
1048
- v = ("" if val is None else str(val)).strip()
1049
- # 输入与当前值相同则不写入
1050
- if v == cur:
1051
- return False
1052
- config_data[_key] = v
1053
- return True
1054
- except Exception:
1055
- # 异常时不写入,保持精简
1172
+
1173
+ def _ask_config_str(config_data: dict, ask_all: bool, _key: str, _tip: str, _default: str = "") -> bool:
1174
+ """询问并设置字符串类型配置项"""
1175
+ try:
1176
+ if not ask_all and _key in config_data:
1056
1177
  return False
1178
+ from jarvis.jarvis_utils.input import get_single_line_input
1179
+ cur = str(config_data.get(_key, _default or ""))
1180
+ val = get_single_line_input(f"{_tip}", default=cur)
1181
+ v = ("" if val is None else str(val)).strip()
1182
+ if v == cur:
1183
+ return False
1184
+ config_data[_key] = v
1185
+ return True
1186
+ except Exception:
1187
+ return False
1057
1188
 
1058
- def _ask_and_set_optional_str(_key, _tip, _default: str = "") -> bool:
1059
- try:
1060
- if not ask_all and _key in config_data:
1061
- return False
1062
- cur = str(config_data.get(_key, _default or ""))
1063
- val = get_single_line_input(f"{_tip}", default=cur)
1064
- if val is None:
1065
- return False
1066
- s = str(val).strip()
1067
- # 空输入表示不改变
1068
- if s == "":
1069
- return False
1070
- if s == cur:
1071
- return False
1072
- config_data[_key] = s
1073
- return True
1074
- except Exception:
1189
+
1190
+ def _ask_config_optional_str(config_data: dict, ask_all: bool, _key: str, _tip: str, _default: str = "") -> bool:
1191
+ """询问并设置可选字符串类型配置项(空输入表示不改变)"""
1192
+ try:
1193
+ if not ask_all and _key in config_data:
1194
+ return False
1195
+ from jarvis.jarvis_utils.input import get_single_line_input
1196
+ cur = str(config_data.get(_key, _default or ""))
1197
+ val = get_single_line_input(f"{_tip}", default=cur)
1198
+ if val is None:
1075
1199
  return False
1200
+ s = str(val).strip()
1201
+ if s == "" or s == cur:
1202
+ return False
1203
+ config_data[_key] = s
1204
+ return True
1205
+ except Exception:
1206
+ return False
1207
+
1076
1208
 
1077
- def _ask_and_set_int(_key, _tip, _default: int) -> bool:
1209
+ def _ask_config_int(config_data: dict, ask_all: bool, _key: str, _tip: str, _default: int) -> bool:
1210
+ """询问并设置整数类型配置项"""
1211
+ try:
1212
+ if not ask_all and _key in config_data:
1213
+ return False
1214
+ from jarvis.jarvis_utils.input import get_single_line_input
1215
+ cur = str(config_data.get(_key, _default))
1216
+ val_str = get_single_line_input(f"{_tip}", default=cur)
1217
+ s = "" if val_str is None else str(val_str).strip()
1218
+ if s == "" or s == cur:
1219
+ return False
1078
1220
  try:
1079
- if not ask_all and _key in config_data:
1080
- return False
1081
- cur = str(config_data.get(_key, _default))
1082
- val_str = get_single_line_input(f"{_tip}", default=cur)
1083
- s = "" if val_str is None else str(val_str).strip()
1084
- if s == "" or s == cur:
1085
- return False
1086
- try:
1087
- v = int(s)
1088
- except Exception:
1089
- return False
1090
- if str(v) == cur:
1091
- return False
1092
- config_data[_key] = v
1093
- return True
1221
+ v = int(s)
1094
1222
  except Exception:
1095
1223
  return False
1224
+ if str(v) == cur:
1225
+ return False
1226
+ config_data[_key] = v
1227
+ return True
1228
+ except Exception:
1229
+ return False
1096
1230
 
1097
- def _ask_and_set_list(_key, _tip) -> bool:
1098
- try:
1099
- if not ask_all and _key in config_data:
1100
- return False
1101
- cur_val = config_data.get(_key, [])
1102
- if isinstance(cur_val, list):
1103
- cur_display = ", ".join([str(x) for x in cur_val])
1104
- else:
1105
- cur_display = str(cur_val or "")
1106
- val = get_single_line_input(f"{_tip}", default=cur_display)
1107
- if val is None:
1108
- return False
1109
- s = str(val).strip()
1110
- if s == cur_display.strip():
1111
- return False
1112
- if not s:
1113
- # 输入为空表示不改变
1114
- return False
1115
- items = [x.strip() for x in s.split(",") if x.strip()]
1116
- if isinstance(cur_val, list) and items == cur_val:
1117
- return False
1118
- config_data[_key] = items
1119
- return True
1120
- except Exception:
1231
+
1232
+ def _ask_config_list(config_data: dict, ask_all: bool, _key: str, _tip: str) -> bool:
1233
+ """询问并设置列表类型配置项(逗号分隔)"""
1234
+ try:
1235
+ if not ask_all and _key in config_data:
1236
+ return False
1237
+ from jarvis.jarvis_utils.input import get_single_line_input
1238
+ cur_val = config_data.get(_key, [])
1239
+ if isinstance(cur_val, list):
1240
+ cur_display = ", ".join([str(x) for x in cur_val])
1241
+ else:
1242
+ cur_display = str(cur_val or "")
1243
+ val = get_single_line_input(f"{_tip}", default=cur_display)
1244
+ if val is None:
1245
+ return False
1246
+ s = str(val).strip()
1247
+ if s == cur_display.strip():
1248
+ return False
1249
+ if not s:
1250
+ return False
1251
+ items = [x.strip() for x in s.split(",") if x.strip()]
1252
+ if isinstance(cur_val, list) and items == cur_val:
1121
1253
  return False
1254
+ config_data[_key] = items
1255
+ return True
1256
+ except Exception:
1257
+ return False
1122
1258
 
1259
+
1260
+ def _collect_basic_switches(config_data: dict, ask_all: bool) -> bool:
1261
+ """收集基础开关配置"""
1123
1262
  changed = False
1124
- # 现有两个开关
1125
- changed = (
1126
- _ask_and_set(
1127
- "JARVIS_ENABLE_GIT_JCA_SWITCH",
1128
- "是否在检测到Git仓库时,提示并可自动切换到代码开发模式(jca)?",
1129
- False,
1130
- "bool",
1131
- )
1132
- or changed
1133
- )
1134
- changed = (
1135
- _ask_and_set(
1136
- "JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR",
1137
- "在进入默认通用代理前,是否先列出可用配置(agent/multi_agent/roles)供选择?",
1138
- False,
1139
- "bool",
1140
- )
1141
- or changed
1142
- )
1263
+ changed = _ask_config_bool(
1264
+ config_data, ask_all,
1265
+ "JARVIS_ENABLE_GIT_JCA_SWITCH",
1266
+ "是否在检测到Git仓库时,提示并可自动切换到代码开发模式(jca)?",
1267
+ False,
1268
+ ) or changed
1269
+ changed = _ask_config_bool(
1270
+ config_data, ask_all,
1271
+ "JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR",
1272
+ "在进入默认通用代理前,是否先列出可用配置(agent/multi_agent/roles)供选择?",
1273
+ False,
1274
+ ) or changed
1275
+ return changed
1143
1276
 
1144
- # 新增的配置项交互(通用体验相关)
1145
- changed = (
1146
- _ask_and_set(
1147
- "JARVIS_PRETTY_OUTPUT",
1148
- "是否启用更美观的终端输出(Pretty Output)?",
1149
- False,
1150
- "bool",
1151
- )
1152
- or changed
1153
- )
1154
- changed = (
1155
- _ask_and_set(
1156
- "JARVIS_PRINT_PROMPT",
1157
- "是否打印发送给模型的提示词(Prompt)?",
1158
- False,
1159
- "bool",
1160
- )
1161
- or changed
1162
- )
1163
- changed = (
1164
- _ask_and_set(
1165
- "JARVIS_IMMEDIATE_ABORT",
1166
- "是否启用立即中断?\n- 选择 是/true:在对话输出流的每次迭代中检测到用户中断(例如 Ctrl+C)时,立即返回当前已生成的内容并停止继续输出。\n- 选择 否/false:不会在输出过程中立刻返回,而是按既有流程处理(不中途打断输出)。",
1167
- False,
1168
- "bool",
1169
- )
1170
- or changed
1171
- )
1172
- changed = (
1173
- _ask_and_set(
1174
- "JARVIS_ENABLE_STATIC_ANALYSIS",
1175
- "是否启用静态代码分析(Static Analysis)?",
1176
- True,
1177
- "bool",
1178
- )
1179
- or changed
1180
- )
1181
- changed = (
1182
- _ask_and_set(
1183
- "JARVIS_USE_METHODOLOGY",
1184
- "是否启用方法论系统(Methodology)?",
1185
- True,
1186
- "bool",
1187
- )
1188
- or changed
1189
- )
1190
- changed = (
1191
- _ask_and_set(
1192
- "JARVIS_USE_ANALYSIS",
1193
- "是否启用分析流程(Analysis)?",
1194
- True,
1195
- "bool",
1196
- )
1197
- or changed
1198
- )
1199
- changed = (
1200
- _ask_and_set(
1201
- "JARVIS_FORCE_SAVE_MEMORY",
1202
- "是否强制保存会话记忆?",
1203
- True,
1204
- "bool",
1205
- )
1206
- or changed
1207
- )
1208
1277
 
1209
- # 代码与工具操作安全提示
1210
- changed = (
1211
- _ask_and_set(
1212
- "JARVIS_EXECUTE_TOOL_CONFIRM",
1213
- "执行工具前是否需要确认?",
1214
- False,
1215
- "bool",
1216
- )
1217
- or changed
1218
- )
1219
- changed = (
1220
- _ask_and_set(
1221
- "JARVIS_CONFIRM_BEFORE_APPLY_PATCH",
1222
- "应用补丁前是否需要确认?",
1223
- False,
1224
- "bool",
1225
- )
1226
- or changed
1227
- )
1278
+ def _collect_ui_experience_config(config_data: dict, ask_all: bool) -> bool:
1279
+ """收集UI体验相关配置"""
1280
+ changed = False
1281
+ try:
1282
+ import platform as _platform_mod
1283
+ _default_pretty = False if _platform_mod.system() == "Windows" else True
1284
+ except Exception:
1285
+ _default_pretty = True
1286
+
1287
+ changed = _ask_config_bool(
1288
+ config_data, ask_all,
1289
+ "JARVIS_PRETTY_OUTPUT",
1290
+ "是否启用更美观的终端输出(Pretty Output)?",
1291
+ _default_pretty,
1292
+ ) or changed
1293
+ changed = _ask_config_bool(
1294
+ config_data, ask_all,
1295
+ "JARVIS_PRINT_PROMPT",
1296
+ "是否打印发送给模型的提示词(Prompt)?",
1297
+ False,
1298
+ ) or changed
1299
+ changed = _ask_config_bool(
1300
+ config_data, ask_all,
1301
+ "JARVIS_IMMEDIATE_ABORT",
1302
+ "是否启用立即中断?\n- 选择 是/true:在对话输出流的每次迭代中检测到用户中断(例如 Ctrl+C)时,立即返回当前已生成的内容并停止继续输出。\n- 选择 否/false:不会在输出过程中立刻返回,而是按既有流程处理(不中途打断输出)。",
1303
+ False,
1304
+ ) or changed
1305
+ return changed
1228
1306
 
1229
- # 数据目录与最大输入Token
1230
- from jarvis.jarvis_utils.config import get_data_dir as _get_data_dir # lazy import
1231
1307
 
1232
- changed = (
1233
- _ask_and_set_optional_str(
1234
- "JARVIS_DATA_PATH",
1235
- f"是否自定义数据目录路径(JARVIS_DATA_PATH)?留空使用默认: {_get_data_dir()}",
1236
- )
1237
- or changed
1238
- )
1239
- changed = (
1240
- _ask_and_set_int(
1241
- "JARVIS_MAX_INPUT_TOKEN_COUNT",
1242
- "自定义最大输入Token数量(留空使用默认: 32000)",
1243
- 32000,
1244
- )
1245
- or changed
1246
- )
1247
- changed = (
1248
- _ask_and_set_int(
1249
- "JARVIS_TOOL_FILTER_THRESHOLD",
1250
- "设置AI工具筛选阈值 (当可用工具数超过此值时触发AI筛选, 默认30)",
1251
- 30,
1252
- )
1253
- or changed
1254
- )
1308
+ def _collect_analysis_config(config_data: dict, ask_all: bool) -> bool:
1309
+ """收集代码分析相关配置"""
1310
+ changed = False
1311
+ changed = _ask_config_bool(
1312
+ config_data, ask_all,
1313
+ "JARVIS_ENABLE_STATIC_ANALYSIS",
1314
+ "是否启用静态代码分析(Static Analysis)?",
1315
+ True,
1316
+ ) or changed
1317
+ changed = _ask_config_bool(
1318
+ config_data, ask_all,
1319
+ "JARVIS_ENABLE_BUILD_VALIDATION",
1320
+ "是否启用构建验证(Build Validation)?在代码编辑后自动验证代码能否成功编译/构建。",
1321
+ True,
1322
+ ) or changed
1323
+ changed = _ask_config_int(
1324
+ config_data, ask_all,
1325
+ "JARVIS_BUILD_VALIDATION_TIMEOUT",
1326
+ "构建验证的超时时间(秒,默认30秒)",
1327
+ 30,
1328
+ ) or changed
1329
+ changed = _ask_config_bool(
1330
+ config_data, ask_all,
1331
+ "JARVIS_ENABLE_IMPACT_ANALYSIS",
1332
+ "是否启用编辑影响范围分析(Impact Analysis)?分析代码编辑的影响范围,识别可能受影响的文件、函数、测试等。",
1333
+ True,
1334
+ ) or changed
1335
+ return changed
1255
1336
 
1256
- # 目录类配置(逗号分隔)
1257
- changed = (
1258
- _ask_and_set_list(
1259
- "JARVIS_TOOL_LOAD_DIRS",
1260
- "指定工具加载目录(逗号分隔,留空跳过):",
1261
- )
1262
- or changed
1263
- )
1264
- changed = (
1265
- _ask_and_set_list(
1266
- "JARVIS_METHODOLOGY_DIRS",
1267
- "指定方法论加载目录(逗号分隔,留空跳过):",
1268
- )
1269
- or changed
1270
- )
1271
- changed = (
1272
- _ask_and_set_list(
1273
- "JARVIS_AGENT_DEFINITION_DIRS",
1274
- "指定 agent 定义加载目录(逗号分隔,留空跳过):",
1275
- )
1276
- or changed
1277
- )
1278
- changed = (
1279
- _ask_and_set_list(
1280
- "JARVIS_MULTI_AGENT_DIRS",
1281
- "指定 multi_agent 加载目录(逗号分隔,留空跳过):",
1282
- )
1283
- or changed
1284
- )
1285
- changed = (
1286
- _ask_and_set_list(
1287
- "JARVIS_ROLES_DIRS",
1288
- "指定 roles 加载目录(逗号分隔,留空跳过):",
1289
- )
1290
- or changed
1291
- )
1292
1337
 
1293
- # Web 搜索配置(可选)
1294
- changed = (
1295
- _ask_and_set_optional_str(
1296
- "JARVIS_WEB_SEARCH_PLATFORM",
1297
- "配置 Web 搜索平台名称(留空跳过):",
1298
- )
1299
- or changed
1300
- )
1301
- changed = (
1302
- _ask_and_set_optional_str(
1303
- "JARVIS_WEB_SEARCH_MODEL",
1304
- "配置 Web 搜索模型名称(留空跳过):",
1305
- )
1306
- or changed
1307
- )
1338
+ def _collect_agent_features_config(config_data: dict, ask_all: bool) -> bool:
1339
+ """收集Agent功能相关配置"""
1340
+ changed = False
1341
+ changed = _ask_config_bool(
1342
+ config_data, ask_all,
1343
+ "JARVIS_USE_METHODOLOGY",
1344
+ "是否启用方法论系统(Methodology)?",
1345
+ True,
1346
+ ) or changed
1347
+ changed = _ask_config_bool(
1348
+ config_data, ask_all,
1349
+ "JARVIS_USE_ANALYSIS",
1350
+ "是否启用分析流程(Analysis)?",
1351
+ True,
1352
+ ) or changed
1353
+ changed = _ask_config_bool(
1354
+ config_data, ask_all,
1355
+ "JARVIS_FORCE_SAVE_MEMORY",
1356
+ "是否强制保存会话记忆?",
1357
+ False,
1358
+ ) or changed
1359
+ return changed
1308
1360
 
1309
- # Git 校验模式
1310
- def _ask_git_check_mode() -> bool:
1311
- try:
1312
- _key = "JARVIS_GIT_CHECK_MODE"
1313
- if not ask_all and _key in config_data:
1314
- return False
1315
1361
 
1316
- from jarvis.jarvis_utils.input import get_choice
1317
- from jarvis.jarvis_utils.config import get_git_check_mode
1318
-
1319
- current_mode = config_data.get(_key, get_git_check_mode())
1320
- choices = ["strict", "warn"]
1321
- tip = (
1322
- "请选择 Git 仓库检查模式 (JARVIS_GIT_CHECK_MODE):\n"
1323
- "此设置决定了当在 Git 仓库中检测到未提交的更改时,Jarvis应如何处理。\n"
1324
- "这对于确保代码修改和提交操作在干净的工作区上进行至关重要。\n"
1325
- " - strict: (推荐) 如果存在未提交的更改,则中断相关操作(如代码修改、自动提交)。\n"
1326
- " 这可以防止意外覆盖或丢失本地工作。\n"
1327
- " - warn: 如果存在未提交的更改,仅显示警告信息,然后继续执行操作。\n"
1328
- " 适用于您希望绕过检查并自行管理仓库状态的场景。"
1329
- )
1362
+ def _collect_session_config(config_data: dict, ask_all: bool) -> bool:
1363
+ """收集会话与调试相关配置"""
1364
+ changed = False
1365
+ changed = _ask_config_bool(
1366
+ config_data, ask_all,
1367
+ "JARVIS_SAVE_SESSION_HISTORY",
1368
+ "是否保存会话记录?",
1369
+ False,
1370
+ ) or changed
1371
+ changed = _ask_config_bool(
1372
+ config_data, ask_all,
1373
+ "JARVIS_PRINT_ERROR_TRACEBACK",
1374
+ "是否在错误输出时打印回溯调用链?",
1375
+ False,
1376
+ ) or changed
1377
+ changed = _ask_config_bool(
1378
+ config_data, ask_all,
1379
+ "JARVIS_SKIP_PREDEFINED_TASKS",
1380
+ "是否跳过预定义任务加载(不读取 pre-command 列表)?",
1381
+ False,
1382
+ ) or changed
1383
+ return changed
1330
1384
 
1331
1385
 
1386
+ def _collect_safety_config(config_data: dict, ask_all: bool) -> bool:
1387
+ """收集代码与工具操作安全提示配置"""
1388
+ changed = False
1389
+ changed = _ask_config_bool(
1390
+ config_data, ask_all,
1391
+ "JARVIS_EXECUTE_TOOL_CONFIRM",
1392
+ "执行工具前是否需要确认?",
1393
+ False,
1394
+ ) or changed
1395
+ changed = _ask_config_bool(
1396
+ config_data, ask_all,
1397
+ "JARVIS_CONFIRM_BEFORE_APPLY_PATCH",
1398
+ "应用补丁前是否需要确认?",
1399
+ False,
1400
+ ) or changed
1401
+ return changed
1332
1402
 
1333
- new_mode = get_choice(
1334
- tip,
1335
- choices,
1336
- )
1337
1403
 
1338
- if new_mode == current_mode:
1339
- return False
1404
+ def _collect_data_and_token_config(config_data: dict, ask_all: bool) -> bool:
1405
+ """收集数据目录与最大输入Token配置"""
1406
+ changed = False
1407
+ from jarvis.jarvis_utils.config import get_data_dir as _get_data_dir
1408
+ changed = _ask_config_optional_str(
1409
+ config_data, ask_all,
1410
+ "JARVIS_DATA_PATH",
1411
+ f"是否自定义数据目录路径(JARVIS_DATA_PATH)?留空使用默认: {_get_data_dir()}",
1412
+ ) or changed
1413
+ changed = _ask_config_int(
1414
+ config_data, ask_all,
1415
+ "JARVIS_MAX_INPUT_TOKEN_COUNT",
1416
+ "自定义最大输入Token数量(留空使用默认: 32000)",
1417
+ 32000,
1418
+ ) or changed
1419
+ changed = _ask_config_int(
1420
+ config_data, ask_all,
1421
+ "JARVIS_TOOL_FILTER_THRESHOLD",
1422
+ "设置AI工具筛选阈值 (当可用工具数超过此值时触发AI筛选, 默认30)",
1423
+ 30,
1424
+ ) or changed
1425
+ return changed
1340
1426
 
1341
- config_data[_key] = new_mode
1342
- return True
1343
- except Exception:
1427
+
1428
+ def _collect_planning_config(config_data: dict, ask_all: bool) -> bool:
1429
+ """收集规划相关配置"""
1430
+ changed = False
1431
+ changed = _ask_config_bool(
1432
+ config_data, ask_all,
1433
+ "JARVIS_PLAN_ENABLED",
1434
+ "是否默认启用任务规划?当 Agent 初始化时 plan 参数未指定,将从此配置加载",
1435
+ True,
1436
+ ) or changed
1437
+ changed = _ask_config_int(
1438
+ config_data, ask_all,
1439
+ "JARVIS_PLAN_MAX_DEPTH",
1440
+ "任务规划的最大层数(限制递归拆分深度,默认2;仅在启用规划时生效)",
1441
+ 2,
1442
+ ) or changed
1443
+ return changed
1444
+
1445
+
1446
+ def _collect_advanced_config(config_data: dict, ask_all: bool) -> bool:
1447
+ """收集高级配置(自动总结、脚本超时等)"""
1448
+ changed = False
1449
+ changed = _ask_config_int(
1450
+ config_data, ask_all,
1451
+ "JARVIS_AUTO_SUMMARY_ROUNDS",
1452
+ "基于对话轮次的自动总结阈值(达到该轮次后自动总结并清理历史,默认50)",
1453
+ 50,
1454
+ ) or changed
1455
+ changed = _ask_config_int(
1456
+ config_data, ask_all,
1457
+ "JARVIS_SCRIPT_EXECUTION_TIMEOUT",
1458
+ "脚本执行超时时间(秒,默认300,仅非交互模式生效)",
1459
+ 300,
1460
+ ) or changed
1461
+ changed = _ask_config_int(
1462
+ config_data, ask_all,
1463
+ "JARVIS_ADDON_PROMPT_THRESHOLD",
1464
+ "附加提示的触发阈值(字符数,默认1024)。当消息长度超过此值时,会自动添加默认的附加提示",
1465
+ 1024,
1466
+ ) or changed
1467
+ changed = _ask_config_bool(
1468
+ config_data, ask_all,
1469
+ "JARVIS_ENABLE_INTENT_RECOGNITION",
1470
+ "是否启用意图识别功能?用于智能上下文推荐中的LLM意图提取和语义分析",
1471
+ True,
1472
+ ) or changed
1473
+ return changed
1474
+
1475
+
1476
+ def _collect_directory_config(config_data: dict, ask_all: bool) -> bool:
1477
+ """收集目录类配置(逗号分隔)"""
1478
+ changed = False
1479
+ changed = _ask_config_list(
1480
+ config_data, ask_all,
1481
+ "JARVIS_TOOL_LOAD_DIRS",
1482
+ "指定工具加载目录(逗号分隔,留空跳过):",
1483
+ ) or changed
1484
+ changed = _ask_config_list(
1485
+ config_data, ask_all,
1486
+ "JARVIS_METHODOLOGY_DIRS",
1487
+ "指定方法论加载目录(逗号分隔,留空跳过):",
1488
+ ) or changed
1489
+ changed = _ask_config_list(
1490
+ config_data, ask_all,
1491
+ "JARVIS_AGENT_DEFINITION_DIRS",
1492
+ "指定 agent 定义加载目录(逗号分隔,留空跳过):",
1493
+ ) or changed
1494
+ changed = _ask_config_list(
1495
+ config_data, ask_all,
1496
+ "JARVIS_MULTI_AGENT_DIRS",
1497
+ "指定 multi_agent 加载目录(逗号分隔,留空跳过):",
1498
+ ) or changed
1499
+ changed = _ask_config_list(
1500
+ config_data, ask_all,
1501
+ "JARVIS_ROLES_DIRS",
1502
+ "指定 roles 加载目录(逗号分隔,留空跳过):",
1503
+ ) or changed
1504
+ changed = _ask_config_list(
1505
+ config_data, ask_all,
1506
+ "JARVIS_AFTER_TOOL_CALL_CB_DIRS",
1507
+ "指定工具调用后回调实现目录(逗号分隔,留空跳过):",
1508
+ ) or changed
1509
+ return changed
1510
+
1511
+
1512
+ def _collect_web_search_config(config_data: dict, ask_all: bool) -> bool:
1513
+ """收集Web搜索配置"""
1514
+ changed = False
1515
+ changed = _ask_config_optional_str(
1516
+ config_data, ask_all,
1517
+ "JARVIS_WEB_SEARCH_PLATFORM",
1518
+ "配置 Web 搜索平台名称(留空跳过):",
1519
+ ) or changed
1520
+ changed = _ask_config_optional_str(
1521
+ config_data, ask_all,
1522
+ "JARVIS_WEB_SEARCH_MODEL",
1523
+ "配置 Web 搜索模型名称(留空跳过):",
1524
+ ) or changed
1525
+ return changed
1526
+
1527
+
1528
+ def _ask_git_check_mode(config_data: dict, ask_all: bool) -> bool:
1529
+ """询问Git校验模式"""
1530
+ try:
1531
+ _key = "JARVIS_GIT_CHECK_MODE"
1532
+ if not ask_all and _key in config_data:
1533
+ return False
1534
+ from jarvis.jarvis_utils.input import get_choice
1535
+ from jarvis.jarvis_utils.config import get_git_check_mode
1536
+ current_mode = config_data.get(_key, get_git_check_mode())
1537
+ choices = ["strict", "warn"]
1538
+ tip = (
1539
+ "请选择 Git 仓库检查模式 (JARVIS_GIT_CHECK_MODE):\n"
1540
+ "此设置决定了当在 Git 仓库中检测到未提交的更改时,Jarvis应如何处理。\n"
1541
+ "这对于确保代码修改和提交操作在干净的工作区上进行至关重要。\n"
1542
+ " - strict: (推荐) 如果存在未提交的更改,则中断相关操作(如代码修改、自动提交)。\n"
1543
+ " 这可以防止意外覆盖或丢失本地工作。\n"
1544
+ " - warn: 如果存在未提交的更改,仅显示警告信息,然后继续执行操作。\n"
1545
+ " 适用于您希望绕过检查并自行管理仓库状态的场景。"
1546
+ )
1547
+ new_mode = get_choice(tip, choices)
1548
+ if new_mode == current_mode:
1344
1549
  return False
1550
+ config_data[_key] = new_mode
1551
+ return True
1552
+ except Exception:
1553
+ return False
1345
1554
 
1346
- changed = _ask_git_check_mode() or changed
1347
1555
 
1348
- # Git 提交提示词(可选)
1349
- changed = (
1350
- _ask_and_set_optional_str(
1351
- "JARVIS_GIT_COMMIT_PROMPT",
1352
- "自定义 Git 提交提示模板(留空跳过):",
1556
+ def _ask_patch_format_mode(config_data: dict, ask_all: bool) -> bool:
1557
+ """询问补丁格式模式"""
1558
+ try:
1559
+ _key = "JARVIS_PATCH_FORMAT"
1560
+ if not ask_all and _key in config_data:
1561
+ return False
1562
+ from jarvis.jarvis_utils.input import get_choice
1563
+ from jarvis.jarvis_utils.config import get_patch_format
1564
+ current_mode = config_data.get(_key, get_patch_format())
1565
+ choices = ["all", "search", "search_range"]
1566
+ tip = (
1567
+ "请选择补丁格式处理模式 (JARVIS_PATCH_FORMAT):\n"
1568
+ "该设置影响 edit_file_handler 在处理补丁时允许的匹配方式。\n"
1569
+ " - all: 同时支持 SEARCH 与 SEARCH_START/SEARCH_END 两种模式(默认)。\n"
1570
+ " - search: 仅允许精确片段匹配(SEARCH)。更稳定,适合较弱模型或严格控制改动。\n"
1571
+ " - search_range: 仅允许范围匹配(SEARCH_START/SEARCH_END)。更灵活,适合较强模型和块内细粒度修改。"
1353
1572
  )
1354
- or changed
1355
- )
1573
+ new_mode = get_choice(tip, choices)
1574
+ if new_mode == current_mode:
1575
+ return False
1576
+ config_data[_key] = new_mode
1577
+ return True
1578
+ except Exception:
1579
+ return False
1580
+
1581
+
1582
+ def _collect_git_config(config_data: dict, ask_all: bool) -> bool:
1583
+ """收集Git相关配置"""
1584
+ changed = False
1585
+ changed = _ask_git_check_mode(config_data, ask_all) or changed
1586
+ changed = _ask_patch_format_mode(config_data, ask_all) or changed
1587
+ changed = _ask_config_optional_str(
1588
+ config_data, ask_all,
1589
+ "JARVIS_GIT_COMMIT_PROMPT",
1590
+ "自定义 Git 提交提示模板(留空跳过):",
1591
+ ) or changed
1592
+ return changed
1593
+
1356
1594
 
1357
- # RAG 配置(可选)
1595
+ def _collect_rag_config(config_data: dict, ask_all: bool) -> bool:
1596
+ """收集RAG配置"""
1597
+ changed = False
1358
1598
  try:
1359
1599
  from jarvis.jarvis_utils.config import (
1360
1600
  get_rag_embedding_model as _get_rag_embedding_model,
1361
1601
  get_rag_rerank_model as _get_rag_rerank_model,
1362
1602
  )
1363
-
1603
+ from jarvis.jarvis_utils.input import user_confirm as get_yes_no
1604
+ from jarvis.jarvis_utils.input import get_single_line_input
1605
+
1364
1606
  rag_default_embed = _get_rag_embedding_model()
1365
1607
  rag_default_rerank = _get_rag_rerank_model()
1366
1608
  except Exception:
1367
1609
  rag_default_embed = "BAAI/bge-m3"
1368
1610
  rag_default_rerank = "BAAI/bge-reranker-v2-m3"
1369
-
1611
+ get_yes_no = None
1612
+ get_single_line_input = None
1613
+
1370
1614
  try:
1371
- if "JARVIS_RAG" not in config_data:
1615
+ if "JARVIS_RAG" not in config_data and get_yes_no:
1372
1616
  if get_yes_no("是否配置 RAG 检索增强参数?", default=False):
1373
1617
  rag_conf: Dict[str, Any] = {}
1374
1618
  emb = get_single_line_input(
@@ -1395,49 +1639,75 @@ def _collect_optional_config_interactively(
1395
1639
  changed = True
1396
1640
  except Exception:
1397
1641
  pass
1642
+ return changed
1398
1643
 
1399
- # 中心仓库配置
1400
- changed = (
1401
- _ask_and_set(
1402
- "JARVIS_CENTRAL_METHODOLOGY_REPO",
1403
- "请输入中心方法论仓库地址(可留空跳过):",
1404
- "",
1405
- "str",
1406
- )
1407
- or changed
1408
- )
1409
- changed = (
1410
- _ask_and_set(
1411
- "JARVIS_CENTRAL_TOOL_REPO",
1412
- "请输入中心工具仓库地址(可留空跳过):",
1413
- "",
1414
- "str",
1415
- )
1416
- or changed
1417
- )
1418
-
1419
- # 已移除 LLM 组配置交互
1420
1644
 
1421
- # 已移除 RAG 组配置交互
1645
+ def _collect_central_repo_config(config_data: dict, ask_all: bool) -> bool:
1646
+ """收集中心仓库配置"""
1647
+ changed = False
1648
+ changed = _ask_config_str(
1649
+ config_data, ask_all,
1650
+ "JARVIS_CENTRAL_METHODOLOGY_REPO",
1651
+ "请输入中心方法论仓库路径或Git地址(可留空跳过):",
1652
+ "",
1653
+ ) or changed
1654
+ changed = _ask_config_str(
1655
+ config_data, ask_all,
1656
+ "JARVIS_CENTRAL_TOOL_REPO",
1657
+ "请输入中心工具仓库路径或Git地址(可留空跳过):",
1658
+ "",
1659
+ ) or changed
1660
+ return changed
1422
1661
 
1423
- # 已移除 工具组配置交互
1424
1662
 
1425
- # 已移除:替换映射(JARVIS_REPLACE_MAP)的交互式配置,保持最简交互
1426
- # SHELL 覆盖(可选)
1663
+ def _collect_shell_config(config_data: dict, ask_all: bool) -> bool:
1664
+ """收集SHELL覆盖配置"""
1665
+ changed = False
1427
1666
  try:
1667
+ import os
1428
1668
  default_shell = os.getenv("SHELL", "/bin/bash")
1429
- changed = (
1430
- _ask_and_set_optional_str(
1431
- "SHELL",
1432
- f"覆盖 SHELL 路径(留空使用系统默认: {default_shell}):",
1433
- default_shell,
1434
- )
1435
- or changed
1436
- )
1669
+ changed = _ask_config_optional_str(
1670
+ config_data, ask_all,
1671
+ "SHELL",
1672
+ f"覆盖 SHELL 路径(留空使用系统默认: {default_shell}):",
1673
+ default_shell,
1674
+ ) or changed
1437
1675
  except Exception:
1438
1676
  pass
1677
+ return changed
1678
+
1439
1679
 
1440
- # 已移除:MCP(JARVIS_MCP)的交互式配置,保持最简交互
1680
+ def _collect_optional_config_interactively(
1681
+ config_data: dict, ask_all: bool = False
1682
+ ) -> bool:
1683
+ """
1684
+ 复用的交互式配置收集逻辑:
1685
+ - ask_all=False(默认):仅对缺省的新功能开关/可选项逐项询问,已存在项跳过
1686
+ - ask_all=True:对所有项进行询问,默认值取自当前配置文件,可覆盖现有设置
1687
+ - 修改传入的 config_data
1688
+ - 包含更多来自 config.py 的可选项
1689
+ 返回:
1690
+ bool: 是否有变更
1691
+ """
1692
+ changed = False
1693
+
1694
+ # 收集各类配置
1695
+ changed = _collect_basic_switches(config_data, ask_all) or changed
1696
+ changed = _collect_ui_experience_config(config_data, ask_all) or changed
1697
+ changed = _collect_analysis_config(config_data, ask_all) or changed
1698
+ changed = _collect_agent_features_config(config_data, ask_all) or changed
1699
+ changed = _collect_session_config(config_data, ask_all) or changed
1700
+ changed = _collect_safety_config(config_data, ask_all) or changed
1701
+ changed = _collect_data_and_token_config(config_data, ask_all) or changed
1702
+ changed = _collect_planning_config(config_data, ask_all) or changed
1703
+ changed = _collect_advanced_config(config_data, ask_all) or changed
1704
+ changed = _collect_directory_config(config_data, ask_all) or changed
1705
+ changed = _collect_web_search_config(config_data, ask_all) or changed
1706
+ changed = _collect_git_config(config_data, ask_all) or changed
1707
+ changed = _collect_rag_config(config_data, ask_all) or changed
1708
+ changed = _collect_central_repo_config(config_data, ask_all) or changed
1709
+ changed = _collect_shell_config(config_data, ask_all) or changed
1710
+
1441
1711
  return changed
1442
1712
 
1443
1713
 
@@ -1680,11 +1950,11 @@ def while_success(func: Callable[[], Any], sleep_time: float = 0.1, max_retries:
1680
1950
  try:
1681
1951
  result = func()
1682
1952
  break
1683
- except Exception:
1953
+ except Exception as e:
1684
1954
  retry_count += 1
1685
1955
  if retry_count < max_retries:
1686
1956
  PrettyOutput.print(
1687
- f"发生异常,重试中 ({retry_count}/{max_retries}),等待 {sleep_time}s...",
1957
+ f"发生异常:\n{e}\n重试中 ({retry_count}/{max_retries}),等待 {sleep_time}s...",
1688
1958
  OutputType.WARNING,
1689
1959
  )
1690
1960
  time.sleep(sleep_time)
@@ -1730,9 +2000,22 @@ def get_file_md5(filepath: str) -> str:
1730
2000
  filepath: 要计算哈希的文件路径
1731
2001
 
1732
2002
  返回:
1733
- str: 文件内容的MD5哈希值
2003
+ str: 文件内容的MD5哈希值(为降低内存占用,仅读取前100MB进行计算)
1734
2004
  """
1735
- return hashlib.md5(open(filepath, "rb").read(100 * 1024 * 1024)).hexdigest()
2005
+ # 采用流式读取,避免一次性加载100MB到内存
2006
+ h = hashlib.md5()
2007
+ max_bytes = 100 * 1024 * 1024 # 与原实现保持一致:仅读取前100MB
2008
+ buf_size = 8 * 1024 * 1024 # 8MB缓冲
2009
+ read_bytes = 0
2010
+ with open(filepath, "rb") as f:
2011
+ while read_bytes < max_bytes:
2012
+ to_read = min(buf_size, max_bytes - read_bytes)
2013
+ chunk = f.read(to_read)
2014
+ if not chunk:
2015
+ break
2016
+ h.update(chunk)
2017
+ read_bytes += len(chunk)
2018
+ return h.hexdigest()
1736
2019
 
1737
2020
 
1738
2021
  def get_file_line_count(filename: str) -> int:
@@ -1745,7 +2028,9 @@ def get_file_line_count(filename: str) -> int:
1745
2028
  int: 文件中的行数,如果文件无法读取则返回0
1746
2029
  """
1747
2030
  try:
1748
- return len(open(filename, "r", encoding="utf-8", errors="ignore").readlines())
2031
+ # 使用流式逐行计数,避免将整个文件读入内存
2032
+ with open(filename, "r", encoding="utf-8", errors="ignore") as f:
2033
+ return sum(1 for _ in f)
1749
2034
  except Exception:
1750
2035
  return 0
1751
2036