jarvis-ai-assistant 0.3.23__py3-none-any.whl → 0.3.25__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 (43) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +96 -13
  3. jarvis/jarvis_agent/agent_manager.py +0 -3
  4. jarvis/jarvis_agent/jarvis.py +19 -34
  5. jarvis/jarvis_agent/main.py +2 -8
  6. jarvis/jarvis_code_agent/code_agent.py +5 -11
  7. jarvis/jarvis_code_analysis/code_review.py +12 -40
  8. jarvis/jarvis_data/config_schema.json +11 -18
  9. jarvis/jarvis_git_utils/git_commiter.py +11 -25
  10. jarvis/jarvis_mcp/sse_mcp_client.py +4 -3
  11. jarvis/jarvis_mcp/streamable_mcp_client.py +9 -8
  12. jarvis/jarvis_memory_organizer/memory_organizer.py +46 -53
  13. jarvis/jarvis_methodology/main.py +4 -2
  14. jarvis/jarvis_platform/base.py +90 -21
  15. jarvis/jarvis_platform/kimi.py +16 -22
  16. jarvis/jarvis_platform/registry.py +7 -14
  17. jarvis/jarvis_platform/tongyi.py +21 -32
  18. jarvis/jarvis_platform/yuanbao.py +15 -17
  19. jarvis/jarvis_platform_manager/main.py +14 -51
  20. jarvis/jarvis_rag/cli.py +21 -13
  21. jarvis/jarvis_rag/embedding_manager.py +138 -6
  22. jarvis/jarvis_rag/llm_interface.py +0 -2
  23. jarvis/jarvis_rag/rag_pipeline.py +41 -17
  24. jarvis/jarvis_rag/reranker.py +24 -2
  25. jarvis/jarvis_rag/retriever.py +21 -23
  26. jarvis/jarvis_smart_shell/main.py +1 -10
  27. jarvis/jarvis_tools/cli/main.py +22 -15
  28. jarvis/jarvis_tools/edit_file.py +6 -6
  29. jarvis/jarvis_tools/execute_script.py +1 -2
  30. jarvis/jarvis_tools/file_analyzer.py +12 -6
  31. jarvis/jarvis_tools/registry.py +13 -10
  32. jarvis/jarvis_tools/sub_agent.py +5 -8
  33. jarvis/jarvis_tools/sub_code_agent.py +5 -5
  34. jarvis/jarvis_utils/config.py +24 -10
  35. jarvis/jarvis_utils/input.py +8 -5
  36. jarvis/jarvis_utils/methodology.py +11 -6
  37. jarvis/jarvis_utils/utils.py +29 -12
  38. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/METADATA +10 -3
  39. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/RECORD +43 -43
  40. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/WHEEL +0 -0
  41. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/entry_points.txt +0 -0
  42. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/licenses/LICENSE +0 -0
  43. {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.25.dist-info}/top_level.txt +0 -0
@@ -184,32 +184,26 @@ class ChromaRetriever:
184
184
  deleted = result["deleted"]
185
185
  if not changed and not deleted:
186
186
  return
187
+ # 为避免在循环中逐条打印,先拼接后统一打印
188
+ lines: list[str] = []
187
189
  if changed:
