jarvis-ai-assistant 0.3.20__py3-none-any.whl → 0.3.22__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 (57) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +24 -3
  3. jarvis/jarvis_agent/config_editor.py +5 -1
  4. jarvis/jarvis_agent/edit_file_handler.py +15 -9
  5. jarvis/jarvis_agent/jarvis.py +99 -3
  6. jarvis/jarvis_agent/memory_manager.py +3 -3
  7. jarvis/jarvis_agent/share_manager.py +3 -1
  8. jarvis/jarvis_agent/task_analyzer.py +0 -1
  9. jarvis/jarvis_agent/task_manager.py +15 -5
  10. jarvis/jarvis_agent/tool_executor.py +2 -2
  11. jarvis/jarvis_code_agent/code_agent.py +42 -18
  12. jarvis/jarvis_git_utils/git_commiter.py +3 -6
  13. jarvis/jarvis_mcp/sse_mcp_client.py +9 -3
  14. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -5
  15. jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
  16. jarvis/jarvis_methodology/main.py +4 -4
  17. jarvis/jarvis_multi_agent/__init__.py +3 -3
  18. jarvis/jarvis_platform/base.py +10 -5
  19. jarvis/jarvis_platform/kimi.py +18 -6
  20. jarvis/jarvis_platform/tongyi.py +18 -5
  21. jarvis/jarvis_platform/yuanbao.py +10 -3
  22. jarvis/jarvis_platform_manager/main.py +21 -7
  23. jarvis/jarvis_platform_manager/service.py +4 -3
  24. jarvis/jarvis_rag/cli.py +61 -22
  25. jarvis/jarvis_rag/embedding_manager.py +10 -3
  26. jarvis/jarvis_rag/llm_interface.py +4 -1
  27. jarvis/jarvis_rag/query_rewriter.py +3 -1
  28. jarvis/jarvis_rag/rag_pipeline.py +47 -3
  29. jarvis/jarvis_rag/retriever.py +240 -2
  30. jarvis/jarvis_smart_shell/main.py +59 -18
  31. jarvis/jarvis_stats/cli.py +11 -9
  32. jarvis/jarvis_stats/stats.py +14 -8
  33. jarvis/jarvis_stats/storage.py +23 -6
  34. jarvis/jarvis_tools/cli/main.py +63 -29
  35. jarvis/jarvis_tools/edit_file.py +17 -90
  36. jarvis/jarvis_tools/file_analyzer.py +0 -1
  37. jarvis/jarvis_tools/generate_new_tool.py +3 -3
  38. jarvis/jarvis_tools/read_code.py +0 -1
  39. jarvis/jarvis_tools/read_webpage.py +14 -4
  40. jarvis/jarvis_tools/registry.py +16 -9
  41. jarvis/jarvis_tools/retrieve_memory.py +0 -1
  42. jarvis/jarvis_tools/save_memory.py +0 -1
  43. jarvis/jarvis_tools/search_web.py +0 -2
  44. jarvis/jarvis_tools/sub_agent.py +197 -0
  45. jarvis/jarvis_tools/sub_code_agent.py +194 -0
  46. jarvis/jarvis_tools/virtual_tty.py +21 -13
  47. jarvis/jarvis_utils/config.py +35 -5
  48. jarvis/jarvis_utils/input.py +297 -56
  49. jarvis/jarvis_utils/methodology.py +3 -1
  50. jarvis/jarvis_utils/output.py +5 -2
  51. jarvis/jarvis_utils/utils.py +483 -170
  52. {jarvis_ai_assistant-0.3.20.dist-info → jarvis_ai_assistant-0.3.22.dist-info}/METADATA +10 -2
  53. {jarvis_ai_assistant-0.3.20.dist-info → jarvis_ai_assistant-0.3.22.dist-info}/RECORD +57 -55
  54. {jarvis_ai_assistant-0.3.20.dist-info → jarvis_ai_assistant-0.3.22.dist-info}/WHEEL +0 -0
  55. {jarvis_ai_assistant-0.3.20.dist-info → jarvis_ai_assistant-0.3.22.dist-info}/entry_points.txt +0 -0
  56. {jarvis_ai_assistant-0.3.20.dist-info → jarvis_ai_assistant-0.3.22.dist-info}/licenses/LICENSE +0 -0
  57. {jarvis_ai_assistant-0.3.20.dist-info → jarvis_ai_assistant-0.3.22.dist-info}/top_level.txt +0 -0
