jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__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 (181) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +458 -152
  3. jarvis/jarvis_agent/agent_manager.py +17 -13
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +329 -0
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +628 -55
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +34 -10
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +105 -9
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +20 -22
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  31. jarvis/jarvis_agent/task_analyzer.py +31 -6
  32. jarvis/jarvis_agent/task_manager.py +11 -27
  33. jarvis/jarvis_agent/tool_executor.py +2 -3
  34. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  35. jarvis/jarvis_agent/utils.py +5 -1
  36. jarvis/jarvis_agent/web_bridge.py +189 -0
  37. jarvis/jarvis_agent/web_output_sink.py +53 -0
  38. jarvis/jarvis_agent/web_server.py +786 -0
  39. jarvis/jarvis_c2rust/__init__.py +26 -0
  40. jarvis/jarvis_c2rust/cli.py +575 -0
  41. jarvis/jarvis_c2rust/collector.py +250 -0
  42. jarvis/jarvis_c2rust/constants.py +26 -0
  43. jarvis/jarvis_c2rust/library_replacer.py +1254 -0
  44. jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
  45. jarvis/jarvis_c2rust/loaders.py +207 -0
  46. jarvis/jarvis_c2rust/models.py +28 -0
  47. jarvis/jarvis_c2rust/optimizer.py +2157 -0
  48. jarvis/jarvis_c2rust/scanner.py +1681 -0
  49. jarvis/jarvis_c2rust/transpiler.py +2983 -0
  50. jarvis/jarvis_c2rust/utils.py +385 -0
  51. jarvis/jarvis_code_agent/build_validation_config.py +132 -0
  52. jarvis/jarvis_code_agent/code_agent.py +1371 -220
  53. jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
  54. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
  60. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
  61. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
  62. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
  63. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
  64. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
  65. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
  66. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
  67. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
  68. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  69. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
  70. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  71. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  72. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  73. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  74. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  75. jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
  76. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
  77. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
  78. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
  79. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  80. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  81. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
  82. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
  83. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  84. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
  85. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  86. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
  87. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
  88. jarvis/jarvis_code_agent/lint.py +501 -8
  89. jarvis/jarvis_code_agent/utils.py +141 -0
  90. jarvis/jarvis_code_analysis/code_review.py +493 -584
  91. jarvis/jarvis_data/config_schema.json +128 -12
  92. jarvis/jarvis_git_squash/main.py +4 -5
  93. jarvis/jarvis_git_utils/git_commiter.py +82 -75
  94. jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
  95. jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
  96. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  97. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  98. jarvis/jarvis_methodology/main.py +32 -48
  99. jarvis/jarvis_multi_agent/__init__.py +287 -55
  100. jarvis/jarvis_multi_agent/main.py +36 -4
  101. jarvis/jarvis_platform/base.py +524 -202
  102. jarvis/jarvis_platform/human.py +7 -8
  103. jarvis/jarvis_platform/kimi.py +30 -36
  104. jarvis/jarvis_platform/openai.py +88 -25
  105. jarvis/jarvis_platform/registry.py +26 -10
  106. jarvis/jarvis_platform/tongyi.py +24 -25
  107. jarvis/jarvis_platform/yuanbao.py +32 -43
  108. jarvis/jarvis_platform_manager/main.py +66 -77
  109. jarvis/jarvis_platform_manager/service.py +8 -13
  110. jarvis/jarvis_rag/cli.py +53 -55
  111. jarvis/jarvis_rag/embedding_manager.py +13 -18
  112. jarvis/jarvis_rag/llm_interface.py +8 -9
  113. jarvis/jarvis_rag/query_rewriter.py +10 -21
  114. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  115. jarvis/jarvis_rag/reranker.py +4 -5
  116. jarvis/jarvis_rag/retriever.py +28 -30
  117. jarvis/jarvis_sec/__init__.py +305 -0
  118. jarvis/jarvis_sec/agents.py +143 -0
  119. jarvis/jarvis_sec/analysis.py +276 -0
  120. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  121. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  122. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  123. jarvis/jarvis_sec/cli.py +139 -0
  124. jarvis/jarvis_sec/clustering.py +1439 -0
  125. jarvis/jarvis_sec/file_manager.py +427 -0
  126. jarvis/jarvis_sec/parsers.py +73 -0
  127. jarvis/jarvis_sec/prompts.py +268 -0
  128. jarvis/jarvis_sec/report.py +336 -0
  129. jarvis/jarvis_sec/review.py +453 -0
  130. jarvis/jarvis_sec/status.py +264 -0
  131. jarvis/jarvis_sec/types.py +20 -0
  132. jarvis/jarvis_sec/utils.py +499 -0
  133. jarvis/jarvis_sec/verification.py +848 -0
  134. jarvis/jarvis_sec/workflow.py +226 -0
  135. jarvis/jarvis_smart_shell/main.py +38 -87
  136. jarvis/jarvis_stats/cli.py +2 -2
  137. jarvis/jarvis_stats/stats.py +8 -8
  138. jarvis/jarvis_stats/storage.py +15 -21
  139. jarvis/jarvis_stats/visualizer.py +1 -1
  140. jarvis/jarvis_tools/clear_memory.py +3 -20
  141. jarvis/jarvis_tools/cli/main.py +21 -23
  142. jarvis/jarvis_tools/edit_file.py +1019 -132
  143. jarvis/jarvis_tools/execute_script.py +83 -25
  144. jarvis/jarvis_tools/file_analyzer.py +6 -9
  145. jarvis/jarvis_tools/generate_new_tool.py +14 -21
  146. jarvis/jarvis_tools/lsp_client.py +1552 -0
  147. jarvis/jarvis_tools/methodology.py +2 -3
  148. jarvis/jarvis_tools/read_code.py +1736 -35
  149. jarvis/jarvis_tools/read_symbols.py +140 -0
  150. jarvis/jarvis_tools/read_webpage.py +12 -13
  151. jarvis/jarvis_tools/registry.py +427 -200
  152. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  153. jarvis/jarvis_tools/rewrite_file.py +72 -158
  154. jarvis/jarvis_tools/save_memory.py +3 -15
  155. jarvis/jarvis_tools/search_web.py +18 -18
  156. jarvis/jarvis_tools/sub_agent.py +36 -43
  157. jarvis/jarvis_tools/sub_code_agent.py +25 -26
  158. jarvis/jarvis_tools/virtual_tty.py +55 -33
  159. jarvis/jarvis_utils/clipboard.py +7 -10
  160. jarvis/jarvis_utils/config.py +232 -45
  161. jarvis/jarvis_utils/embedding.py +8 -5
  162. jarvis/jarvis_utils/fzf.py +8 -8
  163. jarvis/jarvis_utils/git_utils.py +225 -36
  164. jarvis/jarvis_utils/globals.py +3 -3
  165. jarvis/jarvis_utils/http.py +1 -1
  166. jarvis/jarvis_utils/input.py +99 -48
  167. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  168. jarvis/jarvis_utils/methodology.py +52 -48
  169. jarvis/jarvis_utils/utils.py +819 -491
  170. jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
  171. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  172. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
  173. jarvis/jarvis_agent/config.py +0 -92
  174. jarvis/jarvis_agent/edit_file_handler.py +0 -296
  175. jarvis/jarvis_platform/ai8.py +0 -332
  176. jarvis/jarvis_tools/ask_user.py +0 -54
  177. jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
  178. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  179. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  180. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  181. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,8 @@ import re
