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