@@ -29,6 +29,9 @@ from jarvis.jarvis_utils.globals import get_in_chat, get_interrupt, set_interrup
29
29
  from jarvis.jarvis_utils.input import user_confirm
30
30
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
31
31
 
32
+ # 向后兼容:导出 get_yes_no 供外部模块引用
33
+ get_yes_no = user_confirm
34
+
32
35
  g_config_file = None
33
36
 
34
37
  COMMAND_MAPPING = {
@@ -601,6 +604,19 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
601
604
  welcome_str: 欢迎信息字符串
602
605
  config_file: 配置文件路径,默认为None(使用~/.jarvis/config.yaml)
603
606
  """
607
+ # 0. 检查是否处于Jarvis打开的终端环境,避免嵌套
608
+ try:
609
+ if os.environ.get("JARVIS_TERMINAL") == "1":
610
+ PrettyOutput.print(
611
+ "检测到当前终端由 Jarvis 打开。再次启动可能导致嵌套。",
612
+ OutputType.WARNING,
613
+ )
614
+ if not user_confirm("是否仍要继续启动 Jarvis?", default=False):
615
+ PrettyOutput.print("已取消启动以避免终端嵌套。", OutputType.INFO)
616
+ sys.exit(0)
617
+ except Exception:
618
+ pass
619
+
604
620
  # 1. 设置信号处理
605
621
  _setup_signal_handler()
606
622
 
@@ -717,7 +733,7 @@ def _interactive_config_setup(config_file_path: Path):
717
733
  except Exception:
718
734
  PrettyOutput.print("测试失败", OutputType.ERROR)
719
735
 
720
- # 5. 生成并保存配置
736
+ # 5. 交互式确认并应用配置(不直接生成配置文件)
721
737
  config_data = {
722
738
  "ENV": env_vars,
723
739
  "JARVIS_PLATFORM": platform_name,
@@ -726,57 +742,36 @@ def _interactive_config_setup(config_file_path: Path):
726
742
  "JARVIS_THINKING_MODEL": model_name,
727
743
  }
728
744
 
729
- if test_passed:
730
- PrettyOutput.print("配置已测试通过,将为您生成配置文件。", OutputType.SUCCESS)
731
- else:
732
- if not get_yes_no("配置测试失败,您确定要保存这个配置吗?"):
733
- PrettyOutput.print("配置未保存。", OutputType.INFO)
745
+ if not test_passed:
746
+ if not get_yes_no("配置测试失败,是否仍要应用该配置并继续?", default=False):
747
+ PrettyOutput.print("已取消配置。", OutputType.INFO)
734
748
  sys.exit(0)
735
749
 
750
+ # 6. 选择其他功能开关与可选项(复用统一逻辑)
751
+ _collect_optional_config_interactively(config_data)
752
+
753
+ # 7. 应用到当前会话并写入配置文件(基于交互结果,不从默认值生成)
754
+ set_global_env_data(config_data)
755
+ _process_env_variables(config_data)
736
756
  try:
737
757
  schema_path = (
738
758
  Path(__file__).parent.parent / "jarvis_data" / "config_schema.json"
739
759
  )
760
+ config_file_path.parent.mkdir(parents=True, exist_ok=True)
761
+ header = ""
740
762
  if schema_path.exists():
741
- config_file_path.parent.mkdir(parents=True, exist_ok=True)
742
- # 使用现有的函数生成默认结构,然后覆盖引导配置
743
- generate_default_config(str(schema_path.absolute()), str(config_file_path))
744
-
745
- # 读取刚生成的默认配置
746
- with open(config_file_path, "r", encoding="utf-8") as f:
747
- content = f.read()
748
- default_config = yaml.safe_load(
749
- content.split("\n", 1)[1]
750
- ) # 跳过 schema 行
751
-
752
- # 合并用户配置
753
- if default_config is None:
754
- default_config = {}
755
- default_config.update(config_data)
756
-
757
- # 写回合并后的配置
758
- final_content = (
759
- f"# yaml-language-server: $schema={str(schema_path.absolute())}\n"
760
- )
761
- final_content += yaml.dump(
762
- default_config, allow_unicode=True, sort_keys=False
763
- )
764
- with open(config_file_path, "w", encoding="utf-8") as f:
765
- f.write(final_content)
766
-
767
- PrettyOutput.print(
768
- f"配置文件已生成: {config_file_path}", OutputType.SUCCESS
769
- )
770
- PrettyOutput.print("配置完成,请重新启动Jarvis。", OutputType.INFO)
771
- sys.exit(0)
772
- else:
773
- PrettyOutput.print(
774
- "未找到config schema,无法生成配置文件。", OutputType.ERROR
775
- )
776
- sys.exit(1)
777
-
763
+ header = f"# yaml-language-server: $schema={str(schema_path.absolute())}\n"
764
+ _prune_defaults_with_schema(config_data)
765
+ yaml_str = yaml.dump(config_data, allow_unicode=True, sort_keys=False)
766
+ with open(config_file_path, "w", encoding="utf-8") as f:
767
+ if header:
768
+ f.write(header)
769
+ f.write(yaml_str)
770
+ PrettyOutput.print(f"配置文件已生成: {config_file_path}", OutputType.SUCCESS)
771
+ PrettyOutput.print("配置完成,请重新启动Jarvis。", OutputType.INFO)
772
+ sys.exit(0)
778
773
  except Exception:
779
- PrettyOutput.print("生成配置文件失败", OutputType.ERROR)
774
+ PrettyOutput.print("写入配置文件失败", OutputType.ERROR)
780
775
  sys.exit(1)
781
776
 
782
777
 
@@ -857,152 +852,410 @@ def _process_env_variables(config_data: dict) -> None:
857
852
  )
858
853
 
859
854
 
860
- def _load_and_process_config(jarvis_dir: str, config_file: str) -> None:
861
- """加载并处理配置文件
862
-
863
- 功能:
864
- 1. 读取配置文件
865
- 2. 确保schema声明存在
866
- 3. 保存配置到全局变量
867
- 4. 处理环境变量
868
-
869
- 参数:
870
- jarvis_dir: Jarvis数据目录路径
871
- config_file: 配置文件路径
855
+ def _collect_optional_config_interactively(
856
+ config_data: dict, ask_all: bool = False
857
+ ) -> bool:
858
+ """
859
+ 复用的交互式配置收集逻辑:
860
+ - ask_all=False(默认):仅对缺省的新功能开关/可选项逐项询问,已存在项跳过
861
+ - ask_all=True:对所有项进行询问,默认值取自当前配置文件,可覆盖现有设置
862
+ - 修改传入的 config_data
863
+ - 包含更多来自 config.py 的可选项
864
+ 返回:
865
+ bool: 是否有变更
872
866
  """
873
867
  from jarvis.jarvis_utils.input import user_confirm as get_yes_no
874
868
  from jarvis.jarvis_utils.input import get_single_line_input
875
869
 
876
- try:
877
- content, config_data = _load_config_file(config_file)
878
- _ensure_schema_declaration(jarvis_dir, config_file, content, config_data)
879
- set_global_env_data(config_data)
880
- _process_env_variables(config_data)
870
+ def _ask_and_set(_key, _tip, _default, _type="bool"):
871
+ try:
872
+ if not ask_all and _key in config_data:
873
+ return False
874
+ if _type == "bool":
875
+ cur = bool(config_data.get(_key, _default))
876
+ val = get_yes_no(_tip, default=cur)
877
+ # 与当前值相同则不写入,避免冗余
878
+ if bool(val) == cur:
879
+ return False
880
+ config_data[_key] = bool(val)
881
+ else:
882
+ cur = str(config_data.get(_key, _default or ""))
883
+ val = get_single_line_input(f"{_tip}", default=cur)
884
+ v = ("" if val is None else str(val)).strip()
885
+ # 输入与当前值相同则不写入
886
+ if v == cur:
887
+ return False
888
+ config_data[_key] = v
889
+ return True
890
+ except Exception:
891
+ # 异常时不写入,保持精简
892
+ return False
893
+
894
+ def _ask_and_set_optional_str(_key, _tip, _default: str = "") -> bool:
895
+ try:
896
+ if not ask_all and _key in config_data:
897
+ return False
898
+ cur = str(config_data.get(_key, _default or ""))
899
+ val = get_single_line_input(f"{_tip}", default=cur)
900
+ if val is None:
901
+ return False
902
+ s = str(val).strip()
903
+ # 空输入表示不改变
904
+ if s == "":
905
+ return False
906
+ if s == cur:
907
+ return False
908
+ config_data[_key] = s
909
+ return True
910
+ except Exception:
911
+ return False
881
912
 
882
- # 首次运行提示:为新功能开关询问用户(默认值见各配置的getter)
883
- def _ask_and_set(_key, _tip, _default, _type="bool"):
913
+ def _ask_and_set_int(_key, _tip, _default: int) -> bool:
914
+ try:
915
+ if not ask_all and _key in config_data:
916
+ return False
917
+ cur = str(config_data.get(_key, _default))
918
+ val_str = get_single_line_input(f"{_tip}", default=cur)
919
+ s = "" if val_str is None else str(val_str).strip()
920
+ if s == "" or s == cur:
921
+ return False
884
922
  try:
885
- if _key in config_data:
886
- return False
887
- if _type == "bool":
888
- val = get_yes_no(_tip, default=bool(_default))
889
- config_data[_key] = bool(val)
890
- else:
891
- val = get_single_line_input(f"{_tip}", default=str(_default or ""))
892
- config_data[_key] = val.strip()
893
- return True
923
+ v = int(s)
894
924
  except Exception:
895
- # 出现异常时按默认值设置,避免中断流程
896
- config_data[_key] = (
897
- bool(_default) if _type == "bool" else str(_default or "")
898
- )
899
- return True
925
+ return False
926
+ if str(v) == cur:
927
+ return False
928
+ config_data[_key] = v
929
+ return True
930
+ except Exception:
931
+ return False
900
932
 
901
- changed = False
902
- # 现有两个开关
903
- changed = (
904
- _ask_and_set(
905
- "JARVIS_ENABLE_GIT_JCA_SWITCH",
906
- "是否在检测到Git仓库时,提示并可自动切换到代码开发模式(jca)?",
907
- False,
908
- "bool",
909
- )
910
- or changed
933
+ def _ask_and_set_list(_key, _tip) -> bool:
934
+ try:
935
+ if not ask_all and _key in config_data:
936
+ return False
937
+ cur_val = config_data.get(_key, [])
938
+ if isinstance(cur_val, list):
939
+ cur_display = ", ".join([str(x) for x in cur_val])
940
+ else:
941
+ cur_display = str(cur_val or "")
942
+ val = get_single_line_input(f"{_tip}", default=cur_display)
943
+ if val is None:
944
+ return False
945
+ s = str(val).strip()
946
+ if s == cur_display.strip():
947
+ return False
948
+ if not s:
949
+ # 输入为空表示不改变
950
+ return False
951
+ items = [x.strip() for x in s.split(",") if x.strip()]
952
+ if isinstance(cur_val, list) and items == cur_val:
953
+ return False
954
+ config_data[_key] = items
955
+ return True
956
+ except Exception:
957
+ return False
958
+
959
+ changed = False
960
+ # 现有两个开关
961
+ changed = (
962
+ _ask_and_set(
963
+ "JARVIS_ENABLE_GIT_JCA_SWITCH",
964
+ "是否在检测到Git仓库时,提示并可自动切换到代码开发模式(jca)?",
965
+ False,
966
+ "bool",
911
967
  )
912
- changed = (
913
- _ask_and_set(
914
- "JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR",
915
- "在进入默认通用代理前,是否先列出可用配置(agent/multi_agent/roles)供选择?",
916
- False,
917
- "bool",
918
- )
919
- or changed
968
+ or changed
969
+ )
970
+ changed = (
971
+ _ask_and_set(
972
+ "JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR",
973
+ "在进入默认通用代理前,是否先列出可用配置(agent/multi_agent/roles)供选择?",
974
+ False,
975
+ "bool",
920
976
  )
977
+ or changed
978
+ )
921
979
 
922
- # 新增的配置项交互
923
- changed = (
924
- _ask_and_set(
925
- "JARVIS_PRETTY_OUTPUT",
926
- "是否启用更美观的终端输出(Pretty Output)?",
927
- False,
928
- "bool",
929
- )
930
- or changed
980
+ # 新增的配置项交互(通用体验相关)
981
+ changed = (
982
+ _ask_and_set(
983
+ "JARVIS_PRETTY_OUTPUT",
984
+ "是否启用更美观的终端输出(Pretty Output)?",
985
+ False,
986
+ "bool",
931
987
  )
932
- changed = (
933
- _ask_and_set(
934
- "JARVIS_PRINT_PROMPT",
935
- "是否打印发送给模型的提示词(Prompt)?",
936
- False,
937
- "bool",
938
- )
939
- or changed
988
+ or changed
989
+ )
990
+ changed = (
991
+ _ask_and_set(
992
+ "JARVIS_PRINT_PROMPT",
993
+ "是否打印发送给模型的提示词(Prompt)?",
994
+ False,
995
+ "bool",
940
996
  )
941
- changed = (
942
- _ask_and_set(
943
- "JARVIS_IMMEDIATE_ABORT",
944
- "是否启用立即中断?\n- 选择 是/true:在对话输出流的每次迭代中检测到用户中断(例如 Ctrl+C)时,立即返回当前已生成的内容并停止继续输出。\n- 选择 否/false:不会在输出过程中立刻返回,而是按既有流程处理(不中途打断输出)。",
945
- False,
946
- "bool",
947
- )
948
- or changed
997
+ or changed
998
+ )
999
+ changed = (
1000
+ _ask_and_set(
1001
+ "JARVIS_IMMEDIATE_ABORT",
1002
+ "是否启用立即中断?\n- 选择 是/true:在对话输出流的每次迭代中检测到用户中断(例如 Ctrl+C)时,立即返回当前已生成的内容并停止继续输出。\n- 选择 否/false:不会在输出过程中立刻返回,而是按既有流程处理(不中途打断输出)。",
1003
+ False,
1004
+ "bool",
949
1005
  )
950
- changed = (
951
- _ask_and_set(
952
- "JARVIS_ENABLE_STATIC_ANALYSIS",
953
- "是否启用静态代码分析(Static Analysis)?",
954
- True,
955
- "bool",
956
- )
957
- or changed
1006
+ or changed
1007
+ )
1008
+ changed = (
1009
+ _ask_and_set(
1010
+ "JARVIS_ENABLE_STATIC_ANALYSIS",
1011
+ "是否启用静态代码分析(Static Analysis)?",
1012
+ True,
1013
+ "bool",
958
1014
  )
959
- changed = (
960
- _ask_and_set(
961
- "JARVIS_USE_METHODOLOGY",
962
- "是否启用方法论系统(Methodology)?",
963
- True,
964
- "bool",
965
- )
966
- or changed
1015
+ or changed
1016
+ )
1017
+ changed = (
1018
+ _ask_and_set(
1019
+ "JARVIS_USE_METHODOLOGY",
1020
+ "是否启用方法论系统(Methodology)?",
1021
+ True,
1022
+ "bool",
967
1023
  )
968
- changed = (
969
- _ask_and_set(
970
- "JARVIS_USE_ANALYSIS",
971
- "是否启用分析流程(Analysis)?",
972
- True,
973
- "bool",
974
- )
975
- or changed
1024
+ or changed
1025
+ )
1026
+ changed = (
1027
+ _ask_and_set(
1028
+ "JARVIS_USE_ANALYSIS",
1029
+ "是否启用分析流程(Analysis)?",
1030
+ True,
1031
+ "bool",
976
1032
  )
977
- changed = (
978
- _ask_and_set(
979
- "JARVIS_FORCE_SAVE_MEMORY",
980
- "是否强制保存会话记忆?",
981
- True,
982
- "bool",
983
- )
984
- or changed
1033
+ or changed
1034
+ )
1035
+ changed = (
1036
+ _ask_and_set(
1037
+ "JARVIS_FORCE_SAVE_MEMORY",
1038
+ "是否强制保存会话记忆?",
1039
+ True,
1040
+ "bool",
985
1041
  )
986
- changed = (
987
- _ask_and_set(
988
- "JARVIS_CENTRAL_METHODOLOGY_REPO",
989
- "请输入中心方法论仓库地址(可留空跳过):",
990
- "",
991
- "str",
992
- )
993
- or changed
1042
+ or changed
1043
+ )
1044
+
1045
+ # 代码与工具操作安全提示
1046
+ changed = (
1047
+ _ask_and_set(
1048
+ "JARVIS_EXECUTE_TOOL_CONFIRM",
1049
+ "执行工具前是否需要确认?",
1050
+ False,
1051
+ "bool",
1052
+ )
1053
+ or changed
1054
+ )
1055
+ changed = (
1056
+ _ask_and_set(
1057
+ "JARVIS_CONFIRM_BEFORE_APPLY_PATCH",
1058
+ "应用补丁前是否需要确认?",
1059
+ False,
1060
+ "bool",
1061
+ )
1062
+ or changed
1063
+ )
1064
+
1065
+ # 数据目录与最大输入Token
1066
+ from jarvis.jarvis_utils.config import get_data_dir as _get_data_dir # lazy import
1067
+
1068
+ changed = (
1069
+ _ask_and_set_optional_str(
1070
+ "JARVIS_DATA_PATH",
1071
+ f"是否自定义数据目录路径(JARVIS_DATA_PATH)?留空使用默认: {_get_data_dir()}",
1072
+ )
1073
+ or changed
1074
+ )
1075
+ changed = (
1076
+ _ask_and_set_int(
1077
+ "JARVIS_MAX_INPUT_TOKEN_COUNT",
1078
+ "自定义最大输入Token数量(留空使用默认: 32000)",
1079
+ 32000,
1080
+ )
1081
+ or changed
1082
+ )
1083
+
1084
+ # 目录类配置(逗号分隔)
1085
+ changed = (
1086
+ _ask_and_set_list(
1087
+ "JARVIS_TOOL_LOAD_DIRS",
1088
+ "指定工具加载目录(逗号分隔,留空跳过):",
1089
+ )
1090
+ or changed
1091
+ )
1092
+ changed = (
1093
+ _ask_and_set_list(
1094
+ "JARVIS_METHODOLOGY_DIRS",
1095
+ "指定方法论加载目录(逗号分隔,留空跳过):",
1096
+ )
1097
+ or changed
1098
+ )
1099
+ changed = (
1100
+ _ask_and_set_list(
1101
+ "JARVIS_AGENT_DEFINITION_DIRS",
1102
+ "指定 agent 定义加载目录(逗号分隔,留空跳过):",
1103
+ )
1104
+ or changed
1105
+ )
1106
+ changed = (
1107
+ _ask_and_set_list(
1108
+ "JARVIS_MULTI_AGENT_DIRS",
1109
+ "指定 multi_agent 加载目录(逗号分隔,留空跳过):",
1110
+ )
1111
+ or changed
1112
+ )
1113
+ changed = (
1114
+ _ask_and_set_list(
1115
+ "JARVIS_ROLES_DIRS",
1116
+ "指定 roles 加载目录(逗号分隔,留空跳过):",
1117
+ )
1118
+ or changed
1119
+ )
1120
+
1121
+ # Web 搜索配置(可选)
1122
+ changed = (
1123
+ _ask_and_set_optional_str(
1124
+ "JARVIS_WEB_SEARCH_PLATFORM",
1125
+ "配置 Web 搜索平台名称(留空跳过):",
1126
+ )
1127
+ or changed
1128
+ )
1129
+ changed = (
1130
+ _ask_and_set_optional_str(
1131
+ "JARVIS_WEB_SEARCH_MODEL",
1132
+ "配置 Web 搜索模型名称(留空跳过):",
1133
+ )
1134
+ or changed
1135
+ )
1136
+
1137
+ # Git 提交提示词(可选)
1138
+ changed = (
1139
+ _ask_and_set_optional_str(
1140
+ "JARVIS_GIT_COMMIT_PROMPT",
1141
+ "自定义 Git 提交提示模板(留空跳过):",
1142
+ )
1143
+ or changed
1144
+ )
1145
+
1146
+ # RAG 配置(可选)
1147
+ try:
1148
+ from jarvis.jarvis_utils.config import (
1149
+ get_rag_embedding_model as _get_rag_embedding_model,
1150
+ get_rag_rerank_model as _get_rag_rerank_model,
1151
+ )
1152
+
1153
+ rag_default_embed = _get_rag_embedding_model()
1154
+ rag_default_rerank = _get_rag_rerank_model()
1155
+ except Exception:
1156
+ rag_default_embed = "BAAI/bge-m3"
1157
+ rag_default_rerank = "BAAI/bge-reranker-v2-m3"
1158
+
1159
+ try:
1160
+ if "JARVIS_RAG" not in config_data:
1161
+ if get_yes_no("是否配置 RAG 检索增强参数?", default=False):
1162
+ rag_conf = {}
1163
+ emb = get_single_line_input(
1164
+ f"RAG 嵌入模型(留空使用默认: {rag_default_embed}):",
1165
+ default="",
1166
+ ).strip()
1167
+ rerank = get_single_line_input(
1168
+ f"RAG rerank 模型(留空使用默认: {rag_default_rerank}):",
1169
+ default="",
1170
+ ).strip()
1171
+ use_bm25 = get_yes_no("RAG 是否使用 BM25?", default=True)
1172
+ use_rerank = get_yes_no("RAG 是否使用 rerank?", default=True)
1173
+ if emb:
1174
+ rag_conf["embedding_model"] = emb
1175
+ else:
1176
+ rag_conf["embedding_model"] = rag_default_embed
1177
+ if rerank:
1178
+ rag_conf["rerank_model"] = rerank
1179
+ else:
1180
+ rag_conf["rerank_model"] = rag_default_rerank
1181
+ rag_conf["use_bm25"] = bool(use_bm25)
1182
+ rag_conf["use_rerank"] = bool(use_rerank)
1183
+ config_data["JARVIS_RAG"] = rag_conf
1184
+ changed = True
1185
+ except Exception:
1186
+ pass
1187
+
1188
+ # 中心仓库配置
1189
+ changed = (
1190
+ _ask_and_set(
1191
+ "JARVIS_CENTRAL_METHODOLOGY_REPO",
1192
+ "请输入中心方法论仓库地址(可留空跳过):",
1193
+ "",
1194
+ "str",
1195
+ )
1196
+ or changed
1197
+ )
1198
+ changed = (
1199
+ _ask_and_set(
1200
+ "JARVIS_CENTRAL_TOOL_REPO",
1201
+ "请输入中心工具仓库地址(可留空跳过):",
1202
+ "",
1203
+ "str",
994
1204
  )
1205
+ or changed
1206
+ )
1207
+
1208
+ # 已移除 LLM 组配置交互
1209
+
1210
+ # 已移除 RAG 组配置交互
1211
+
1212
+ # 已移除 工具组配置交互
1213
+
1214
+ # 已移除:替换映射(JARVIS_REPLACE_MAP)的交互式配置,保持最简交互
1215
+ # SHELL 覆盖(可选)
1216
+ try:
1217
+ default_shell = os.getenv("SHELL", "/bin/bash")
995
1218
  changed = (
996
- _ask_and_set(
997
- "JARVIS_CENTRAL_TOOL_REPO",
998
- "请输入中心工具仓库地址(可留空跳过):",
999
- "",
1000
- "str",
1219
+ _ask_and_set_optional_str(
1220
+ "SHELL",
1221
+ f"覆盖 SHELL 路径(留空使用系统默认: {default_shell}):",
1222
+ default_shell,
1001
1223
  )
1002
1224
  or changed
1003
1225
  )
1226
+ except Exception:
1227
+ pass
1228
+
1229
+ # 已移除:MCP(JARVIS_MCP)的交互式配置,保持最简交互
1230
+ return changed
1231
+
1232
+
1233
+ def _load_and_process_config(jarvis_dir: str, config_file: str) -> None:
1234
+ """加载并处理配置文件
1235
+
1236
+ 功能:
1237
+ 1. 读取配置文件
1238
+ 2. 确保schema声明存在
1239
+ 3. 保存配置到全局变量
1240
+ 4. 处理环境变量
1241
+
1242
+ 参数:
1243
+ jarvis_dir: Jarvis数据目录路径
1244
+ config_file: 配置文件路径
1245
+ """
1246
+ from jarvis.jarvis_utils.input import user_confirm as get_yes_no
1247
+ from jarvis.jarvis_utils.input import get_single_line_input
1004
1248
 
1005
- if changed:
1249
+ try:
1250
+ content, config_data = _load_config_file(config_file)
1251
+ _ensure_schema_declaration(jarvis_dir, config_file, content, config_data)
1252
+ set_global_env_data(config_data)
1253
+ _process_env_variables(config_data)
1254
+
1255
+ # 加载 schema 默认并剔除等于默认值的项
1256
+ pruned = _prune_defaults_with_schema(config_data)
1257
+
1258
+ if pruned:
1006
1259
  # 保留schema声明,如无则自动补充
1007
1260
  header = ""
1008
1261
  try:
@@ -1079,6 +1332,69 @@ def generate_default_config(schema_path: str, output_path: str) -> None:
1079
1332
  f.write(content)
1080
1333
 
1081
1334
 
1335
+ def _load_default_config_from_schema() -> dict:
1336
+ """从 schema 生成默认配置字典,用于对比并剔除等于默认值的键"""
1337
+ try:
1338
+ schema_path = (
1339
+ Path(__file__).parent.parent / "jarvis_data" / "config_schema.json"
1340
+ )
1341
+ if not schema_path.exists():
1342
+ return {}
1343
+ with open(schema_path, "r", encoding="utf-8") as f:
1344
+ schema = json.load(f)
1345
+
1346
+ def _generate_from_schema(schema_dict: Dict[str, Any]) -> Dict[str, Any]:
1347
+ cfg: Dict[str, Any] = {}
1348
+ if isinstance(schema_dict, dict) and "properties" in schema_dict:
1349
+ for key, value in schema_dict["properties"].items():
1350
+ if "default" in value:
1351
+ cfg[key] = value["default"]
1352
+ elif value.get("type") == "array":
1353
+ cfg[key] = []
1354
+ elif "properties" in value:
1355
+ cfg[key] = _generate_from_schema(value)
1356
+ return cfg
1357
+
1358
+ return _generate_from_schema(schema)
1359
+ except Exception:
1360
+ return {}
1361
+
1362
+
1363
+ def _prune_defaults_with_schema(config_data: dict) -> bool:
1364
+ """
1365
+ 删除与 schema 默认值一致的配置项,返回是否发生了变更
1366
+ 仅处理 schema 中定义的键,未在 schema 中的键不会被修改
1367
+ """
1368
+ defaults = _load_default_config_from_schema()
1369
+ if not defaults or not isinstance(config_data, dict):
1370
+ return False
1371
+
1372
+ changed = False
1373
+
1374
+ def _prune_node(node: dict, default_node: dict):
1375
+ nonlocal changed
1376
+ for key in list(node.keys()):
1377
+ if key in default_node:
1378
+ dv = default_node[key]
1379
+ v = node[key]
1380
+ if isinstance(dv, dict) and isinstance(v, dict):
1381
+ _prune_node(v, dv)
1382
+ if not v:
1383
+ del node[key]
1384
+ changed = True
1385
+ elif isinstance(dv, list) and isinstance(v, list):
1386
+ if v == dv:
1387
+ del node[key]
1388
+ changed = True
1389
+ else:
1390
+ if v == dv:
1391
+ del node[key]
1392
+ changed = True
1393
+
1394
+ _prune_node(config_data, defaults)
1395
+ return changed
1396
+
1397
+
1082
1398
  def _read_old_config_file(config_file):
1083
1399
  """读取并解析旧格式的env配置文件
1084
1400
 
@@ -1151,9 +1467,7 @@ def while_success(func: Callable[[], Any], sleep_time: float = 0.1) -> Any:
1151
1467
  try:
1152
1468
  return func()
1153
1469
  except Exception as e:
1154
- PrettyOutput.print(
1155
- f"重试中,等待 {sleep_time}s...", OutputType.WARNING
1156
- )
1470
+ PrettyOutput.print(f"重试中,等待 {sleep_time}s...", OutputType.WARNING)
1157
1471
  time.sleep(sleep_time)
1158
1472
  continue
1159
1473
 
@@ -1258,7 +1572,6 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
1258
1572
  if not git_dir.is_dir():
1259
1573
  return
1260
1574
 
1261
-
1262
1575
  try:
1263
1576
  # 检查是否有远程仓库
1264
1577
  remote_result = subprocess.run(