7
7
  import sys
8
8
  from pathlib import Path
9
9
  from enum import Enum
10
- from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, Union
10
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
11
+
11
12
 
12
13
  # 第三方库导入
13
14
  from rich.align import Align
@@ -28,12 +29,11 @@ from jarvis.jarvis_agent.file_methodology_manager import FileMethodologyManager
28
29
  from jarvis.jarvis_agent.prompts import (
29
30
  DEFAULT_SUMMARY_PROMPT,
30
31
  SUMMARY_REQUEST_PROMPT,
31
- TASK_ANALYSIS_PROMPT,
32
+ TASK_ANALYSIS_PROMPT, # noqa: F401
32
33
  )
33
34
  from jarvis.jarvis_tools.registry import ToolRegistry
34
35
  from jarvis.jarvis_agent.prompt_manager import PromptManager
35
36
  from jarvis.jarvis_agent.event_bus import EventBus
36
- from jarvis.jarvis_agent.config import AgentConfig
37
37
  from jarvis.jarvis_agent.run_loop import AgentRunLoop
38
38
  from jarvis.jarvis_agent.events import (
39
39
  BEFORE_SUMMARY,
@@ -54,6 +54,9 @@ from jarvis.jarvis_agent.events import (
54
54
  from jarvis.jarvis_agent.user_interaction import UserInteractionHandler
55
55
  from jarvis.jarvis_agent.utils import join_prompts
56
56
  from jarvis.jarvis_utils.methodology import _load_all_methodologies
57
+ from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
58
+ from jarvis.jarvis_agent.file_context_handler import file_context_handler
59
+ from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
57
60
 
58
61
  # jarvis_platform 相关
59
62
  from jarvis.jarvis_platform.base import BasePlatform
@@ -62,7 +65,6 @@ from jarvis.jarvis_platform.registry import PlatformRegistry
62
65
  # jarvis_utils 相关
63
66
  from jarvis.jarvis_utils.config import (
64
67
  get_data_dir,
65
- get_max_token_count,
66
68
  get_normal_model_name,
67
69
  get_normal_platform_name,
68
70
  is_execute_tool_confirm,
@@ -71,17 +73,19 @@ from jarvis.jarvis_utils.config import (
71
73
  is_use_methodology,
72
74
  get_tool_filter_threshold,
73
75
  get_after_tool_call_cb_dirs,
76
+ get_addon_prompt_threshold,
77
+ is_enable_memory_organizer,
74
78
  )
75
79
  from jarvis.jarvis_utils.embedding import get_context_token_count
76
80
  from jarvis.jarvis_utils.globals import (
77
81
  delete_agent,
78
82
  get_interrupt,
83
+ get_short_term_memories,
79
84
  make_agent_name,
80
85
  set_agent,
81
86
  set_interrupt,
82
87
  )
83
88
  from jarvis.jarvis_utils.input import get_multiline_input, user_confirm
84
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
85
89
  from jarvis.jarvis_utils.tag import ot
86
90
 
87
91
 
@@ -130,6 +134,10 @@ def show_agent_startup_stats(
130
134
  if project_memory_dir.exists():
131
135
  project_memory_count = len(list(project_memory_dir.glob("*.json")))
132
136
 
137
+ # 检查短期记忆
138
+ short_term_memories = get_short_term_memories()
139
+ short_term_memory_count = len(short_term_memories) if short_term_memories else 0
140
+
133
141
  # 获取当前工作目录
134
142
  current_dir = os.getcwd()
135
143
 
@@ -149,6 +157,12 @@ def show_agent_startup_stats(
149
157
  f"📝 项目记忆: [bold magenta]{project_memory_count}[/bold magenta]"
150
158
  )
151
159
 
160
+ # 如果有短期记忆,添加到统计信息中
161
+ if short_term_memory_count > 0:
162
+ stats_parts.append(
163
+ f"💭 短期记忆: [bold blue]{short_term_memory_count}[/bold blue]"
164
+ )
165
+
152
166
  stats_text = Text.from_markup(" | ".join(stats_parts), justify="center")
153
167
 
154
168
  # 创建包含欢迎信息和统计信息的面板内容
@@ -172,7 +186,7 @@ def show_agent_startup_stats(
172
186
  console.print(Align.center(panel))
173
187
 
174
188
  except Exception as e:
175
- PrettyOutput.print(f"加载统计信息失败: {e}", OutputType.WARNING)
189
+ print(f"⚠️ 加载统计信息失败: {e}")
176
190
 
177
191
 
178
192
  origin_agent_system_prompt = f"""
@@ -243,9 +257,34 @@ class Agent:
243
257
  def clear_history(self):
244
258
  """
245
259
  Clears the current conversation history by delegating to the session manager.
260
+ 直接调用关键流程函数,事件总线仅用于非关键流程(如日志、监控等)。
246
261
  """
262
+ # 关键流程:直接调用 memory_manager 确保记忆提示
263
+ try:
264
+ self.memory_manager._ensure_memory_prompt(agent=self)
265
+ except Exception:
266
+ pass
267
+
268
+ # 非关键流程:广播清理历史前事件(用于日志、监控等)
269
+ try:
270
+ self.event_bus.emit(BEFORE_HISTORY_CLEAR, agent=self)
271
+ except Exception:
272
+ pass
273
+
274
+ # 清理会话历史并重置模型状态
247
275
  self.session.clear_history()
248
- # 广播清理历史后的事件
276
+ # 重置 addon_prompt 跳过轮数计数器
277
+ self._addon_prompt_skip_rounds = 0
278
+ # 重置没有工具调用的计数器
279
+ self._no_tool_call_count = 0
280
+
281
+ # 重置后重新设置系统提示词,确保系统约束仍然生效
282
+ try:
283
+ self._setup_system_prompt()
284
+ except Exception:
285
+ pass
286
+
287
+ # 非关键流程:广播清理历史后的事件(用于日志、监控等)
249
288
  try:
250
289
  self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
251
290
  except Exception:
@@ -264,6 +303,21 @@ class Agent:
264
303
  """获取工具使用提示"""
265
304
  return build_action_prompt(self.output_handler) # type: ignore
266
305
 
306
+ def __new__(cls, *args, **kwargs):
307
+ if kwargs.get("agent_type") == "code":
308
+ try:
309
+ from jarvis.jarvis_code_agent.code_agent import CodeAgent
310
+ except ImportError as e:
311
+ raise RuntimeError(
312
+ "CodeAgent could not be imported. Please ensure jarvis_code_agent is installed correctly."
313
+ ) from e
314
+
315
+ # 移除 agent_type 避免无限循环,并传递所有其他参数
316
+ kwargs.pop("agent_type", None)
317
+ return CodeAgent(**kwargs)
318
+ else:
319
+ return super().__new__(cls)
320
+
267
321
  def __init__(
268
322
  self,
269
323
  system_prompt: str,
@@ -274,7 +328,6 @@ class Agent:
274
328
  auto_complete: bool = False,
275
329
  output_handler: Optional[List[OutputHandlerProtocol]] = None,
276
330
  use_tools: Optional[List[str]] = None,
277
- input_handler: Optional[List[Callable[[str, Any], Tuple[str, bool]]]] = None,
278
331
  execute_tool_confirm: Optional[bool] = None,
279
332
  need_summary: bool = True,
280
333
  multiline_inputer: Optional[Callable[[str], str]] = None,
@@ -283,6 +336,10 @@ class Agent:
283
336
  force_save_memory: Optional[bool] = None,
284
337
  files: Optional[List[str]] = None,
285
338
  confirm_callback: Optional[Callable[[str, bool], bool]] = None,
339
+ non_interactive: Optional[bool] = None,
340
+ in_multi_agent: Optional[bool] = None,
341
+ agent_type: str = "normal",
342
+ **kwargs,
286
343
  ):
287
344
  """初始化Jarvis Agent实例
288
345
 
@@ -293,8 +350,6 @@ class Agent:
293
350
 
294
351
  summary_prompt: 任务总结提示模板
295
352
  auto_complete: 是否自动完成任务
296
- output_handler: 输出处理器列表
297
- input_handler: 输入处理器列表
298
353
  execute_tool_confirm: 执行工具前是否需要确认
299
354
  need_summary: 是否需要生成总结
300
355
  multiline_inputer: 多行输入处理器
@@ -302,21 +357,42 @@ class Agent:
302
357
  use_analysis: 是否使用任务分析
303
358
  force_save_memory: 是否强制保存记忆
304
359
  confirm_callback: 用户确认回调函数,签名为 (tip: str, default: bool) -> bool;默认使用CLI的user_confirm
360
+ non_interactive: 是否以非交互模式运行(优先级最高,覆盖环境变量与配置)
305
361
  """
306
- # 基础属性初始化
307
- self.files = files or []
362
+ # 基础属性初始化(仅根据入参设置原始值;实际生效的默认回退在 _init_config 中统一解析)
363
+ # 标识与描述
308
364
  self.name = make_agent_name(name)
309
365
  self.description = description
310
366
  self.system_prompt = system_prompt
311
- self.need_summary = need_summary
312
- self.auto_complete = auto_complete
367
+ # 行为控制开关(原始入参值)
368
+ self.auto_complete = bool(auto_complete)
369
+ self.need_summary = bool(need_summary)
370
+ self.use_methodology = use_methodology
371
+ self.use_analysis = use_analysis
372
+ self.execute_tool_confirm = execute_tool_confirm
373
+ self.summary_prompt = summary_prompt
374
+ self.force_save_memory = force_save_memory
375
+ # 资源与环境
376
+ self.model_group = model_group
377
+ self.files = files or []
378
+ self.use_tools = use_tools
379
+ self.non_interactive = non_interactive
380
+ # 多智能体运行标志:用于控制非交互模式下的自动完成行为
381
+ self.in_multi_agent = bool(in_multi_agent)
382
+ # 运行时状态
313
383
  self.first = True
314
384
  self.run_input_handlers_next_turn = False
315
385
  self.user_data: Dict[str, Any] = {}
386
+ # 记录连续未添加 addon_prompt 的轮数
387
+ self._addon_prompt_skip_rounds: int = 0
388
+ # 记录连续没有工具调用的次数(用于非交互模式下的工具使用提示)
389
+ self._no_tool_call_count: int = 0
390
+
391
+ self._agent_type = "normal"
316
392
 
317
393
 
318
394
  # 用户确认回调:默认使用 CLI 的 user_confirm,可由外部注入以支持 TUI/GUI
319
- self.user_confirm: Callable[[str, bool], bool] = (
395
+ self.confirm_callback: Callable[[str, bool], bool] = (
320
396
  confirm_callback or user_confirm # type: ignore[assignment]
321
397
  )
322
398
 
@@ -326,25 +402,62 @@ class Agent:
326
402
 
327
403
  # 初始化处理器
328
404
  self._init_handlers(
329
- output_handler or [],
330
- input_handler,
331
405
  multiline_inputer,
406
+ output_handler,
332
407
  use_tools or [],
333
408
  )
334
409
  # 初始化用户交互封装,保持向后兼容
335
- self.user_interaction = UserInteractionHandler(self.multiline_inputer, self.user_confirm)
410
+ self.user_interaction = UserInteractionHandler(self.multiline_inputer, self.confirm_callback)
336
411
  # 将确认函数指向封装后的 confirm,保持既有调用不变
337
- self.user_confirm = self.user_interaction.confirm # type: ignore[assignment]
338
-
339
- # 初始化配置
340
- self._init_config(
341
- use_methodology,
342
- use_analysis,
343
- execute_tool_confirm,
344
- summary_prompt,
345
- model_group,
346
- force_save_memory,
347
- )
412
+ self.confirm_callback = self.user_interaction.confirm # type: ignore[assignment]
413
+ # 非交互模式参数支持:允许通过构造参数显式控制,便于其他Agent调用时设置
414
+ try:
415
+ # 优先使用构造参数,其次回退到环境变量
416
+ self.non_interactive = (
417
+ bool(non_interactive)
418
+ if non_interactive is not None
419
+ else str(os.environ.get("JARVIS_NON_INTERACTIVE", "")).lower() in ("1", "true", "yes")
420
+ )
421
+ # 如果构造参数显式提供,则同步到环境变量与全局配置,供下游组件读取
422
+ if non_interactive is not None:
423
+ os.environ["JARVIS_NON_INTERACTIVE"] = "true" if self.non_interactive else "false"
424
+
425
+ except Exception:
426
+ # 防御式回退
427
+ self.non_interactive = False
428
+
429
+ # 初始化配置(直接解析,不再依赖 _init_config)
430
+ try:
431
+ resolved_use_methodology = bool(use_methodology if use_methodology is not None else is_use_methodology())
432
+ except Exception:
433
+ resolved_use_methodology = bool(use_methodology) if use_methodology is not None else True
434
+
435
+ try:
436
+ resolved_use_analysis = bool(use_analysis if use_analysis is not None else is_use_analysis())
437
+ except Exception:
438
+ resolved_use_analysis = bool(use_analysis) if use_analysis is not None else True
439
+
440
+ try:
441
+ resolved_execute_tool_confirm = bool(execute_tool_confirm if execute_tool_confirm is not None else is_execute_tool_confirm())
442
+ except Exception:
443
+ resolved_execute_tool_confirm = bool(execute_tool_confirm) if execute_tool_confirm is not None else False
444
+
445
+ try:
446
+ resolved_force_save_memory = bool(force_save_memory if force_save_memory is not None else is_force_save_memory())
447
+ except Exception:
448
+ resolved_force_save_memory = bool(force_save_memory) if force_save_memory is not None else False
449
+
450
+ self.use_methodology = resolved_use_methodology
451
+ self.use_analysis = resolved_use_analysis
452
+ self.execute_tool_confirm = resolved_execute_tool_confirm
453
+ self.summary_prompt = (summary_prompt or DEFAULT_SUMMARY_PROMPT)
454
+ self.force_save_memory = resolved_force_save_memory
455
+ # 多智能体模式下,默认不自动完成(即使是非交互),仅在明确传入 auto_complete=True 时开启
456
+ if self.in_multi_agent:
457
+ self.auto_complete = bool(self.auto_complete)
458
+ else:
459
+ # 非交互模式下默认自动完成;否则保持传入的 auto_complete 值
460
+ self.auto_complete = bool(self.auto_complete or (self.non_interactive or False))
348
461
 
349
462
  # 初始化事件总线需先于管理器,以便管理器在构造中安全订阅事件
350
463
  self.event_bus = EventBus()
@@ -354,6 +467,14 @@ class Agent:
354
467
  self.file_methodology_manager = FileMethodologyManager(self)
355
468
  self.prompt_manager = PromptManager(self)
356
469
 
470
+ # 如果配置了强制保存记忆,确保 save_memory 工具可用
471
+ if self.force_save_memory:
472
+ self._ensure_save_memory_tool()
473
+
474
+ # 如果启用了分析,确保 methodology 工具可用
475
+ if self.use_analysis:
476
+ self._ensure_methodology_tool()
477
+
357
478
  # 设置系统提示词
358
479
  self._setup_system_prompt()
359
480
 
@@ -364,6 +485,7 @@ class Agent:
364
485
  self.get_tool_registry(), # type: ignore
365
486
  platform_name=self.model.platform_name(), # type: ignore
366
487
  )
488
+
367
489
  # 动态加载工具调用后回调
368
490
  self._load_after_tool_callbacks()
369
491
 
@@ -374,9 +496,7 @@ class Agent:
374
496
 
375
497
  maybe_model = PlatformRegistry().create_platform(platform_name)
376
498
  if maybe_model is None:
377
- PrettyOutput.print(
378
- f"平台 {platform_name} 不存在,将使用普通模型", OutputType.WARNING
379
- )
499
+ print(f"⚠️ 平台 {platform_name} 不存在,将使用普通模型")
380
500
  maybe_model = PlatformRegistry().get_normal_platform()
381
501
 
382
502
  # 在此处收敛为非可选类型,确保后续赋值满足类型检查
@@ -394,67 +514,26 @@ class Agent:
394
514
 
395
515
  def _init_handlers(
396
516
  self,
397
- output_handler: List[OutputHandlerProtocol],
398
- input_handler: Optional[List[Callable[[str, Any], Tuple[str, bool]]]],
399
517
  multiline_inputer: Optional[Callable[[str], str]],
518
+ output_handler: Optional[List[OutputHandlerProtocol]],
400
519
  use_tools: List[str],
401
520
  ):
402
521
  """初始化各种处理器"""
403
- self.output_handler = output_handler or [ToolRegistry()]
522
+ default_handlers: List[Any] = [ToolRegistry()]
523
+ handlers = output_handler or default_handlers
524
+ self.output_handler = handlers
404
525
  self.set_use_tools(use_tools)
405
- self.input_handler = input_handler or []
526
+ self.input_handler = [
527
+ builtin_input_handler,
528
+ shell_input_handler,
529
+ file_context_handler,
530
+ ]
406
531
  self.multiline_inputer = multiline_inputer or get_multiline_input
407
532
 
408
- def _init_config(
409
- self,
410
- use_methodology: Optional[bool],
411
- use_analysis: Optional[bool],
412
- execute_tool_confirm: Optional[bool],
413
- summary_prompt: Optional[str],
414
- model_group: Optional[str],
415
- force_save_memory: Optional[bool],
416
- ):
417
- """初始化配置选项"""
418
- # 使用集中配置解析,保持与原逻辑一致
419
- cfg = AgentConfig(
420
- system_prompt=self.system_prompt,
421
- name=self.name,
422
- description=self.description,
423
- model_group=model_group,
424
- auto_complete=self.auto_complete,
425
- need_summary=self.need_summary,
426
- summary_prompt=summary_prompt,
427
- execute_tool_confirm=execute_tool_confirm,
428
- use_methodology=use_methodology,
429
- use_analysis=use_analysis,
430
- force_save_memory=force_save_memory,
431
- files=self.files,
432
- max_token_count=None,
433
- ).resolve_defaults()
434
-
435
- # 将解析结果回填到 Agent 实例属性,保持向后兼容
436
- self.use_methodology = bool(cfg.use_methodology)
437
- self.use_analysis = bool(cfg.use_analysis)
438
- self.execute_tool_confirm = bool(cfg.execute_tool_confirm)
439
- self.summary_prompt = cfg.summary_prompt or DEFAULT_SUMMARY_PROMPT
440
- self.max_token_count = int(cfg.max_token_count or get_max_token_count(model_group))
441
- self.force_save_memory = bool(cfg.force_save_memory)
442
-
443
- # 聚合配置到 AgentConfig,作为后续单一事实来源(保持兼容,不改变既有属性使用)
444
- self.config = cfg
445
-
446
533
  def _setup_system_prompt(self):
447
534
  """设置系统提示词"""
448
535
  try:
449
- if hasattr(self, "prompt_manager"):
450
- prompt_text = self.prompt_manager.build_system_prompt()
451
- else:
452
- action_prompt = self.get_tool_usage_prompt()
453
- prompt_text = f"""
454
- {self.system_prompt}
455
-
456
- {action_prompt}
457
- """
536
+ prompt_text = self.prompt_manager.build_system_prompt()
458
537
  self.model.set_system_prompt(prompt_text) # type: ignore
459
538
  except Exception:
460
539
  # 回退到原始行为,确保兼容性
@@ -475,6 +554,19 @@ class Agent:
475
554
  """Gets user data from the session."""
476
555
  return self.session.get_user_data(key)
477
556
 
557
+ def get_remaining_token_count(self) -> int:
558
+ """获取剩余可用的token数量
559
+
560
+ 返回:
561
+ int: 剩余可用的token数量,如果无法获取则返回0
562
+ """
563
+ if not self.model:
564
+ return 0
565
+ try:
566
+ return self.model.get_remaining_token_count()
567
+ except Exception:
568
+ return 0
569
+
478
570
  def set_use_tools(self, use_tools):
479
571
  """设置要使用的工具列表"""
480
572
  for handler in self.output_handler:
@@ -498,8 +590,10 @@ class Agent:
498
590
  otherwise, fall back to calling with a single argument for compatibility.
499
591
  """
500
592
  # 优先通过用户交互封装,便于未来替换 UI
501
- if hasattr(self, "user_interaction"):
593
+ try:
502
594
  return self.user_interaction.multiline_input(tip, print_on_empty)
595
+ except Exception:
596
+ pass
503
597
  try:
504
598
  # Try to pass the keyword for enhanced input handler
505
599
  return self.multiline_inputer(tip, print_on_empty=print_on_empty) # type: ignore
@@ -587,7 +681,7 @@ class Agent:
587
681
  pass
588
682
 
589
683
  except Exception as e:
590
- PrettyOutput.print(f"从 {file_path} 加载回调失败: {e}", OutputType.WARNING)
684
+ print(f"⚠️ 从 {file_path} 加载回调失败: {e}")
591
685
  finally:
592
686
  if added_path:
593
687
  try:
@@ -595,7 +689,7 @@ class Agent:
595
689
  except ValueError:
596
690
  pass
597
691
  except Exception as e:
598
- PrettyOutput.print(f"加载回调目录时发生错误: {e}", OutputType.WARNING)
692
+ print(f"⚠️ 加载回调目录时发生错误: {e}")
599
693
 
600
694
  def save_session(self) -> bool:
601
695
  """Saves the current session state by delegating to the session manager."""
@@ -615,6 +709,58 @@ class Agent:
615
709
  return handler
616
710
  return None
617
711
 
712
+ def _ensure_save_memory_tool(self) -> None:
713
+ """如果配置了强制保存记忆,确保 save_memory 工具在 use_tools 列表中"""
714
+ try:
715
+ tool_registry = self.get_tool_registry()
716
+ if not tool_registry:
717
+ return
718
+
719
+ # 检查 save_memory 工具是否已注册(工具默认都会注册)
720
+ if not tool_registry.get_tool("save_memory"):
721
+ # 如果工具本身不存在,则无法使用,直接返回
722
+ return
723
+
724
+ # 检查 save_memory 是否在 use_tools 列表中
725
+ # 如果 use_tools 为 None,表示使用所有工具,无需添加
726
+ if self.use_tools is None:
727
+ return
728
+
729
+ # 如果 save_memory 不在 use_tools 列表中,则添加
730
+ if "save_memory" not in self.use_tools:
731
+ self.use_tools.append("save_memory")
732
+ # 更新工具注册表的工具列表
733
+ self.set_use_tools(self.use_tools)
734
+ except Exception:
735
+ # 忽略所有错误,不影响主流程
736
+ pass
737
+
738
+ def _ensure_methodology_tool(self) -> None:
739
+ """如果启用了分析,确保 methodology 工具在 use_tools 列表中"""
740
+ try:
741
+ tool_registry = self.get_tool_registry()
742
+ if not tool_registry:
743
+ return
744
+
745
+ # 检查 methodology 工具是否已注册(工具默认都会注册)
746
+ if not tool_registry.get_tool("methodology"):
747
+ # 如果工具本身不存在,则无法使用,直接返回
748
+ return
749
+
750
+ # 检查 methodology 是否在 use_tools 列表中
751
+ # 如果 use_tools 为 None,表示使用所有工具,无需添加
752
+ if self.use_tools is None:
753
+ return
754
+
755
+ # 如果 methodology 不在 use_tools 列表中,则添加
756
+ if "methodology" not in self.use_tools:
757
+ self.use_tools.append("methodology")
758
+ # 更新工具注册表的工具列表
759
+ self.set_use_tools(self.use_tools)
760
+ except Exception:
761
+ # 忽略所有错误,不影响主流程
762
+ pass
763
+
618
764
  def get_event_bus(self) -> EventBus:
619
765
  """获取事件总线实例"""
620
766
  return self.event_bus
@@ -666,7 +812,13 @@ class Agent:
666
812
  return message
667
813
 
668
814
  def _add_addon_prompt(self, message: str, need_complete: bool) -> str:
669
- """添加附加提示到消息"""
815
+ """添加附加提示到消息
816
+
817
+ 规则:
818
+ 1. 如果 session.addon_prompt 存在,优先使用它
819
+ 2. 如果消息长度超过阈值,添加默认 addon_prompt
820
+ 3. 如果连续10轮都没有添加过 addon_prompt,强制添加一次
821
+ """
670
822
  # 广播添加附加提示前事件(不影响主流程)
671
823
  try:
672
824
  self.event_bus.emit(
@@ -680,13 +832,32 @@ class Agent:
680
832
  pass
681
833
 
682
834
  addon_text = ""
835
+ should_add = False
836
+
683
837
  if self.session.addon_prompt:
838
+ # 优先使用 session 中设置的 addon_prompt
684
839
  addon_text = self.session.addon_prompt
685
840
  message = join_prompts([message, addon_text])
686
841
  self.session.addon_prompt = ""
842
+ should_add = True
687
843
  else:
688
- addon_text = self.make_default_addon_prompt(need_complete)
689
- message = join_prompts([message, addon_text])
844
+ threshold = get_addon_prompt_threshold()
845
+ # 条件1:消息长度超过阈值
846
+ if len(message) > threshold:
847
+ addon_text = self.make_default_addon_prompt(need_complete)
848
+ message = join_prompts([message, addon_text])
849
+ should_add = True
850
+ # 条件2:连续10轮都没有添加过 addon_prompt,强制添加一次
851
+ elif self._addon_prompt_skip_rounds >= 10:
852
+ addon_text = self.make_default_addon_prompt(need_complete)
853
+ message = join_prompts([message, addon_text])
854
+ should_add = True
855
+
856
+ # 更新计数器:如果添加了 addon_prompt,重置计数器;否则递增
857
+ if should_add:
858
+ self._addon_prompt_skip_rounds = 0
859
+ else:
860
+ self._addon_prompt_skip_rounds += 1
690
861
 
691
862
  # 广播添加附加提示后事件(不影响主流程)
692
863
  try:
@@ -702,14 +873,9 @@ class Agent:
702
873
  return message
703
874
 
704
875
  def _manage_conversation_length(self, message: str) -> str:
705
- """管理对话长度,必要时进行摘要"""
876
+ """管理对话长度计数;摘要触发由剩余token数量在 AgentRunLoop 中统一处理(剩余token低于20%时触发)。"""
706
877
  self.session.conversation_length += get_context_token_count(message)
707
878
 
708
- if self.session.conversation_length > self.max_token_count:
709
- summary = self._summarize_and_clear_history()
710
- if summary:
711
- message = join_prompts([summary, message])
712
- self.session.conversation_length = get_context_token_count(message)
713
879
 
714
880
  return message
715
881
 
@@ -729,7 +895,14 @@ class Agent:
729
895
  pass
730
896
 
731
897
  response = self.model.chat_until_success(message) # type: ignore
732
-
898
+ # 防御: 模型可能返回空响应(None或空字符串),统一为空字符串并告警
899
+ if not response:
900
+ try:
901
+ print("⚠️ 模型返回空响应,已使用空字符串回退。")
902
+ except Exception:
903
+ pass
904
+ response = ""
905
+
733
906
  # 事件:模型调用后
734
907
  try:
735
908
  self.event_bus.emit(
@@ -745,9 +918,13 @@ class Agent:
745
918
 
746
919
  return response
747
920
 
748
- def generate_summary(self) -> str:
921
+ def generate_summary(self, for_token_limit: bool = False) -> str:
749
922
  """生成对话历史摘要
750
923
 
924
+ 参数:
925
+ for_token_limit: 如果为True,表示由于token限制触发的summary,使用SUMMARY_REQUEST_PROMPT
926
+ 如果为False,表示任务完成时的summary,使用用户传入的summary_prompt
927
+
751
928
  返回:
752
929
  str: 包含对话摘要的字符串
753
930
 
@@ -758,13 +935,31 @@ class Agent:
758
935
  try:
759
936
  if not self.model:
760
937
  raise RuntimeError("Model not initialized")
761
- summary = self.model.chat_until_success(
762
- self.session.prompt + "\n" + SUMMARY_REQUEST_PROMPT
763
- ) # type: ignore
764
-
938
+
939
+ print("🔍 开始生成对话历史摘要...")
940
+
941
+ if for_token_limit:
942
+ # token限制触发的summary:使用SUMMARY_REQUEST_PROMPT进行上下文压缩
943
+ prompt_to_use = self.session.prompt + "\n" + SUMMARY_REQUEST_PROMPT
944
+ else:
945
+ # 任务完成时的summary:使用用户传入的summary_prompt或DEFAULT_SUMMARY_PROMPT
946
+ safe_summary_prompt = self.summary_prompt or ""
947
+ if isinstance(safe_summary_prompt, str) and safe_summary_prompt.strip() != "":
948
+ prompt_to_use = safe_summary_prompt
949
+ else:
950
+ prompt_to_use = DEFAULT_SUMMARY_PROMPT
951
+
952
+ summary = self.model.chat_until_success(prompt_to_use) # type: ignore
953
+ # 防御: 可能返回空响应(None或空字符串),统一为空字符串并告警
954
+ if not summary:
955
+ try:
956
+ print("⚠️ 总结模型返回空响应,已使用空字符串回退。")
957
+ except Exception:
958
+ pass
959
+ summary = ""
765
960
  return summary
766
961
  except Exception:
767
- PrettyOutput.print("总结对话历史失败", OutputType.ERROR)
962
+ print("总结对话历史失败")
768
963
  return ""
769
964
 
770
965
  def _summarize_and_clear_history(self) -> str:
@@ -784,11 +979,6 @@ class Agent:
784
979
  注意:
785
980
  当上下文长度超过最大值时使用
786
981
  """
787
- # 在清理历史之前,提示用户保存重要记忆(事件驱动触发实际保存)
788
- if self.force_save_memory:
789
- PrettyOutput.print(
790
- "对话历史即将被总结和清理,请先保存重要信息...", OutputType.INFO
791
- )
792
982
 
793
983
  if self._should_use_file_upload():
794
984
  return self._handle_history_with_file_upload()
@@ -801,26 +991,39 @@ class Agent:
801
991
 
802
992
  def _handle_history_with_summary(self) -> str:
803
993
  """使用摘要方式处理历史"""
804
- summary = self.generate_summary()
994
+ # token限制触发的summary,使用SUMMARY_REQUEST_PROMPT
995
+ summary = self.generate_summary(for_token_limit=True)
805
996
 
806
997
  # 先获取格式化的摘要消息
807
998
  formatted_summary = ""
808
999
  if summary:
809
1000
  formatted_summary = self._format_summary_message(summary)
810
1001
 
811
- # 清理历史(但不清理prompt,因为prompt会在builtin_input_handler中设置)
812
- if self.model:
813
- # 广播清理历史前事件
1002
+ # 关键流程:直接调用 memory_manager 确保记忆提示
1003
+ try:
1004
+ self.memory_manager._ensure_memory_prompt(agent=self)
1005
+ except Exception:
1006
+ pass
1007
+
1008
+ # 非关键流程:广播清理历史前事件(用于日志、监控等)
814
1009
  try:
815
1010
  self.event_bus.emit(BEFORE_HISTORY_CLEAR, agent=self)
816
1011
  except Exception:
817
1012
  pass
1013
+
1014
+ # 清理历史(但不清理prompt,因为prompt会在builtin_input_handler中设置)
1015
+ if self.model:
818
1016
  self.model.reset()
819
1017
  # 重置后重新设置系统提示词,确保系统约束仍然生效
820
1018
  self._setup_system_prompt()
821
1019
  # 重置会话
822
1020
  self.session.clear_history()
823
- # 广播清理历史后的事件
1021
+ # 重置 addon_prompt 跳过轮数计数器
1022
+ self._addon_prompt_skip_rounds = 0
1023
+ # 重置没有工具调用的计数器
1024
+ self._no_tool_call_count = 0
1025
+
1026
+ # 非关键流程:广播清理历史后的事件(用于日志、监控等)
824
1027
  try:
825
1028
  self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
826
1029
  except Exception:
@@ -830,13 +1033,25 @@ class Agent:
830
1033
 
831
1034
  def _handle_history_with_file_upload(self) -> str:
832
1035
  """使用文件上传方式处理历史"""
833
- # 广播清理历史前事件
1036
+ # 关键流程:直接调用 memory_manager 确保记忆提示
1037
+ try:
1038
+ self.memory_manager._ensure_memory_prompt(agent=self)
1039
+ except Exception:
1040
+ pass
1041
+
1042
+ # 非关键流程:广播清理历史前事件(用于日志、监控等)
834
1043
  try:
835
1044
  self.event_bus.emit(BEFORE_HISTORY_CLEAR, agent=self)
836
1045
  except Exception:
837
1046
  pass
1047
+
838
1048
  result = self.file_methodology_manager.handle_history_with_file_upload()
839
- # 广播清理历史后的事件
1049
+ # 重置 addon_prompt 跳过轮数计数器
1050
+ self._addon_prompt_skip_rounds = 0
1051
+ # 重置没有工具调用的计数器
1052
+ self._no_tool_call_count = 0
1053
+
1054
+ # 非关键流程:广播清理历史后的事件(用于日志、监控等)
840
1055
  try:
841
1056
  self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
842
1057
  except Exception:
@@ -876,19 +1091,37 @@ class Agent:
876
1091
  # - TaskAnalyzer 通过订阅 before_summary/task_completed 事件执行分析与满意度收集
877
1092
  # - MemoryManager 通过订阅 before_history_clear/task_completed 事件执行记忆保存(受 force_save_memory 控制)
878
1093
  # 为减少耦合,这里不再直接调用上述组件,保持行为由事件触发
879
- self._check_and_organize_memory()
1094
+ # 仅在启用自动记忆整理时检查并整理记忆
1095
+ if is_enable_memory_organizer():
1096
+ self._check_and_organize_memory()
880
1097
 
881
1098
  result = "任务完成"
882
1099
 
883
1100
  if self.need_summary:
884
1101
 
885
- self.session.prompt = self.summary_prompt
886
- # 广播将要生成总结事件
1102
+ # 确保总结提示词非空:若为None或仅空白,则回退到默认提示词
1103
+ safe_summary_prompt = self.summary_prompt or ""
1104
+ if isinstance(safe_summary_prompt, str) and safe_summary_prompt.strip() == "":
1105
+ safe_summary_prompt = DEFAULT_SUMMARY_PROMPT
1106
+ # 注意:不要写回 session.prompt,避免回调修改/清空后导致使用空prompt
1107
+
1108
+ # 关键流程:直接调用 task_analyzer 执行任务分析
1109
+ try:
1110
+ self.task_analyzer._on_before_summary(
1111
+ agent=self,
1112
+ prompt=safe_summary_prompt,
1113
+ auto_completed=auto_completed,
1114
+ need_summary=self.need_summary,
1115
+ )
1116
+ except Exception:
1117
+ pass
1118
+
1119
+ # 非关键流程:广播将要生成总结事件(用于日志、监控等)
887
1120
  try:
888
1121
  self.event_bus.emit(
889
1122
  BEFORE_SUMMARY,
890
1123
  agent=self,
891
- prompt=self.session.prompt,
1124
+ prompt=safe_summary_prompt,
892
1125
  auto_completed=auto_completed,
893
1126
  need_summary=self.need_summary,
894
1127
  )
@@ -897,10 +1130,18 @@ class Agent:
897
1130
 
898
1131
  if not self.model:
899
1132
  raise RuntimeError("Model not initialized")
900
- ret = self.model.chat_until_success(self.session.prompt) # type: ignore
1133
+ # 直接使用本地变量,避免受事件回调影响
1134
+ ret = self.model.chat_until_success(safe_summary_prompt) # type: ignore
1135
+ # 防御: 总结阶段模型可能返回空响应(None或空字符串),统一为空字符串并告警
1136
+ if not ret:
1137
+ try:
1138
+ print("⚠️ 总结阶段模型返回空响应,已使用空字符串回退。")
1139
+ except Exception:
1140
+ pass
1141
+ ret = ""
901
1142
  result = ret
902
1143
 
903
- # 广播完成总结事件
1144
+ # 非关键流程:广播完成总结事件(用于日志、监控等)
904
1145
  try:
905
1146
  self.event_bus.emit(
906
1147
  AFTER_SUMMARY,
@@ -910,7 +1151,26 @@ class Agent:
910
1151
  except Exception:
911
1152
  pass
912
1153
 
913
- # 广播任务完成事件(不影响主流程)
1154
+ # 关键流程:直接调用 task_analyzer 和 memory_manager
1155
+ try:
1156
+ self.task_analyzer._on_task_completed(
1157
+ agent=self,
1158
+ auto_completed=auto_completed,
1159
+ need_summary=self.need_summary,
1160
+ )
1161
+ except Exception:
1162
+ pass
1163
+
1164
+ try:
1165
+ self.memory_manager._ensure_memory_prompt(
1166
+ agent=self,
1167
+ auto_completed=auto_completed,
1168
+ need_summary=self.need_summary,
1169
+ )
1170
+ except Exception:
1171
+ pass
1172
+
1173
+ # 非关键流程:广播任务完成事件(用于日志、监控等)
914
1174
  try:
915
1175
  self.event_bus.emit(
916
1176
  TASK_COMPLETED,
@@ -931,10 +1191,12 @@ class Agent:
931
1191
 
932
1192
  """
933
1193
  # 优先使用 PromptManager 以保持逻辑集中
934
- if hasattr(self, "prompt_manager"):
1194
+ try:
935
1195
  return self.prompt_manager.build_default_addon_prompt(need_complete)
1196
+ except Exception:
1197
+ pass
936
1198
 
937
- # 结构化系统指令
1199
+ # 结构化系统指令(回退方案)
938
1200
  action_handlers = ", ".join([handler.name() for handler in self.output_handler])
939
1201
 
940
1202
  # 任务完成提示
@@ -957,10 +1219,11 @@ class Agent:
957
1219
  {complete_prompt}
958
1220
  如果没有完成,请进行下一步操作:
959
1221
  - 仅包含一个操作
960
- - 不要询问用户是否继续,直接继续执行直至完成
961
1222
  - 如果信息不明确,请请求用户补充
962
- - 如果执行过程中连续失败5次,请使用ask_user询问用户操作
1223
+ - 如果执行过程中连续失败5次,请请求用户操作
963
1224
  - 操作列表:{action_handlers}{memory_prompts}
1225
+
1226
+ 注意:如果当前部分任务已完成,之前的上下文价值不大,可以输出<!!!SUMMARY!!!>标记来触发总结并清空历史,以便开始新的任务阶段。
964
1227
  </system_prompt>
965
1228
 
966
1229
  请继续。
@@ -986,7 +1249,19 @@ class Agent:
986
1249
  self.session.prompt = f"{user_input}"
987
1250
  try:
988
1251
  set_agent(self.name, self)
989
- # 广播任务开始事件(不影响主流程)
1252
+
1253
+ # 关键流程:直接调用 memory_manager 重置任务状态
1254
+ try:
1255
+ self.memory_manager._on_task_started(
1256
+ agent=self,
1257
+ name=self.name,
1258
+ description=self.description,
1259
+ user_input=self.session.prompt,
1260
+ )
1261
+ except Exception:
1262
+ pass
1263
+
1264
+ # 非关键流程:广播任务开始事件(用于日志、监控等)
990
1265
  try:
991
1266
  self.event_bus.emit(
992
1267
  TASK_STARTED,
@@ -999,7 +1274,7 @@ class Agent:
999
1274
  pass
1000
1275
  return self._main_loop()
1001
1276
  except Exception as e:
1002
- PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
1277
+ print(f"任务失败: {str(e)}")
1003
1278
  return f"Task failed: {str(e)}"
1004
1279
 
1005
1280
  def _main_loop(self) -> Any:
@@ -1041,7 +1316,7 @@ class Agent:
1041
1316
  return self._complete_task(auto_completed=False)
1042
1317
 
1043
1318
  if any(handler.can_handle(current_response) for handler in self.output_handler):
1044
- if self.user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
1319
+ if self.confirm_callback("检测到有工具调用,是否继续处理工具调用?", True):
1045
1320
  self.session.prompt = join_prompts([
1046
1321
  f"被用户中断,用户补充信息为:{user_input}",
1047
1322
  "用户同意继续工具调用。"
@@ -1104,8 +1379,42 @@ class Agent:
1104
1379
  temp_model.set_system_prompt(system_prompt)
1105
1380
  return temp_model
1106
1381
 
1382
+ def _build_child_agent_params(self, name: str, description: str) -> Dict[str, Any]:
1383
+ """构建子Agent参数,尽量继承父Agent配置,并确保子Agent非交互自动完成。"""
1384
+ use_tools_param: Optional[List[str]] = None
1385
+ try:
1386
+ tr = self.get_tool_registry()
1387
+ if isinstance(tr, ToolRegistry):
1388
+ selected_tools = tr.get_all_tools()
1389
+ use_tools_param = [t["name"] for t in selected_tools]
1390
+ except Exception:
1391
+ use_tools_param = None
1392
+
1393
+ return {
1394
+ "system_prompt": origin_agent_system_prompt,
1395
+ "name": name,
1396
+ "description": description,
1397
+ "model_group": self.model_group,
1398
+ "summary_prompt": self.summary_prompt,
1399
+ "auto_complete": True,
1400
+ "use_tools": use_tools_param,
1401
+ "execute_tool_confirm": self.execute_tool_confirm,
1402
+ "need_summary": self.need_summary,
1403
+ "multiline_inputer": self.multiline_inputer,
1404
+ "use_methodology": self.use_methodology,
1405
+ "use_analysis": self.use_analysis,
1406
+ "force_save_memory": self.force_save_memory,
1407
+ "files": self.files,
1408
+ "confirm_callback": self.confirm_callback,
1409
+ "non_interactive": True,
1410
+ "in_multi_agent": True,
1411
+ }
1412
+
1107
1413
  def _filter_tools_if_needed(self, task: str):
1108
- """如果工具数量超过阈值,使用大模型筛选相关工具"""
1414
+ """如果工具数量超过阈值,使用大模型筛选相关工具
1415
+
1416
+ 注意:仅筛选用户自定义工具,内置工具不参与筛选(始终保留)
1417
+ """
1109
1418
  tool_registry = self.get_tool_registry()
1110
1419
  if not isinstance(tool_registry, ToolRegistry):
1111
1420
  return
@@ -1115,10 +1424,16 @@ class Agent:
1115
1424
  if len(all_tools) <= threshold:
1116
1425
  return
1117
1426
 
1118
- # 为工具选择构建提示
1427
+ # 获取用户自定义工具(非内置工具),仅对这些工具进行筛选
1428
+ custom_tools = tool_registry.get_custom_tools()
1429
+ if not custom_tools:
1430
+ # 没有用户自定义工具,无需筛选
1431
+ return
1432
+
1433
+ # 为工具选择构建提示(仅包含用户自定义工具)
1119
1434
  tools_prompt_part = ""
1120
1435
  tool_names = []
1121
- for i, tool in enumerate(all_tools, 1):
1436
+ for i, tool in enumerate(custom_tools, 1):
1122
1437
  tool_names.append(tool["name"])
1123
1438
  tools_prompt_part += f"{i}. {tool['name']}: {tool['description']}\n"
1124
1439
 
@@ -1136,9 +1451,7 @@ class Agent:
1136
1451
  请根据用户任务,从列表中选择最相关的工具。
1137
1452
  请仅返回所选工具的编号,以逗号分隔。例如:1, 5, 12
1138
1453
  """
1139
- PrettyOutput.print(
1140
- f"工具数量超过{threshold}个,正在使用AI筛选相关工具...", OutputType.INFO
1141
- )
1454
+ print(f"ℹ️ 工具数量超过{threshold}个,正在使用AI筛选相关工具...")
1142
1455
  # 广播工具筛选开始事件
1143
1456
  try:
1144
1457
  self.event_bus.emit(
@@ -1171,13 +1484,13 @@ class Agent:
1171
1484
  if selected_tool_names:
1172
1485
  # 移除重复项
1173
1486
  selected_tool_names = sorted(list(set(selected_tool_names)))
1174
- tool_registry.use_tools(selected_tool_names)
1487
+ # 合并内置工具名称和筛选出的用户自定义工具名称
1488
+ builtin_names = list(tool_registry._builtin_tool_names)
1489
+ final_tool_names = sorted(list(set(builtin_names + selected_tool_names)))
1490
+ tool_registry.use_tools(final_tool_names)
1175
1491
  # 使用筛选后的工具列表重新设置系统提示
1176
1492
  self._setup_system_prompt()
1177
- PrettyOutput.print(
1178
- f"已筛选出 {len(selected_tool_names)} 个相关工具: {', '.join(selected_tool_names)}",
1179
- OutputType.SUCCESS,
1180
- )
1493
+ print(f"✅ 已筛选出 {len(selected_tool_names)} 个相关工具: {', '.join(selected_tool_names)}")
1181
1494
  # 广播工具筛选事件
1182
1495
  try:
1183
1496
  self.event_bus.emit(
@@ -1191,9 +1504,7 @@ class Agent:
1191
1504
  except Exception:
1192
1505
  pass
1193
1506
  else:
1194
- PrettyOutput.print(
1195
- "AI 未能筛选出任何相关工具,将使用所有工具。", OutputType.WARNING
1196
- )
1507
+ print("⚠️ AI 未能筛选出任何相关工具,将使用所有工具。")
1197
1508
  # 广播工具筛选事件(无筛选结果)
1198
1509
  try:
1199
1510
  self.event_bus.emit(
@@ -1208,9 +1519,7 @@ class Agent:
1208
1519
  pass
1209
1520
 
1210
1521
  except Exception as e:
1211
- PrettyOutput.print(
1212
- f"工具筛选失败: {e},将使用所有工具。", OutputType.ERROR
1213
- )
1522
+ print(f"❌ 工具筛选失败: {e},将使用所有工具。")
1214
1523
 
1215
1524
  def _check_and_organize_memory(self):
1216
1525
  """
@@ -1227,7 +1536,7 @@ class Agent:
1227
1536
  "global",
1228
1537
  )
1229
1538
  except Exception as e:
1230
- PrettyOutput.print(f"检查记忆库时发生意外错误: {e}", OutputType.WARNING)
1539
+ print(f"⚠️ 检查记忆库时发生意外错误: {e}")
1231
1540
 
1232
1541
  def _perform_memory_check(self, memory_type: str, base_path: Path, scope_name: str):
1233
1542
  """执行特定范围的记忆检查和整理"""
@@ -1268,11 +1577,8 @@ class Agent:
1268
1577
  f"并且存在3个以上标签重叠的记忆。\n"
1269
1578
  f"是否立即整理记忆库以优化性能和相关性?"
1270
1579
  )
1271
- if self.user_confirm(prompt, True):
1272
- PrettyOutput.print(
1273
- f"正在开始整理 '{scope_name}' ({memory_type}) 记忆库...",
1274
- OutputType.INFO,
1275
- )
1580
+ if self.confirm_callback(prompt, False):
1581
+ print(f"ℹ️ 正在开始整理 '{scope_name}' ({memory_type}) 记忆库...")
1276
1582
  organizer.organize_memories(memory_type, min_overlap=3)
1277
1583
  else:
1278
- PrettyOutput.print(f"已取消 '{scope_name}' 记忆库整理。", OutputType.INFO)
1584
+ print(f"ℹ️ 已取消 '{scope_name}' 记忆库整理。")