jarvis-ai-assistant 0.3.25__py3-none-any.whl → 0.3.27__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 (35) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +290 -169
  3. jarvis/jarvis_agent/config.py +92 -0
  4. jarvis/jarvis_agent/event_bus.py +48 -0
  5. jarvis/jarvis_agent/jarvis.py +69 -35
  6. jarvis/jarvis_agent/memory_manager.py +70 -2
  7. jarvis/jarvis_agent/prompt_manager.py +82 -0
  8. jarvis/jarvis_agent/run_loop.py +130 -0
  9. jarvis/jarvis_agent/task_analyzer.py +88 -9
  10. jarvis/jarvis_agent/task_manager.py +26 -0
  11. jarvis/jarvis_agent/user_interaction.py +42 -0
  12. jarvis/jarvis_code_agent/code_agent.py +18 -3
  13. jarvis/jarvis_code_agent/lint.py +5 -5
  14. jarvis/jarvis_data/config_schema.json +7 -6
  15. jarvis/jarvis_git_squash/main.py +6 -1
  16. jarvis/jarvis_git_utils/git_commiter.py +38 -12
  17. jarvis/jarvis_platform/base.py +4 -5
  18. jarvis/jarvis_platform_manager/main.py +28 -11
  19. jarvis/jarvis_rag/cli.py +5 -9
  20. jarvis/jarvis_stats/cli.py +13 -32
  21. jarvis/jarvis_stats/stats.py +179 -51
  22. jarvis/jarvis_tools/registry.py +15 -0
  23. jarvis/jarvis_tools/sub_agent.py +94 -84
  24. jarvis/jarvis_tools/sub_code_agent.py +12 -6
  25. jarvis/jarvis_utils/config.py +14 -0
  26. jarvis/jarvis_utils/fzf.py +56 -0
  27. jarvis/jarvis_utils/git_utils.py +3 -8
  28. jarvis/jarvis_utils/input.py +0 -3
  29. jarvis/jarvis_utils/utils.py +96 -16
  30. {jarvis_ai_assistant-0.3.25.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/METADATA +2 -3
  31. {jarvis_ai_assistant-0.3.25.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/RECORD +35 -29
  32. {jarvis_ai_assistant-0.3.25.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/WHEEL +0 -0
  33. {jarvis_ai_assistant-0.3.25.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/entry_points.txt +0 -0
  34. {jarvis_ai_assistant-0.3.25.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/licenses/LICENSE +0 -0
  35. {jarvis_ai_assistant-0.3.25.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,16 @@
3
3
  import datetime
4
4
  import os
5
5
  import platform
6
+ import re
6
7
  from pathlib import Path
8
+ from enum import Enum
7
9
  from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, Union
8
10
 
9
11
  # 第三方库导入
12
+ from rich.align import Align
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.text import Text
10
16
 
11
17
  # 本地库导入
12
18
  # jarvis_agent 相关
@@ -23,6 +29,13 @@ from jarvis.jarvis_agent.prompts import (
23
29
  SUMMARY_REQUEST_PROMPT,
24
30
  TASK_ANALYSIS_PROMPT,
25
31
  )
32
+ from jarvis.jarvis_tools.registry import ToolRegistry
33
+ from jarvis.jarvis_agent.prompt_manager import PromptManager
34
+ from jarvis.jarvis_agent.event_bus import EventBus
35
+ from jarvis.jarvis_agent.config import AgentConfig
36
+ from jarvis.jarvis_agent.run_loop import AgentRunLoop
37
+ from jarvis.jarvis_agent.user_interaction import UserInteractionHandler
38
+ from jarvis.jarvis_utils.methodology import _load_all_methodologies
26
39
 
27
40
  # jarvis_platform 相关
28
41
  from jarvis.jarvis_platform.base import BasePlatform
@@ -63,16 +76,6 @@ def show_agent_startup_stats(
63
76
  model_name: 使用的模型名称
64
77
  """
65
78
  try:
66
- from jarvis.jarvis_utils.methodology import _load_all_methodologies
67
- from jarvis.jarvis_tools.registry import ToolRegistry
68
- from jarvis.jarvis_utils.config import get_data_dir
69
- from pathlib import Path
70
- from rich.console import Console
71
- from rich.panel import Panel
72
- from rich.text import Text
73
- from rich.align import Align
74
- import os
75
-
76
79
  methodologies = _load_all_methodologies()
77
80
  methodology_count = len(methodologies)
78
81
 
@@ -198,12 +201,23 @@ origin_agent_system_prompt = f"""
198
201
  """
199
202
 
200
203
 
204
+ class LoopAction(Enum):
205
+ SKIP_TURN = "skip_turn"
206
+ CONTINUE = "continue"
207
+ COMPLETE = "complete"
208
+
209
+
201
210
  class Agent:
202
211
  def clear_history(self):
203
212
  """
204
213
  Clears the current conversation history by delegating to the session manager.
205
214
  """
206
215
  self.session.clear_history()
216
+ # 广播清理历史后的事件
217
+ try:
218
+ self.event_bus.emit("after_history_clear", agent=self)
219
+ except Exception:
220
+ pass
207
221
 
208
222
  def __del__(self):
209
223
  # 只有在记录启动时才停止记录
@@ -279,15 +293,16 @@ class Agent:
279
293
  self._init_session()
280
294
 
281
295
  # 初始化处理器
282
- safe_output_handlers: List[OutputHandlerProtocol] = []
283
- if output_handler:
284
- safe_output_handlers = output_handler
285
- safe_use_tools: List[str] = []
286
- if use_tools:
287
- safe_use_tools = use_tools
288
296
  self._init_handlers(
289
- safe_output_handlers, input_handler, multiline_inputer, safe_use_tools
297
+ output_handler or [],
298
+ input_handler,
299
+ multiline_inputer,
300
+ use_tools or [],
290
301
  )
302
+ # 初始化用户交互封装,保持向后兼容
303
+ self.user_interaction = UserInteractionHandler(self.multiline_inputer, self.user_confirm)
304
+ # 将确认函数指向封装后的 confirm,保持既有调用不变
305
+ self.user_confirm = self.user_interaction.confirm # type: ignore[assignment]
291
306
 
292
307
  # 初始化配置
293
308
  self._init_config(
@@ -299,10 +314,13 @@ class Agent:
299
314
  force_save_memory,
300
315
  )
301
316
 
317
+ # 初始化事件总线需先于管理器,以便管理器在构造中安全订阅事件
318
+ self.event_bus = EventBus()
302
319
  # 初始化管理器
303
320
  self.memory_manager = MemoryManager(self)
304
321
  self.task_analyzer = TaskAnalyzer(self)
305
322
  self.file_methodology_manager = FileMethodologyManager(self)
323
+ self.prompt_manager = PromptManager(self)
306
324
 
307
325
  # 设置系统提示词
308
326
  self._setup_system_prompt()
@@ -340,16 +358,10 @@ class Agent:
340
358
  use_tools: List[str],
341
359
  ):
342
360
  """初始化各种处理器"""
343
- from jarvis.jarvis_tools.registry import ToolRegistry
344
-
345
- self.output_handler = output_handler if output_handler else [ToolRegistry()]
361
+ self.output_handler = output_handler or [ToolRegistry()]
346
362
  self.set_use_tools(use_tools)
347
-
348
- self.input_handler = input_handler if input_handler is not None else []
349
-
350
- self.multiline_inputer = (
351
- multiline_inputer if multiline_inputer else get_multiline_input
352
- )
363
+ self.input_handler = input_handler or []
364
+ self.multiline_inputer = multiline_inputer or get_multiline_input
353
365
 
354
366
  def _init_config(
355
367
  self,
@@ -361,47 +373,57 @@ class Agent:
361
373
  force_save_memory: Optional[bool],
362
374
  ):
363
375
  """初始化配置选项"""
364
- # 如果有上传文件,自动禁用方法论
365
- self.use_methodology = (
366
- False
367
- if self.files
368
- else (
369
- use_methodology if use_methodology is not None else is_use_methodology()
370
- )
371
- )
372
-
373
- self.use_analysis = (
374
- use_analysis if use_analysis is not None else is_use_analysis()
375
- )
376
-
377
- self.execute_tool_confirm = (
378
- execute_tool_confirm
379
- if execute_tool_confirm is not None
380
- else is_execute_tool_confirm()
381
- )
382
-
383
- self.summary_prompt = (
384
- summary_prompt if summary_prompt else DEFAULT_SUMMARY_PROMPT
385
- )
386
-
387
- self.max_token_count = get_max_token_count(model_group)
388
-
389
- self.force_save_memory = (
390
- force_save_memory
391
- if force_save_memory is not None
392
- else is_force_save_memory()
393
- )
376
+ # 使用集中配置解析,保持与原逻辑一致
377
+ cfg = AgentConfig(
378
+ system_prompt=self.system_prompt,
379
+ name=self.name,
380
+ description=self.description,
381
+ model_group=model_group,
382
+ auto_complete=self.auto_complete,
383
+ need_summary=self.need_summary,
384
+ summary_prompt=summary_prompt,
385
+ execute_tool_confirm=execute_tool_confirm,
386
+ use_methodology=use_methodology,
387
+ use_analysis=use_analysis,
388
+ force_save_memory=force_save_memory,
389
+ files=self.files,
390
+ max_token_count=None,
391
+ ).resolve_defaults()
392
+
393
+ # 将解析结果回填到 Agent 实例属性,保持向后兼容
394
+ self.use_methodology = bool(cfg.use_methodology)
395
+ self.use_analysis = bool(cfg.use_analysis)
396
+ self.execute_tool_confirm = bool(cfg.execute_tool_confirm)
397
+ self.summary_prompt = cfg.summary_prompt or DEFAULT_SUMMARY_PROMPT
398
+ self.max_token_count = int(cfg.max_token_count or get_max_token_count(model_group))
399
+ self.force_save_memory = bool(cfg.force_save_memory)
400
+
401
+ # 聚合配置到 AgentConfig,作为后续单一事实来源(保持兼容,不改变既有属性使用)
402
+ self.config = cfg
394
403
 
395
404
  def _setup_system_prompt(self):
396
405
  """设置系统提示词"""
397
- action_prompt = self.get_tool_usage_prompt()
398
- self.model.set_system_prompt( # type: ignore
399
- f"""
406
+ try:
407
+ if hasattr(self, "prompt_manager"):
408
+ prompt_text = self.prompt_manager.build_system_prompt()
409
+ else:
410
+ action_prompt = self.get_tool_usage_prompt()
411
+ prompt_text = f"""
400
412
  {self.system_prompt}
401
413
 
402
414
  {action_prompt}
403
415
  """
404
- )
416
+ self.model.set_system_prompt(prompt_text) # type: ignore
417
+ except Exception:
418
+ # 回退到原始行为,确保兼容性
419
+ action_prompt = self.get_tool_usage_prompt()
420
+ self.model.set_system_prompt( # type: ignore
421
+ f"""
422
+ {self.system_prompt}
423
+
424
+ {action_prompt}
425
+ """
426
+ )
405
427
 
406
428
  def set_user_data(self, key: str, value: Any):
407
429
  """Sets user data in the session."""
@@ -413,8 +435,6 @@ class Agent:
413
435
 
414
436
  def set_use_tools(self, use_tools):
415
437
  """设置要使用的工具列表"""
416
- from jarvis.jarvis_tools.registry import ToolRegistry
417
-
418
438
  for handler in self.output_handler:
419
439
  if isinstance(handler, ToolRegistry):
420
440
  if use_tools:
@@ -435,6 +455,9 @@ class Agent:
435
455
  If the configured multiline_inputer supports 'print_on_empty' keyword, pass it;
436
456
  otherwise, fall back to calling with a single argument for compatibility.
437
457
  """
458
+ # 优先通过用户交互封装,便于未来替换 UI
459
+ if hasattr(self, "user_interaction"):
460
+ return self.user_interaction.multiline_input(tip, print_on_empty)
438
461
  try:
439
462
  # Try to pass the keyword for enhanced input handler
440
463
  return self.multiline_inputer(tip, print_on_empty=print_on_empty) # type: ignore
@@ -463,13 +486,15 @@ class Agent:
463
486
 
464
487
  def get_tool_registry(self) -> Optional[Any]:
465
488
  """获取工具注册表实例"""
466
- from jarvis.jarvis_tools.registry import ToolRegistry
467
-
468
489
  for handler in self.output_handler:
469
490
  if isinstance(handler, ToolRegistry):
470
491
  return handler
471
492
  return None
472
493
 
494
+ def get_event_bus(self) -> EventBus:
495
+ """获取事件总线实例"""
496
+ return self.event_bus
497
+
473
498
  def _call_model(
474
499
  self, message: str, need_complete: bool = False, run_input_handlers: bool = True
475
500
  ) -> str:
@@ -518,11 +543,38 @@ class Agent:
518
543
 
519
544
  def _add_addon_prompt(self, message: str, need_complete: bool) -> str:
520
545
  """添加附加提示到消息"""
546
+ # 广播添加附加提示前事件(不影响主流程)
547
+ try:
548
+ self.event_bus.emit(
549
+ "before_addon_prompt",
550
+ agent=self,
551
+ need_complete=need_complete,
552
+ current_message=message,
553
+ has_session_addon=bool(self.session.addon_prompt),
554
+ )
555
+ except Exception:
556
+ pass
557
+
558
+ addon_text = ""
521
559
  if self.session.addon_prompt:
522
- message += f"\n\n{self.session.addon_prompt}"
560
+ addon_text = self.session.addon_prompt
561
+ message += f"\n\n{addon_text}"
523
562
  self.session.addon_prompt = ""
524
563
  else:
525
- message += f"\n\n{self.make_default_addon_prompt(need_complete)}"
564
+ addon_text = self.make_default_addon_prompt(need_complete)
565
+ message += f"\n\n{addon_text}"
566
+
567
+ # 广播添加附加提示后事件(不影响主流程)
568
+ try:
569
+ self.event_bus.emit(
570
+ "after_addon_prompt",
571
+ agent=self,
572
+ need_complete=need_complete,
573
+ addon_text=addon_text,
574
+ final_message=message,
575
+ )
576
+ except Exception:
577
+ pass
526
578
  return message
527
579
 
528
580
  def _manage_conversation_length(self, message: str) -> str:
@@ -542,7 +594,29 @@ class Agent:
542
594
  if not self.model:
543
595
  raise RuntimeError("Model not initialized")
544
596
 
597
+ # 事件:模型调用前
598
+ try:
599
+ self.event_bus.emit(
600
+ "before_model_call",
601
+ agent=self,
602
+ message=message,
603
+ )
604
+ except Exception:
605
+ pass
606
+
545
607
  response = self.model.chat_until_success(message) # type: ignore
608
+
609
+ # 事件:模型调用后
610
+ try:
611
+ self.event_bus.emit(
612
+ "after_model_call",
613
+ agent=self,
614
+ message=message,
615
+ response=response,
616
+ )
617
+ except Exception:
618
+ pass
619
+
546
620
  self.session.conversation_length += get_context_token_count(response)
547
621
 
548
622
  return response
@@ -586,12 +660,11 @@ class Agent:
586
660
  注意:
587
661
  当上下文长度超过最大值时使用
588
662
  """
589
- # 在清理历史之前,提示用户保存重要记忆
663
+ # 在清理历史之前,提示用户保存重要记忆(事件驱动触发实际保存)
590
664
  if self.force_save_memory:
591
665
  PrettyOutput.print(
592
666
  "对话历史即将被总结和清理,请先保存重要信息...", OutputType.INFO
593
667
  )
594
- self.memory_manager.prompt_memory_save()
595
668
 
596
669
  if self._should_use_file_upload():
597
670
  return self._handle_history_with_file_upload()
@@ -613,17 +686,38 @@ class Agent:
613
686
 
614
687
  # 清理历史(但不清理prompt,因为prompt会在builtin_input_handler中设置)
615
688
  if self.model:
689
+ # 广播清理历史前事件
690
+ try:
691
+ self.event_bus.emit("before_history_clear", agent=self)
692
+ except Exception:
693
+ pass
616
694
  self.model.reset()
617
695
  # 重置后重新设置系统提示词,确保系统约束仍然生效
618
696
  self._setup_system_prompt()
619
697
  # 重置会话
620
698
  self.session.clear_history()
699
+ # 广播清理历史后的事件
700
+ try:
701
+ self.event_bus.emit("after_history_clear", agent=self)
702
+ except Exception:
703
+ pass
621
704
 
622
705
  return formatted_summary
623
706
 
624
707
  def _handle_history_with_file_upload(self) -> str:
625
708
  """使用文件上传方式处理历史"""
626
- return self.file_methodology_manager.handle_history_with_file_upload()
709
+ # 广播清理历史前事件
710
+ try:
711
+ self.event_bus.emit("before_history_clear", agent=self)
712
+ except Exception:
713
+ pass
714
+ result = self.file_methodology_manager.handle_history_with_file_upload()
715
+ # 广播清理历史后的事件
716
+ try:
717
+ self.event_bus.emit("after_history_clear", agent=self)
718
+ except Exception:
719
+ pass
720
+ return result
627
721
 
628
722
  def _format_summary_message(self, summary: str) -> str:
629
723
  """格式化摘要消息"""
@@ -654,26 +748,56 @@ class Agent:
654
748
  2. 对于子Agent: 可能会生成总结(如果启用)
655
749
  3. 使用spinner显示生成状态
656
750
  """
657
- # 收集满意度反馈
658
- satisfaction_feedback = self.task_analyzer.collect_satisfaction_feedback(
659
- auto_completed
660
- )
661
-
662
- if self.use_analysis:
663
- self.task_analyzer.analysis_task(satisfaction_feedback)
664
-
751
+ # 事件驱动方式:
752
+ # - TaskAnalyzer 通过订阅 before_summary/task_completed 事件执行分析与满意度收集
753
+ # - MemoryManager 通过订阅 before_history_clear/task_completed 事件执行记忆保存(受 force_save_memory 控制)
754
+ # 为减少耦合,这里不再直接调用上述组件,保持行为由事件触发
665
755
  self._check_and_organize_memory()
666
756
 
757
+ result = "任务完成"
758
+
667
759
  if self.need_summary:
668
760
 
669
761
  self.session.prompt = self.summary_prompt
762
+ # 广播将要生成总结事件
763
+ try:
764
+ self.event_bus.emit(
765
+ "before_summary",
766
+ agent=self,
767
+ prompt=self.session.prompt,
768
+ auto_completed=auto_completed,
769
+ need_summary=self.need_summary,
770
+ )
771
+ except Exception:
772
+ pass
773
+
670
774
  if not self.model:
671
775
  raise RuntimeError("Model not initialized")
672
776
  ret = self.model.chat_until_success(self.session.prompt) # type: ignore
777
+ result = ret
673
778
 
674
- return ret
779
+ # 广播完成总结事件
780
+ try:
781
+ self.event_bus.emit(
782
+ "after_summary",
783
+ agent=self,
784
+ summary=result,
785
+ )
786
+ except Exception:
787
+ pass
675
788
 
676
- return "任务完成"
789
+ # 广播任务完成事件(不影响主流程)
790
+ try:
791
+ self.event_bus.emit(
792
+ "task_completed",
793
+ agent=self,
794
+ auto_completed=auto_completed,
795
+ need_summary=self.need_summary,
796
+ )
797
+ except Exception:
798
+ pass
799
+
800
+ return result
677
801
 
678
802
  def make_default_addon_prompt(self, need_complete: bool) -> str:
679
803
  """生成附加提示。
@@ -682,6 +806,10 @@ class Agent:
682
806
  need_complete: 是否需要完成任务
683
807
 
684
808
  """
809
+ # 优先使用 PromptManager 以保持逻辑集中
810
+ if hasattr(self, "prompt_manager"):
811
+ return self.prompt_manager.build_default_addon_prompt(need_complete)
812
+
685
813
  # 结构化系统指令
686
814
  action_handlers = ", ".join([handler.name() for handler in self.output_handler])
687
815
 
@@ -733,6 +861,17 @@ class Agent:
733
861
  self.session.prompt = f"{user_input}"
734
862
  try:
735
863
  set_agent(self.name, self)
864
+ # 广播任务开始事件(不影响主流程)
865
+ try:
866
+ self.event_bus.emit(
867
+ "task_started",
868
+ agent=self,
869
+ name=self.name,
870
+ description=self.description,
871
+ user_input=self.session.prompt,
872
+ )
873
+ except Exception:
874
+ pass
736
875
  return self._main_loop()
737
876
  except Exception as e:
738
877
  PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
@@ -740,81 +879,17 @@ class Agent:
740
879
 
741
880
  def _main_loop(self) -> Any:
742
881
  """主运行循环"""
743
- run_input_handlers = True
882
+ # 委派至独立的运行循环类,保持行为一致
883
+ loop = AgentRunLoop(self)
884
+ return loop.run()
744
885
 
745
- while True:
746
- try:
747
- # 更新输入处理器标志
748
- if self.run_input_handlers_next_turn:
749
- run_input_handlers = True
750
- self.run_input_handlers_next_turn = False
751
-
752
- # 首次运行初始化
753
- if self.first:
754
- self._first_run()
755
-
756
- # 调用模型获取响应
757
- current_response = self._call_model(
758
- self.session.prompt, True, run_input_handlers
759
- )
760
-
761
- self.session.prompt = ""
762
- run_input_handlers = False
763
-
764
- # 处理中断
765
- interrupt_result = self._handle_run_interrupt(current_response)
766
- if interrupt_result:
767
- if isinstance(interrupt_result, tuple):
768
- run_input_handlers, should_continue = interrupt_result
769
- if should_continue:
770
- self.run_input_handlers_next_turn = True
771
- continue
772
- else:
773
- return interrupt_result
774
-
775
- # 处理工具调用
776
- need_return, prompt = self._call_tools(current_response)
777
- if self.session.prompt and prompt:
778
- self.session.prompt += "\n\n" + prompt
779
- else:
780
- self.session.prompt = prompt
781
-
782
- if need_return:
783
- return self.session.prompt
784
-
785
- # 执行回调
786
- if self.after_tool_call_cb:
787
- self.after_tool_call_cb(self)
788
-
789
- # 检查是否需要继续
790
- if self.session.prompt or self.session.addon_prompt:
791
- continue
792
-
793
- # 检查自动完成
794
- if self.auto_complete and ot("!!!COMPLETE!!!") in current_response:
795
- return self._complete_task(auto_completed=True)
796
-
797
- # 获取下一步用户输入
798
- next_action = self._get_next_user_action()
799
- if next_action == "continue":
800
- run_input_handlers = True
801
- continue
802
- elif next_action == "complete":
803
- return self._complete_task(auto_completed=False)
804
-
805
- except Exception as e:
806
- PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
807
- return f"Task failed: {str(e)}"
808
-
809
- def _handle_run_interrupt(
810
- self, current_response: str
811
- ) -> Optional[Union[Any, Tuple[bool, bool]]]:
886
+ def _handle_run_interrupt(self, current_response: str) -> Optional[Union[Any, "LoopAction"]]:
812
887
  """处理运行中的中断
813
888
 
814
889
  返回:
815
- None: 无中断,继续执行
816
- Any: 需要返回的结果
817
- Tuple[bool, bool]: (run_input_handlers, should_continue)
890
+ None: 无中断,或中断后允许继续执行当前响应
891
+ Any: 需要返回的最终结果
892
+ LoopAction.SKIP_TURN: 中断后需要跳过当前响应,并立即开始下一次循环
818
893
  """
819
894
  if not get_interrupt():
820
895
  return None
@@ -823,6 +898,16 @@ class Agent:
823
898
  user_input = self._multiline_input(
824
899
  "模型交互期间被中断,请输入用户干预信息:", False
825
900
  )
901
+ # 广播中断事件(包含用户输入,可能为空字符串)
902
+ try:
903
+ self.event_bus.emit(
904
+ "interrupt_triggered",
905
+ agent=self,
906
+ current_response=current_response,
907
+ user_input=user_input,
908
+ )
909
+ except Exception:
910
+ pass
826
911
 
827
912
  self.run_input_handlers_next_turn = True
828
913
 
@@ -836,16 +921,16 @@ class Agent:
836
921
  return None # 继续执行工具调用
837
922
  else:
838
923
  self.session.prompt = f"被用户中断,用户补充信息为:{user_input}\n\n检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
839
- return (True, True) # run_input_handlers=True, should_continue=True
924
+ return LoopAction.SKIP_TURN # 请求主循环 continue
840
925
  else:
841
926
  self.session.prompt = f"被用户中断,用户补充信息为:{user_input}"
842
- return (True, True) # run_input_handlers=True, should_continue=True
927
+ return LoopAction.SKIP_TURN # 请求主循环 continue
843
928
 
844
- def _get_next_user_action(self) -> str:
929
+ def _get_next_user_action(self) -> Union[str, "LoopAction"]:
845
930
  """获取用户下一步操作
846
931
 
847
932
  返回:
848
- str: "continue""complete"
933
+ LoopAction.CONTINUE 或 LoopAction.COMPLETE(兼容旧字符串值 "continue"/"complete"
849
934
  """
850
935
  user_input = self._multiline_input(
851
936
  f"{self.name}: 请输入,或输入空行来结束当前任务:", False
@@ -853,9 +938,10 @@ class Agent:
853
938
 
854
939
  if user_input:
855
940
  self.session.prompt = user_input
856
- return "continue"
941
+ # 使用显式动作信号,保留返回类型注释以保持兼容
942
+ return LoopAction.CONTINUE # type: ignore[return-value]
857
943
  else:
858
- return "complete"
944
+ return LoopAction.COMPLETE # type: ignore[return-value]
859
945
 
860
946
  def _first_run(self):
861
947
  """首次运行初始化"""
@@ -875,11 +961,20 @@ class Agent:
875
961
 
876
962
  self.first = False
877
963
 
964
+ def _create_temp_model(self, system_prompt: str) -> BasePlatform:
965
+ """创建一个用于执行一次性任务的临时模型实例,以避免污染主会话。"""
966
+ temp_model = PlatformRegistry().create_platform(
967
+ self.model.platform_name() # type: ignore
968
+ )
969
+ if not temp_model:
970
+ raise RuntimeError("创建临时模型失败。")
971
+
972
+ temp_model.set_model_name(self.model.name()) # type: ignore
973
+ temp_model.set_system_prompt(system_prompt)
974
+ return temp_model
975
+
878
976
  def _filter_tools_if_needed(self, task: str):
879
977
  """如果工具数量超过阈值,使用大模型筛选相关工具"""
880
- import re
881
- from jarvis.jarvis_tools.registry import ToolRegistry
882
-
883
978
  tool_registry = self.get_tool_registry()
884
979
  if not isinstance(tool_registry, ToolRegistry):
885
980
  return
@@ -913,19 +1008,21 @@ class Agent:
913
1008
  PrettyOutput.print(
914
1009
  f"工具数量超过{threshold}个,正在使用AI筛选相关工具...", OutputType.INFO
915
1010
  )
916
-
917
- # 使用临时模型实例调用模型,以避免污染历史记录
1011
+ # 广播工具筛选开始事件
918
1012
  try:
919
- from jarvis.jarvis_platform.registry import PlatformRegistry
920
-
921
- temp_model = PlatformRegistry().create_platform(
922
- self.model.platform_name() # type: ignore
1013
+ self.event_bus.emit(
1014
+ "before_tool_filter",
1015
+ agent=self,
1016
+ task=task,
1017
+ total_tools=len(all_tools),
1018
+ threshold=threshold,
923
1019
  )
924
- if not temp_model:
925
- raise RuntimeError("为工具选择创建临时模型失败。")
1020
+ except Exception:
1021
+ pass
926
1022
 
927
- temp_model.set_model_name(self.model.name()) # type: ignore
928
- temp_model.set_system_prompt("你是一个帮助筛选工具的助手。")
1023
+ # 使用临时模型实例调用模型,以避免污染历史记录
1024
+ try:
1025
+ temp_model = self._create_temp_model("你是一个帮助筛选工具的助手。")
929
1026
  selected_tools_str = temp_model.chat_until_success(
930
1027
  selection_prompt
931
1028
  ) # type: ignore
@@ -950,10 +1047,34 @@ class Agent:
950
1047
  f"已筛选出 {len(selected_tool_names)} 个相关工具: {', '.join(selected_tool_names)}",
951
1048
  OutputType.SUCCESS,
952
1049
  )
1050
+ # 广播工具筛选事件
1051
+ try:
1052
+ self.event_bus.emit(
1053
+ "tool_filtered",
1054
+ agent=self,
1055
+ task=task,
1056
+ selected_tools=selected_tool_names,
1057
+ total_tools=len(all_tools),
1058
+ threshold=threshold,
1059
+ )
1060
+ except Exception:
1061
+ pass
953
1062
  else:
954
1063
  PrettyOutput.print(
955
1064
  "AI 未能筛选出任何相关工具,将使用所有工具。", OutputType.WARNING
956
1065
  )
1066
+ # 广播工具筛选事件(无筛选结果)
1067
+ try:
1068
+ self.event_bus.emit(
1069
+ "tool_filtered",
1070
+ agent=self,
1071
+ task=task,
1072
+ selected_tools=[],
1073
+ total_tools=len(all_tools),
1074
+ threshold=threshold,
1075
+ )
1076
+ except Exception:
1077
+ pass
957
1078
 
958
1079
  except Exception as e:
959
1080
  PrettyOutput.print(