188
- PrettyOutput.print(
189
- f"检测到 {len(changed)} 个已索引文件发生变化,建议重新索引以保证检索准确性。",
190
- OutputType.WARNING,
190
+ lines.append(
191
+ f"检测到 {len(changed)} 个已索引文件发生变化,建议重新索引以保证检索准确性。"
191
192
  )
192
- for p in changed[:5]:
193
- PrettyOutput.print(f" 变更: {p}", OutputType.WARNING)
193
+ lines.extend([f" 变更: {p}" for p in changed[:5]])
194
194
  if len(changed) > 5:
195
- PrettyOutput.print(
196
- f" ... 以及另外 {len(changed) - 5} 个文件", OutputType.WARNING
197
- )
195
+ lines.append(f" ... 以及另外 {len(changed) - 5} 个文件")
198
196
  if deleted:
199
- PrettyOutput.print(
200
- f"检测到 {len(deleted)} 个已索引文件已被删除,建议清理并重新索引。",
201
- OutputType.WARNING,
197
+ lines.append(
198
+ f"检测到 {len(deleted)} 个已索引文件已被删除,建议清理并重新索引。"
202
199
  )
203
- for p in deleted[:5]:
204
- PrettyOutput.print(f" 删除: {p}", OutputType.WARNING)
200
+ lines.extend([f" 删除: {p}" for p in deleted[:5]])
205
201
  if len(deleted) > 5:
206
- PrettyOutput.print(
207
- f" ... 以及另外 {len(deleted) - 5} 个文件", OutputType.WARNING
208
- )
209
- PrettyOutput.print(
210
- "提示:请使用 'jarvis-rag add <路径>' 重新索引相关文件,以更新向量库与BM25索引。",
211
- OutputType.INFO,
202
+ lines.append(f" ... 以及另外 {len(deleted) - 5} 个文件")
203
+ lines.append(
204
+ "提示:请使用 'jarvis-rag add <路径>' 重新索引相关文件,以更新向量库与BM25索引。"
212
205
  )
206
+ PrettyOutput.print("\n".join(lines), OutputType.WARNING)
213
207
 
214
208
  def detect_index_changes(self) -> Dict[str, List[str]]:
215
209
  """
@@ -253,14 +247,18 @@ class ChromaRetriever:
253
247
  return
254
248
 
255
249
  # 先处理删除
250
+ delete_errors: list[str] = []
256
251
  for src in deleted:
257
252
  try:
258
253
  self.collection.delete(where={"source": src}) # type: ignore[arg-type]
259
254
  except Exception as e:
260
- PrettyOutput.print(f"删除源 '{src}' 时出错: {e}", OutputType.WARNING)
255
+ delete_errors.append(f"删除源 '{src}' 时出错: {e}")
256
+ if delete_errors:
257
+ PrettyOutput.print("\n".join(delete_errors), OutputType.WARNING)
261
258
 
262
259
  # 再处理变更(重建)
263
260
  docs_to_add: List[Document] = []
261
+ rebuild_errors: list[str] = []
264
262
  for src in changed:
265
263
  try:
266
264
  # 删除旧条目
@@ -275,9 +273,9 @@ class ChromaRetriever:
275
273
  Document(page_content=content, metadata={"source": src})
276
274
  )
277
275
  except Exception as e:
278
- PrettyOutput.print(
279
- f"重建源 '{src}' 内容时出错: {e}", OutputType.WARNING
280
- )
276
+ rebuild_errors.append(f"重建源 '{src}' 内容时出错: {e}")
277
+ if rebuild_errors:
278
+ PrettyOutput.print("\n".join(rebuild_errors), OutputType.WARNING)
281
279
 
282
280
  if docs_to_add:
283
281
  try:
@@ -24,20 +24,11 @@ Example:
24
24
 
25
25
  def execute_command(command: str, should_run: bool) -> None:
26
26
  """Print command without execution"""
27
- PrettyOutput.print(command, OutputType.CODE, lang="bash")
27
+ print(command)
28
28
  if should_run:
29
29
  os.system(command)
30
30
 
31
31
 
32
- def _check_fish_shell() -> bool:
33
- """Check if current shell is fish
34
-
35
- Returns:
36
- bool: True if fish shell, False otherwise
37
- """
38
- return get_shell_name() == "fish"
39
-
40
-
41
32
  def _get_config_file() -> str:
42
33
  """Get fish config file path
43
34
 
@@ -39,18 +39,22 @@ def list_tools(
39
39
  )
40
40
  else:
41
41
  PrettyOutput.section("可用工具列表", OutputType.SYSTEM)
42
+ # 为避免 PrettyOutput 对每行加框造成信息稀疏,先拼接字符串再统一打印
43
+ lines = []
44
+ import json as _json # local import to ensure available
42
45
  for tool in tools:
43
- PrettyOutput.print(f"\n{tool['name']}", OutputType.SUCCESS)
44
- PrettyOutput.print(f" 描述: {tool['description']}", OutputType.INFO)
46
+ lines.append(f"\n{tool['name']}")
47
+ lines.append(f" 描述: {tool['description']}")
45
48
  if detailed:
46
- PrettyOutput.print(" 参数:", OutputType.INFO)
47
- import json as _json # local import to ensure available
48
-
49
- PrettyOutput.print(
50
- _json.dumps(tool["parameters"], ensure_ascii=False, indent=2),
51
- OutputType.CODE,
52
- lang="json",
53
- )
49
+ lines.append(" 参数:")
50
+ # 使用 Markdown 代码块统一展示参数
51
+ lines.append("```json")
52
+ try:
53
+ lines.append(_json.dumps(tool["parameters"], ensure_ascii=False, indent=2))
54
+ except Exception:
55
+ lines.append(str(tool.get("parameters")))
56
+ lines.append("```")
57
+ PrettyOutput.print("\n".join(lines), OutputType.INFO, lang="markdown")
54
58
 
55
59
 
56
60
  @app.command("stat")
@@ -202,15 +206,18 @@ def call_tool(
202
206
  missing_params = [p for p in required_params if p not in tool_args]
203
207
 
204
208
  if missing_params:
205
- PrettyOutput.print(
206
- f"错误: 缺少必需参数: {', '.join(missing_params)}", OutputType.ERROR
207
- )
208
- PrettyOutput.print("\n参数说明:", OutputType.INFO)
209
+ # 先拼接提示与参数说明,再统一打印,避免循环中逐条打印
209
210
  params = tool_obj.parameters.get("properties", {})
211
+ lines = [
212
+ f"错误: 缺少必需参数: {', '.join(missing_params)}",
213
+ "",
214
+ "参数说明:",
215
+ ]
210
216
  for param_name in required_params:
211
217
  param_info = params.get(param_name, {})
212
218
  desc = param_info.get("description", "无描述")
213
- PrettyOutput.print(f" - {param_name}: {desc}", OutputType.INFO)
219
+ lines.append(f" - {param_name}: {desc}")
220
+ PrettyOutput.print("\n".join(lines), OutputType.ERROR)
214
221
  raise typer.Exit(code=1)
215
222
 
216
223
  result = registry.execute_tool(tool_name, tool_args)
@@ -122,8 +122,8 @@ class FileSearchReplaceTool:
122
122
 
123
123
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
124
124
 
125
- stdout_messages = []
126
- stderr_messages = []
125
+ stdout_messages: list[str] = []
126
+ stderr_messages: list[str] = []
127
127
  overall_success = False
128
128
  file_results = []
129
129
 
@@ -168,10 +168,10 @@ class FileSearchReplaceTool:
168
168
  )
169
169
 
170
170
  # 整合所有错误信息到stderr
171
- all_stderr = []
172
- for result in file_results:
173
- if not result["success"]:
174
- all_stderr.append(f"文件 {result['file']} 处理失败: {result['stderr']}")
171
+ all_stderr: list[str] = []
172
+ for file_result in file_results:
173
+ if not file_result["success"]:
174
+ all_stderr.append(f"文件 {file_result['file']} 处理失败: {file_result['stderr']}")
175
175
 
176
176
  return {
177
177
  "success": overall_success,
@@ -74,8 +74,7 @@ class ScriptTool:
74
74
  stream.feed(data)
75
75
 
76
76
  # 清理每行右侧空格,并过滤空行
77
- cleaned = []
78
- cleaned = []
77
+ cleaned: list[str] = []
79
78
  for y in range(screen.lines):
80
79
  line = screen.buffer[y]
81
80
  stripped = "".join(char.data for char in line.values()).rstrip()
@@ -30,7 +30,7 @@ class FileAnalyzerTool:
30
30
 
31
31
  @staticmethod
32
32
  def check() -> bool:
33
- return PlatformRegistry().get_thinking_platform().support_upload_files()
33
+ return PlatformRegistry().get_normal_platform().support_upload_files()
34
34
 
35
35
  def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
36
36
  """执行文件分析操作
@@ -45,25 +45,31 @@ class FileAnalyzerTool:
45
45
  file_paths = args["file_paths"]
46
46
  prompt = args["prompt"]
47
47
 
48
- # 验证文件路径
48
+ # 验证文件路径(先收集不存在的文件,统一打印一次)
49
49
  valid_files = []
50
+ missing_files = []
50
51
  for file_path in file_paths:
51
52
  if os.path.exists(file_path):
52
53
  valid_files.append(file_path)
53
54
  else:
54
- PrettyOutput.print(f"文件不存在: {file_path}", OutputType.WARNING)
55
+ missing_files.append(file_path)
56
+ if missing_files:
57
+ PrettyOutput.print(
58
+ "以下文件不存在:\n" + "\n".join(f" - {p}" for p in missing_files),
59
+ OutputType.WARNING,
60
+ )
55
61
 
56
62
  if not valid_files:
57
63
  return {"success": False, "stdout": "", "stderr": "没有找到有效的文件"}
58
64
 
59
- # 创建thinking平台实例
60
- platform = PlatformRegistry().get_thinking_platform()
65
+ # 创建平台实例
66
+ platform = PlatformRegistry().get_normal_platform()
61
67
 
62
68
  if not platform:
63
69
  return {
64
70
  "success": False,
65
71
  "stdout": "",
66
- "stderr": "无法创建thinking平台实例",
72
+ "stderr": "无法创建平台实例",
67
73
  }
68
74
 
69
75
  # 设置系统消息
@@ -73,7 +73,7 @@ arguments:
73
73
 
74
74
  <string_format>
75
75
  # 📝 字符串参数格式
76
- 始终使用 |2 语法表示字符串参数,防止多行字符串行首空格引起歧义:
76
+ 使用 |2 语法表示字符串参数,防止多行字符串行首空格引起歧义。
77
77
 
78
78
  {ot("TOOL_CALL")}
79
79
  want: 当前的git状态,期望获取xxx的提交记录
@@ -81,7 +81,7 @@ name: execute_script
81
81
 
82
82
  arguments:
83
83
  interpreter: bash
84
- script_content: |2
84
+ script_content: |
85
85
  git status --porcelain
86
86
  {ct("TOOL_CALL")}
87
87
  </string_format>
@@ -98,7 +98,6 @@ arguments:
98
98
  <common_errors>
99
99
  # ⚠️ 常见错误
100
100
  - 同时调用多个工具
101
- - 字符串参数缺少 |2
102
101
  - 假设工具结果
103
102
  - 创建虚构对话
104
103
  - 在没有所需信息的情况下继续
@@ -276,14 +275,17 @@ class ToolRegistry(OutputHandlerProtocol):
276
275
  # 如果配置了 use 列表,只保留列表中的工具
277
276
  if use_list:
278
277
  filtered_tools = {}
278
+ missing = []
279
279
  for tool_name in use_list:
280
280
  if tool_name in self.tools:
281
281
  filtered_tools[tool_name] = self.tools[tool_name]
282
282
  else:
283
- PrettyOutput.print(
284
- f"警告: 配置的工具 '{tool_name}' 不存在",
285
- OutputType.WARNING,
286
- )
283
+ missing.append(tool_name)
284
+ if missing:
285
+ PrettyOutput.print(
286
+ "警告: 配置的工具不存在: " + ", ".join(f"'{name}'" for name in missing),
287
+ OutputType.WARNING,
288
+ )
287
289
  self.tools = filtered_tools
288
290
 
289
291
  # 如果配置了 dont_use 列表,排除列表中的工具
@@ -315,14 +317,15 @@ class ToolRegistry(OutputHandlerProtocol):
315
317
  )
316
318
 
317
319
  # 遍历目录中的所有.yaml文件
320
+ error_lines = []
318
321
  for file_path in mcp_tools_dir.glob("*.yaml"):
319
322
  try:
320
323
  config = yaml.safe_load(open(file_path, "r", encoding="utf-8"))
321
324
  self.register_mcp_tool_by_config(config)
322
325
  except Exception as e:
323
- PrettyOutput.print(
324
- f"文件 {file_path} 加载失败: {str(e)}", OutputType.WARNING
325
- )
326
+ error_lines.append(f"文件 {file_path} 加载失败: {str(e)}")
327
+ if error_lines:
328
+ PrettyOutput.print("\n".join(error_lines), OutputType.WARNING)
326
329
 
327
330
  def _load_builtin_tools(self) -> None:
328
331
  """从内置工具目录加载工具"""
@@ -64,12 +64,6 @@ class SubAgentTool:
64
64
  f"背景信息:\n{background}\n\n任务:\n{task}" if background else task
65
65
  )
66
66
 
67
- # 读取背景信息并组合任务
68
- background: str = str(args.get("background", "")).strip()
69
- enhanced_task = (
70
- f"背景信息:\n{background}\n\n任务:\n{task}" if background else task
71
- )
72
-
73
67
  # 继承父Agent的运行参数(用于覆盖默认值);若无父Agent则使用默认/全局配置
74
68
  parent_agent = args.get("agent")
75
69
  # 如未注入父Agent,尝试从全局获取当前或任一已注册Agent
@@ -133,7 +127,7 @@ class SubAgentTool:
133
127
  system_prompt=system_prompt,
134
128
  name="SubAgent",
135
129
  description="Temporary sub agent for executing a subtask",
136
- llm_type="normal", # 使用默认模型类型
130
+
137
131
  model_group=model_group, # 继承父Agent模型组(如可用)
138
132
  summary_prompt=summary_prompt, # 继承父Agent总结提示词(如可用)
139
133
  auto_complete=auto_complete,
@@ -160,7 +154,10 @@ class SubAgentTool:
160
154
  try:
161
155
  model_name = parent_agent.model.name() # type: ignore[attr-defined]
162
156
  if model_name:
163
- agent.model.set_model_name(model_name) # type: ignore[attr-defined]
157
+ from typing import Any
158
+ model_obj: Any = getattr(agent, "model", None)
159
+ if model_obj is not None:
160
+ model_obj.set_model_name(model_name)
164
161
  except Exception:
165
162
  pass
166
163
  if use_tools:
@@ -103,12 +103,10 @@ class SubCodeAgentTool:
103
103
  pass
104
104
 
105
105
  # 创建 CodeAgent:参数优先使用父Agent的配置(若可获取),否则使用默认
106
- # 推断/继承 llm_type、need_summary、tool_group
107
- llm_type = "normal"
106
+ # 推断/继承 tool_group
108
107
  tool_group = None
109
108
  try:
110
109
  if parent_agent is not None:
111
- llm_type = getattr(parent_agent, "llm_type", llm_type)
112
110
  tool_group = getattr(parent_agent, "tool_group", tool_group)
113
111
  except Exception:
114
112
  pass
@@ -135,7 +133,6 @@ class SubCodeAgentTool:
135
133
 
136
134
  try:
137
135
  code_agent = CodeAgent(
138
- llm_type=llm_type,
139
136
  model_group=model_group,
140
137
  need_summary=True,
141
138
  append_tools=append_tools,
@@ -164,7 +161,10 @@ class SubCodeAgentTool:
164
161
  try:
165
162
  parent_model_name = parent_agent.model.name() # type: ignore[attr-defined]
166
163
  if parent_model_name:
167
- code_agent.agent.model.set_model_name(parent_model_name) # type: ignore[attr-defined]
164
+ from typing import Any
165
+ model_obj: Any = getattr(code_agent.agent, "model", None)
166
+ if model_obj is not None:
167
+ model_obj.set_model_name(parent_model_name)
168
168
  except Exception:
169
169
  pass
170
170
  except Exception:
@@ -115,6 +115,14 @@ def get_shell_name() -> str:
115
115
  return os.path.basename(shell_path).lower()
116
116
 
117
117
 
118
+ def _apply_llm_group_env_override(group_config: Dict[str, Any]) -> None:
119
+ """如果模型组配置中包含ENV,则应用环境变量覆盖"""
120
+ if "ENV" in group_config and isinstance(group_config["ENV"], dict):
121
+ os.environ.update(
122
+ {str(k): str(v) for k, v in group_config["ENV"].items() if v is not None}
123
+ )
124
+
125
+
118
126
  def _get_resolved_model_config(
119
127
  model_group_override: Optional[str] = None,
120
128
  ) -> Dict[str, Any]:
@@ -141,6 +149,8 @@ def _get_resolved_model_config(
141
149
  if isinstance(group_item, dict) and model_group_name in group_item:
142
150
  group_config = group_item[model_group_name]
143
151
  break
152
+
153
+ _apply_llm_group_env_override(group_config)
144
154
 
145
155
  # Start with group config
146
156
  resolved_config = group_config.copy()
@@ -149,8 +159,6 @@ def _get_resolved_model_config(
149
159
  for key in [
150
160
  "JARVIS_PLATFORM",
151
161
  "JARVIS_MODEL",
152
- "JARVIS_THINKING_PLATFORM",
153
- "JARVIS_THINKING_MODEL",
154
162
  "JARVIS_MAX_INPUT_TOKEN_COUNT",
155
163
  ]:
156
164
  if key in GLOBAL_CONFIG_DATA:
@@ -181,7 +189,7 @@ def get_normal_model_name(model_group_override: Optional[str] = None) -> str:
181
189
  return config.get("JARVIS_MODEL", "deep_seek_v3")
182
190
 
183
191
 
184
- def get_thinking_platform_name(model_group_override: Optional[str] = None) -> str:
192
+ def _deprecated_platform_name_v1(model_group_override: Optional[str] = None) -> str:
185
193
  """
186
194
  获取思考操作的平台名称。
187
195
 
@@ -190,12 +198,10 @@ def get_thinking_platform_name(model_group_override: Optional[str] = None) -> st
190
198
  """
191
199
  config = _get_resolved_model_config(model_group_override)
192
200
  # Fallback to normal platform if thinking platform is not specified
193
- return config.get(
194
- "JARVIS_THINKING_PLATFORM", get_normal_platform_name(model_group_override)
195
- )
201
+ return get_normal_platform_name(model_group_override)
196
202
 
197
203
 
198
- def get_thinking_model_name(model_group_override: Optional[str] = None) -> str:
204
+ def _deprecated_model_name_v1(model_group_override: Optional[str] = None) -> str:
199
205
  """
200
206
  获取思考操作的模型名称。
201
207
 
@@ -204,9 +210,7 @@ def get_thinking_model_name(model_group_override: Optional[str] = None) -> str:
204
210
  """
205
211
  config = _get_resolved_model_config(model_group_override)
206
212
  # Fallback to normal model if thinking model is not specified
207
- return config.get(
208
- "JARVIS_THINKING_MODEL", get_normal_model_name(model_group_override)
209
- )
213
+ return get_normal_model_name(model_group_override)
210
214
 
211
215
 
212
216
  def is_execute_tool_confirm() -> bool:
@@ -638,6 +642,16 @@ def get_tool_dont_use_list() -> List[str]:
638
642
  return config.get("dont_use", [])
639
643
 
640
644
 
645
+ def get_tool_filter_threshold() -> int:
646
+ """
647
+ 获取AI工具筛选的阈值。
648
+
649
+ 返回:
650
+ int: 当工具数量超过此阈值时,触发AI筛选。默认为30
651
+ """
652
+ return int(GLOBAL_CONFIG_DATA.get("JARVIS_TOOL_FILTER_THRESHOLD", 30))
653
+
654
+
641
655
  def is_enable_git_repo_jca_switch() -> bool:
642
656
  """
643
657
  是否启用:在初始化环境前检测Git仓库并提示可切换到代码开发模式(jca)
@@ -384,14 +384,17 @@ def _show_history_and_copy():
384
384
  PrettyOutput.print("没有可复制的消息", OutputType.INFO)
385
385
  return
386
386
 
387
- PrettyOutput.print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20, OutputType.INFO)
387
+ # 为避免 PrettyOutput 在循环中为每行加框,先拼接后统一打印
388
+ lines = []
389
+ lines.append("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20)
388
390
  for i, msg in enumerate(history):
389
391
  cleaned_msg = msg.replace("\n", r"\n")
390
392
  display_msg = (
391
393
  (cleaned_msg[:70] + "...") if len(cleaned_msg) > 70 else cleaned_msg
392
394
  )
393
- PrettyOutput.print(f" {i + 1}: {display_msg.strip()}", OutputType.INFO)
394
- PrettyOutput.print("=" * 58 + "\n", OutputType.INFO)
395
+ lines.append(f" {i + 1}: {display_msg.strip()}")
396
+ lines.append("=" * 58 + "\n")
397
+ PrettyOutput.print("\n".join(lines), OutputType.INFO)
395
398
 
396
399
  while True:
397
400
  try:
@@ -699,7 +702,7 @@ def get_multiline_input(tip: str, print_on_empty: bool = True) -> str:
699
702
  "未检测到 fzf,无法打开文件选择器。", OutputType.WARNING
700
703
  )
701
704
  else:
702
- files: list[str] = []
705
+ files = []
703
706
  try:
704
707
  r = subprocess.run(
705
708
  ["git", "ls-files"],
@@ -832,7 +835,7 @@ def get_multiline_input(tip: str, print_on_empty: bool = True) -> str:
832
835
  "未检测到 fzf,无法打开文件选择器。", OutputType.WARNING
833
836
  )
834
837
  else:
835
- files: list[str] = []
838
+ files = []
836
839
  try:
837
840
  import os as _os
838
841
 
@@ -81,11 +81,13 @@ def _load_all_methodologies() -> Dict[str, str]:
81
81
 
82
82
  import glob
83
83
 
84
+ # 收集循环中的提示,统一打印,避免逐条加框
85
+ warn_dirs: List[str] = []
86
+ error_lines: List[str] = []
87
+
84
88
  for directory in set(methodology_dirs): # Use set to avoid duplicates
85
89
  if not os.path.isdir(directory):
86
- PrettyOutput.print(
87
- f"警告: 方法论目录不存在或不是一个目录: {directory}", OutputType.WARNING
88
- )
90
+ warn_dirs.append(f"警告: 方法论目录不存在或不是一个目录: {directory}")
89
91
  continue
90
92
 
91
93
  for filepath in glob.glob(os.path.join(directory, "*.json")):
@@ -100,10 +102,13 @@ def _load_all_methodologies() -> Dict[str, str]:
100
102
  all_methodologies[problem_type] = content
101
103
  except Exception as e:
102
104
  filename = os.path.basename(filepath)
103
- PrettyOutput.print(
104
- f"加载方法论文件 {filename} 失败: {str(e)}", OutputType.WARNING
105
- )
105
+ error_lines.append(f"加载方法论文件 {filename} 失败: {str(e)}")
106
106
 
107
+ # 统一打印目录警告与文件加载失败信息
108
+ if warn_dirs:
109
+ PrettyOutput.print("\n".join(warn_dirs), OutputType.WARNING)
110
+ if error_lines:
111
+ PrettyOutput.print("\n".join(error_lines), OutputType.WARNING)
107
112
  return all_methodologies
108
113
 
109
114
 
@@ -673,13 +673,20 @@ def _interactive_config_setup(config_file_path: Path):
673
673
 
674
674
  # 如果有配置指导,先显示总体说明
675
675
  if config_guide:
676
- PrettyOutput.print(f"\n配置获取方法:", OutputType.INFO)
676
+ # 为避免 PrettyOutput 在循环中为每行加框,先拼接后统一打印
677
+ guide_lines = ["", "配置获取方法:"]
678
+ for key in required_keys:
679
+ if key in config_guide and config_guide[key]:
680
+ guide_lines.append(f"")
681
+ guide_lines.append(f"{key} 获取方法:")
682
+ guide_lines.append(str(config_guide[key]))
683
+ PrettyOutput.print("\n".join(guide_lines), OutputType.INFO)
684
+ else:
685
+ # 若无指导,仍需遍历以保持后续逻辑一致
686
+ pass
677
687
 
678
688
  for key in required_keys:
679
- # 显示该环境变量的配置指导
680
- if key in config_guide and config_guide[key]:
681
- PrettyOutput.print(f"\n{key} 获取方法:", OutputType.INFO)
682
- PrettyOutput.print(config_guide[key], OutputType.INFO)
689
+ # 显示该环境变量的配置指导(上文已统一打印,此处不再逐条打印)
683
690
 
684
691
  default_value = defaults.get(key, "")
685
692
  prompt_text = f" - {key}"
@@ -737,9 +744,7 @@ def _interactive_config_setup(config_file_path: Path):
737
744
  config_data = {
738
745
  "ENV": env_vars,
739
746
  "JARVIS_PLATFORM": platform_name,
740
- "JARVIS_THINKING_PLATFORM": platform_name,
741
747
  "JARVIS_MODEL": model_name,
742
- "JARVIS_THINKING_MODEL": model_name,
743
748
  }
744
749
 
745
750
  if not test_passed:
@@ -1080,6 +1085,14 @@ def _collect_optional_config_interactively(
1080
1085
  )
1081
1086
  or changed
1082
1087
  )
1088
+ changed = (
1089
+ _ask_and_set_int(
1090
+ "JARVIS_TOOL_FILTER_THRESHOLD",
1091
+ "设置AI工具筛选阈值 (当可用工具数超过此值时触发AI筛选, 默认30)",
1092
+ 30,
1093
+ )
1094
+ or changed
1095
+ )
1083
1096
 
1084
1097
  # 目录类配置(逗号分隔)
1085
1098
  changed = (
@@ -1159,7 +1172,7 @@ def _collect_optional_config_interactively(
1159
1172
  try:
1160
1173
  if "JARVIS_RAG" not in config_data:
1161
1174
  if get_yes_no("是否配置 RAG 检索增强参数?", default=False):
1162
- rag_conf = {}
1175
+ rag_conf: Dict[str, Any] = {}
1163
1176
  emb = get_single_line_input(
1164
1177
  f"RAG 嵌入模型(留空使用默认: {rag_default_embed}):",
1165
1178
  default="",
@@ -1454,7 +1467,7 @@ def _read_old_config_file(config_file):
1454
1467
 
1455
1468
 
1456
1469
  def while_success(func: Callable[[], Any], sleep_time: float = 0.1) -> Any:
1457
- """循环执行函数直到成功
1470
+ """循环执行函数直到成功(累计日志后统一打印,避免逐次加框)
1458
1471
 
1459
1472
  参数:
1460
1473
  func -- 要执行的函数
@@ -1463,17 +1476,20 @@ def while_success(func: Callable[[], Any], sleep_time: float = 0.1) -> Any:
1463
1476
  返回:
1464
1477
  函数执行结果
1465
1478
  """
1479
+ result: Any = None
1466
1480
  while True:
1467
1481
  try:
1468
- return func()
1469
- except Exception as e:
1482
+ result = func()
1483
+ break
1484
+ except Exception:
1470
1485
  PrettyOutput.print(f"重试中,等待 {sleep_time}s...", OutputType.WARNING)
1471
1486
  time.sleep(sleep_time)
1472
1487
  continue
1488
+ return result
1473
1489
 
1474
1490
 
1475
1491
  def while_true(func: Callable[[], bool], sleep_time: float = 0.1) -> Any:
1476
- """循环执行函数直到返回True
1492
+ """循环执行函数直到返回True(累计日志后统一打印,避免逐次加框)
1477
1493
 
1478
1494
  参数:
1479
1495
  func: 要执行的函数,必须返回布尔值
@@ -1486,6 +1502,7 @@ def while_true(func: Callable[[], bool], sleep_time: float = 0.1) -> Any:
1486
1502
  与while_success不同,此函数只检查返回是否为True,
1487
1503
  不捕获异常,异常会直接抛出
1488
1504
  """
1505
+ ret: bool = False
1489
1506
  while True:
1490
1507
  ret = func()
1491
1508
  if ret: