jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.0__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 (115) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +289 -87
  3. jarvis/jarvis_agent/agent_manager.py +17 -8
  4. jarvis/jarvis_agent/edit_file_handler.py +374 -86
  5. jarvis/jarvis_agent/event_bus.py +1 -1
  6. jarvis/jarvis_agent/file_context_handler.py +79 -0
  7. jarvis/jarvis_agent/jarvis.py +601 -43
  8. jarvis/jarvis_agent/main.py +32 -2
  9. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  10. jarvis/jarvis_agent/run_loop.py +38 -5
  11. jarvis/jarvis_agent/share_manager.py +8 -1
  12. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  13. jarvis/jarvis_agent/task_analyzer.py +5 -2
  14. jarvis/jarvis_agent/task_planner.py +496 -0
  15. jarvis/jarvis_agent/utils.py +5 -1
  16. jarvis/jarvis_agent/web_bridge.py +189 -0
  17. jarvis/jarvis_agent/web_output_sink.py +53 -0
  18. jarvis/jarvis_agent/web_server.py +751 -0
  19. jarvis/jarvis_c2rust/__init__.py +26 -0
  20. jarvis/jarvis_c2rust/cli.py +613 -0
  21. jarvis/jarvis_c2rust/collector.py +258 -0
  22. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  23. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  24. jarvis/jarvis_c2rust/optimizer.py +960 -0
  25. jarvis/jarvis_c2rust/scanner.py +1681 -0
  26. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  27. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  28. jarvis/jarvis_code_agent/code_agent.py +1171 -94
  29. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  30. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  31. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  32. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  33. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  34. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  35. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  36. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  37. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  38. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  39. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  40. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  41. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  42. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  43. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  44. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  45. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  46. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  47. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  48. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  49. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  50. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  51. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  52. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  53. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  54. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  55. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  56. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  57. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  58. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  59. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  60. jarvis/jarvis_code_agent/lint.py +270 -8
  61. jarvis/jarvis_code_agent/utils.py +142 -0
  62. jarvis/jarvis_code_analysis/code_review.py +483 -569
  63. jarvis/jarvis_data/config_schema.json +97 -8
  64. jarvis/jarvis_git_utils/git_commiter.py +38 -26
  65. jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
  66. jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
  67. jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
  68. jarvis/jarvis_multi_agent/__init__.py +239 -25
  69. jarvis/jarvis_multi_agent/main.py +37 -1
  70. jarvis/jarvis_platform/base.py +103 -51
  71. jarvis/jarvis_platform/openai.py +26 -1
  72. jarvis/jarvis_platform/yuanbao.py +1 -1
  73. jarvis/jarvis_platform_manager/service.py +2 -2
  74. jarvis/jarvis_rag/cli.py +4 -4
  75. jarvis/jarvis_sec/__init__.py +3605 -0
  76. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  77. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  78. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  79. jarvis/jarvis_sec/cli.py +116 -0
  80. jarvis/jarvis_sec/report.py +257 -0
  81. jarvis/jarvis_sec/status.py +264 -0
  82. jarvis/jarvis_sec/types.py +20 -0
  83. jarvis/jarvis_sec/workflow.py +219 -0
  84. jarvis/jarvis_stats/cli.py +1 -1
  85. jarvis/jarvis_stats/stats.py +1 -1
  86. jarvis/jarvis_stats/visualizer.py +1 -1
  87. jarvis/jarvis_tools/cli/main.py +1 -0
  88. jarvis/jarvis_tools/execute_script.py +46 -9
  89. jarvis/jarvis_tools/generate_new_tool.py +3 -1
  90. jarvis/jarvis_tools/read_code.py +275 -12
  91. jarvis/jarvis_tools/read_symbols.py +141 -0
  92. jarvis/jarvis_tools/read_webpage.py +5 -3
  93. jarvis/jarvis_tools/registry.py +73 -35
  94. jarvis/jarvis_tools/search_web.py +15 -11
  95. jarvis/jarvis_tools/sub_agent.py +24 -42
  96. jarvis/jarvis_tools/sub_code_agent.py +14 -13
  97. jarvis/jarvis_tools/virtual_tty.py +1 -1
  98. jarvis/jarvis_utils/config.py +187 -35
  99. jarvis/jarvis_utils/embedding.py +3 -0
  100. jarvis/jarvis_utils/git_utils.py +181 -6
  101. jarvis/jarvis_utils/globals.py +3 -3
  102. jarvis/jarvis_utils/http.py +1 -1
  103. jarvis/jarvis_utils/input.py +78 -2
  104. jarvis/jarvis_utils/methodology.py +25 -19
  105. jarvis/jarvis_utils/utils.py +644 -359
  106. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
  107. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  108. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
  109. jarvis/jarvis_agent/config.py +0 -92
  110. jarvis/jarvis_tools/edit_file.py +0 -179
  111. jarvis/jarvis_tools/rewrite_file.py +0 -191
  112. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  113. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  114. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  115. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
jarvis/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Jarvis AI Assistant"""
3
3
 
4
- __version__ = "0.3.30"
4
+ __version__ = "0.7.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
@@ -24,16 +25,18 @@ from jarvis.jarvis_agent.tool_executor import execute_tool_call
24
25
  from jarvis.jarvis_agent.memory_manager import MemoryManager
25
26
  from jarvis.jarvis_memory_organizer.memory_organizer import MemoryOrganizer
26
27
  from jarvis.jarvis_agent.task_analyzer import TaskAnalyzer
28
+ from jarvis.jarvis_agent.task_planner import TaskPlanner
27
29
  from jarvis.jarvis_agent.file_methodology_manager import FileMethodologyManager
28
30
  from jarvis.jarvis_agent.prompts import (
29
31
  DEFAULT_SUMMARY_PROMPT,
30
32
  SUMMARY_REQUEST_PROMPT,
31
- TASK_ANALYSIS_PROMPT,
33
+ TASK_ANALYSIS_PROMPT as TASK_ANALYSIS_PROMPT,
32
34
  )
33
35
  from jarvis.jarvis_tools.registry import ToolRegistry
36
+ from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
37
+ from jarvis.jarvis_agent.rewrite_file_handler import RewriteFileHandler
34
38
  from jarvis.jarvis_agent.prompt_manager import PromptManager
35
39
  from jarvis.jarvis_agent.event_bus import EventBus
36
- from jarvis.jarvis_agent.config import AgentConfig
37
40
  from jarvis.jarvis_agent.run_loop import AgentRunLoop
38
41
  from jarvis.jarvis_agent.events import (
39
42
  BEFORE_SUMMARY,
@@ -54,6 +57,9 @@ from jarvis.jarvis_agent.events import (
54
57
  from jarvis.jarvis_agent.user_interaction import UserInteractionHandler
55
58
  from jarvis.jarvis_agent.utils import join_prompts
56
59
  from jarvis.jarvis_utils.methodology import _load_all_methodologies
60
+ from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
61
+ from jarvis.jarvis_agent.file_context_handler import file_context_handler
62
+ from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
57
63
 
58
64
  # jarvis_platform 相关
59
65
  from jarvis.jarvis_platform.base import BasePlatform
@@ -62,7 +68,6 @@ from jarvis.jarvis_platform.registry import PlatformRegistry
62
68
  # jarvis_utils 相关
63
69
  from jarvis.jarvis_utils.config import (
64
70
  get_data_dir,
65
- get_max_token_count,
66
71
  get_normal_model_name,
67
72
  get_normal_platform_name,
68
73
  is_execute_tool_confirm,
@@ -71,6 +76,9 @@ from jarvis.jarvis_utils.config import (
71
76
  is_use_methodology,
72
77
  get_tool_filter_threshold,
73
78
  get_after_tool_call_cb_dirs,
79
+ get_plan_max_depth,
80
+ is_plan_enabled,
81
+ get_addon_prompt_threshold,
74
82
  )
75
83
  from jarvis.jarvis_utils.embedding import get_context_token_count
76
84
  from jarvis.jarvis_utils.globals import (
@@ -243,8 +251,25 @@ class Agent:
243
251
  def clear_history(self):
244
252
  """
245
253
  Clears the current conversation history by delegating to the session manager.
254
+ Emits BEFORE_HISTORY_CLEAR/AFTER_HISTORY_CLEAR and reapplies system prompt to preserve constraints.
246
255
  """
256
+ # 广播清理历史前事件(不影响主流程)
257
+ try:
258
+ self.event_bus.emit(BEFORE_HISTORY_CLEAR, agent=self)
259
+ except Exception:
260
+ pass
261
+
262
+ # 清理会话历史并重置模型状态
247
263
  self.session.clear_history()
264
+ # 重置 addon_prompt 跳过轮数计数器
265
+ self._addon_prompt_skip_rounds = 0
266
+
267
+ # 重置后重新设置系统提示词,确保系统约束仍然生效
268
+ try:
269
+ self._setup_system_prompt()
270
+ except Exception:
271
+ pass
272
+
248
273
  # 广播清理历史后的事件
249
274
  try:
250
275
  self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
@@ -264,6 +289,21 @@ class Agent:
264
289
  """获取工具使用提示"""
265
290
  return build_action_prompt(self.output_handler) # type: ignore
266
291
 
292
+ def __new__(cls, *args, **kwargs):
293
+ if kwargs.get("agent_type") == "code":
294
+ try:
295
+ from jarvis.jarvis_code_agent.code_agent import CodeAgent
296
+ except ImportError as e:
297
+ raise RuntimeError(
298
+ "CodeAgent could not be imported. Please ensure jarvis_code_agent is installed correctly."
299
+ ) from e
300
+
301
+ # 移除 agent_type 避免无限循环,并传递所有其他参数
302
+ kwargs.pop("agent_type", None)
303
+ return CodeAgent(**kwargs)
304
+ else:
305
+ return super().__new__(cls)
306
+
267
307
  def __init__(
268
308
  self,
269
309
  system_prompt: str,
@@ -274,15 +314,23 @@ class Agent:
274
314
  auto_complete: bool = False,
275
315
  output_handler: Optional[List[OutputHandlerProtocol]] = None,
276
316
  use_tools: Optional[List[str]] = None,
277
- input_handler: Optional[List[Callable[[str, Any], Tuple[str, bool]]]] = None,
278
317
  execute_tool_confirm: Optional[bool] = None,
279
318
  need_summary: bool = True,
319
+ auto_summary_rounds: Optional[int] = None,
280
320
  multiline_inputer: Optional[Callable[[str], str]] = None,
281
321
  use_methodology: Optional[bool] = None,
282
322
  use_analysis: Optional[bool] = None,
283
323
  force_save_memory: Optional[bool] = None,
324
+ disable_file_edit: bool = False,
284
325
  files: Optional[List[str]] = None,
285
326
  confirm_callback: Optional[Callable[[str, bool], bool]] = None,
327
+ non_interactive: Optional[bool] = None,
328
+ in_multi_agent: Optional[bool] = None,
329
+ plan: Optional[bool] = None,
330
+ plan_max_depth: Optional[int] = None,
331
+ plan_depth: int = 0,
332
+ agent_type: str = "normal",
333
+ **kwargs,
286
334
  ):
287
335
  """初始化Jarvis Agent实例
288
336
 
@@ -293,8 +341,6 @@ class Agent:
293
341
 
294
342
  summary_prompt: 任务总结提示模板
295
343
  auto_complete: 是否自动完成任务
296
- output_handler: 输出处理器列表
297
- input_handler: 输入处理器列表
298
344
  execute_tool_confirm: 执行工具前是否需要确认
299
345
  need_summary: 是否需要生成总结
300
346
  multiline_inputer: 多行输入处理器
@@ -302,21 +348,57 @@ class Agent:
302
348
  use_analysis: 是否使用任务分析
303
349
  force_save_memory: 是否强制保存记忆
304
350
  confirm_callback: 用户确认回调函数,签名为 (tip: str, default: bool) -> bool;默认使用CLI的user_confirm
351
+ non_interactive: 是否以非交互模式运行(优先级最高,覆盖环境变量与配置)
352
+ plan: 是否启用任务规划与子任务拆分(默认从配置加载;启用后在进入主循环前评估是否需要将任务拆分为 <SUB_TASK> 列表,逐一由子Agent执行并汇总结果)
353
+ plan_max_depth: 任务规划的最大层数(默认3,可通过配置 JARVIS_PLAN_MAX_DEPTH 或入参覆盖)
354
+ plan_depth: 当前规划层数(内部用于递归控制,子Agent会在父基础上+1)
305
355
  """
306
- # 基础属性初始化
307
- self.files = files or []
356
+ # 基础属性初始化(仅根据入参设置原始值;实际生效的默认回退在 _init_config 中统一解析)
357
+ # 标识与描述
308
358
  self.name = make_agent_name(name)
309
359
  self.description = description
310
360
  self.system_prompt = system_prompt
311
- self.need_summary = need_summary
312
- self.auto_complete = auto_complete
361
+ # 行为控制开关(原始入参值)
362
+ self.auto_complete = bool(auto_complete)
363
+ self.need_summary = bool(need_summary)
364
+ # 自动摘要轮次:None 表示使用配置文件中的默认值,由 AgentRunLoop 决定最终取值
365
+ self.auto_summary_rounds = auto_summary_rounds
366
+ self.use_methodology = use_methodology
367
+ self.use_analysis = use_analysis
368
+ self.execute_tool_confirm = execute_tool_confirm
369
+ self.summary_prompt = summary_prompt
370
+ self.force_save_memory = force_save_memory
371
+ self.disable_file_edit = bool(disable_file_edit)
372
+ # 资源与环境
373
+ self.model_group = model_group
374
+ self.files = files or []
375
+ self.use_tools = use_tools
376
+ self.non_interactive = non_interactive
377
+ # 多智能体运行标志:用于控制非交互模式下的自动完成行为
378
+ self.in_multi_agent = bool(in_multi_agent)
379
+ # 任务规划:优先使用入参,否则回退到配置
380
+ self.plan = bool(plan) if plan is not None else is_plan_enabled()
381
+ # 规划深度与上限
382
+ try:
383
+ self.plan_max_depth = (
384
+ int(plan_max_depth) if plan_max_depth is not None else int(get_plan_max_depth())
385
+ )
386
+ except Exception:
387
+ self.plan_max_depth = 2
388
+ try:
389
+ self.plan_depth = int(plan_depth)
390
+ except Exception:
391
+ self.plan_depth = 0
392
+ # 运行时状态
313
393
  self.first = True
314
394
  self.run_input_handlers_next_turn = False
315
395
  self.user_data: Dict[str, Any] = {}
396
+ # 记录连续未添加 addon_prompt 的轮数
397
+ self._addon_prompt_skip_rounds: int = 0
316
398
 
317
399
 
318
400
  # 用户确认回调:默认使用 CLI 的 user_confirm,可由外部注入以支持 TUI/GUI
319
- self.user_confirm: Callable[[str, bool], bool] = (
401
+ self.confirm_callback: Callable[[str, bool], bool] = (
320
402
  confirm_callback or user_confirm # type: ignore[assignment]
321
403
  )
322
404
 
@@ -326,25 +408,62 @@ class Agent:
326
408
 
327
409
  # 初始化处理器
328
410
  self._init_handlers(
329
- output_handler or [],
330
- input_handler,
331
411
  multiline_inputer,
412
+ output_handler,
332
413
  use_tools or [],
333
414
  )
334
415
  # 初始化用户交互封装,保持向后兼容
335
- self.user_interaction = UserInteractionHandler(self.multiline_inputer, self.user_confirm)
416
+ self.user_interaction = UserInteractionHandler(self.multiline_inputer, self.confirm_callback)
336
417
  # 将确认函数指向封装后的 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
- )
418
+ self.confirm_callback = self.user_interaction.confirm # type: ignore[assignment]
419
+ # 非交互模式参数支持:允许通过构造参数显式控制,便于其他Agent调用时设置
420
+ try:
421
+ # 优先使用构造参数,其次回退到环境变量
422
+ self.non_interactive = (
423
+ bool(non_interactive)
424
+ if non_interactive is not None
425
+ else str(os.environ.get("JARVIS_NON_INTERACTIVE", "")).lower() in ("1", "true", "yes")
426
+ )
427
+ # 如果构造参数显式提供,则同步到环境变量与全局配置,供下游组件读取
428
+ if non_interactive is not None:
429
+ os.environ["JARVIS_NON_INTERACTIVE"] = "true" if self.non_interactive else "false"
430
+
431
+ except Exception:
432
+ # 防御式回退
433
+ self.non_interactive = False
434
+
435
+ # 初始化配置(直接解析,不再依赖 _init_config)
436
+ try:
437
+ resolved_use_methodology = bool(use_methodology if use_methodology is not None else is_use_methodology())
438
+ except Exception:
439
+ resolved_use_methodology = bool(use_methodology) if use_methodology is not None else True
440
+
441
+ try:
442
+ resolved_use_analysis = bool(use_analysis if use_analysis is not None else is_use_analysis())
443
+ except Exception:
444
+ resolved_use_analysis = bool(use_analysis) if use_analysis is not None else True
445
+
446
+ try:
447
+ resolved_execute_tool_confirm = bool(execute_tool_confirm if execute_tool_confirm is not None else is_execute_tool_confirm())
448
+ except Exception:
449
+ resolved_execute_tool_confirm = bool(execute_tool_confirm) if execute_tool_confirm is not None else False
450
+
451
+ try:
452
+ resolved_force_save_memory = bool(force_save_memory if force_save_memory is not None else is_force_save_memory())
453
+ except Exception:
454
+ resolved_force_save_memory = bool(force_save_memory) if force_save_memory is not None else False
455
+
456
+ self.use_methodology = resolved_use_methodology
457
+ self.use_analysis = resolved_use_analysis
458
+ self.execute_tool_confirm = resolved_execute_tool_confirm
459
+ self.summary_prompt = (summary_prompt or DEFAULT_SUMMARY_PROMPT)
460
+ self.force_save_memory = resolved_force_save_memory
461
+ # 多智能体模式下,默认不自动完成(即使是非交互),仅在明确传入 auto_complete=True 时开启
462
+ if self.in_multi_agent:
463
+ self.auto_complete = bool(self.auto_complete)
464
+ else:
465
+ # 非交互模式下默认自动完成;否则保持传入的 auto_complete 值
466
+ self.auto_complete = bool(self.auto_complete or (self.non_interactive or False))
348
467
 
349
468
  # 初始化事件总线需先于管理器,以便管理器在构造中安全订阅事件
350
469
  self.event_bus = EventBus()
@@ -353,6 +472,8 @@ class Agent:
353
472
  self.task_analyzer = TaskAnalyzer(self)
354
473
  self.file_methodology_manager = FileMethodologyManager(self)
355
474
  self.prompt_manager = PromptManager(self)
475
+ # 任务规划器:封装规划与子任务调度逻辑
476
+ self.task_planner = TaskPlanner(self, plan_depth=self.plan_depth, plan_max_depth=self.plan_max_depth)
356
477
 
357
478
  # 设置系统提示词
358
479
  self._setup_system_prompt()
@@ -394,55 +515,26 @@ class Agent:
394
515
 
395
516
  def _init_handlers(
396
517
  self,
397
- output_handler: List[OutputHandlerProtocol],
398
- input_handler: Optional[List[Callable[[str, Any], Tuple[str, bool]]]],
399
518
  multiline_inputer: Optional[Callable[[str], str]],
519
+ output_handler: Optional[List[OutputHandlerProtocol]],
400
520
  use_tools: List[str],
401
521
  ):
402
522
  """初始化各种处理器"""
403
- self.output_handler = output_handler or [ToolRegistry()]
523
+ default_handlers: List[Any] = [ToolRegistry()]
524
+ if not getattr(self, "disable_file_edit", False):
525
+ default_handlers.extend([EditFileHandler(), RewriteFileHandler()])
526
+ handlers = output_handler or default_handlers
527
+ if getattr(self, "disable_file_edit", False):
528
+ handlers = [h for h in handlers if not isinstance(h, (EditFileHandler, RewriteFileHandler))]
529
+ self.output_handler = handlers
404
530
  self.set_use_tools(use_tools)
405
- self.input_handler = input_handler or []
531
+ self.input_handler = [
532
+ builtin_input_handler,
533
+ shell_input_handler,
534
+ file_context_handler,
535
+ ]
406
536
  self.multiline_inputer = multiline_inputer or get_multiline_input
407
537
 
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
538
  def _setup_system_prompt(self):
447
539
  """设置系统提示词"""
448
540
  try:
@@ -666,7 +758,13 @@ class Agent:
666
758
  return message
667
759
 
668
760
  def _add_addon_prompt(self, message: str, need_complete: bool) -> str:
669
- """添加附加提示到消息"""
761
+ """添加附加提示到消息
762
+
763
+ 规则:
764
+ 1. 如果 session.addon_prompt 存在,优先使用它
765
+ 2. 如果消息长度超过阈值,添加默认 addon_prompt
766
+ 3. 如果连续10轮都没有添加过 addon_prompt,强制添加一次
767
+ """
670
768
  # 广播添加附加提示前事件(不影响主流程)
671
769
  try:
672
770
  self.event_bus.emit(
@@ -680,13 +778,32 @@ class Agent:
680
778
  pass
681
779
 
682
780
  addon_text = ""
781
+ should_add = False
782
+
683
783
  if self.session.addon_prompt:
784
+ # 优先使用 session 中设置的 addon_prompt
684
785
  addon_text = self.session.addon_prompt
685
786
  message = join_prompts([message, addon_text])
686
787
  self.session.addon_prompt = ""
788
+ should_add = True
687
789
  else:
688
- addon_text = self.make_default_addon_prompt(need_complete)
689
- message = join_prompts([message, addon_text])
790
+ threshold = get_addon_prompt_threshold()
791
+ # 条件1:消息长度超过阈值
792
+ if len(message) > threshold:
793
+ addon_text = self.make_default_addon_prompt(need_complete)
794
+ message = join_prompts([message, addon_text])
795
+ should_add = True
796
+ # 条件2:连续10轮都没有添加过 addon_prompt,强制添加一次
797
+ elif self._addon_prompt_skip_rounds >= 10:
798
+ addon_text = self.make_default_addon_prompt(need_complete)
799
+ message = join_prompts([message, addon_text])
800
+ should_add = True
801
+
802
+ # 更新计数器:如果添加了 addon_prompt,重置计数器;否则递增
803
+ if should_add:
804
+ self._addon_prompt_skip_rounds = 0
805
+ else:
806
+ self._addon_prompt_skip_rounds += 1
690
807
 
691
808
  # 广播添加附加提示后事件(不影响主流程)
692
809
  try:
@@ -702,14 +819,9 @@ class Agent:
702
819
  return message
703
820
 
704
821
  def _manage_conversation_length(self, message: str) -> str:
705
- """管理对话长度,必要时进行摘要"""
822
+ """管理对话长度计数;摘要触发由轮次在 AgentRunLoop 中统一处理。"""
706
823
  self.session.conversation_length += get_context_token_count(message)
707
824
 
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
825
 
714
826
  return message
715
827
 
@@ -729,7 +841,14 @@ class Agent:
729
841
  pass
730
842
 
731
843
  response = self.model.chat_until_success(message) # type: ignore
732
-
844
+ # 防御: 模型可能返回空响应(None或空字符串),统一为空字符串并告警
845
+ if not response:
846
+ try:
847
+ PrettyOutput.print("模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
848
+ except Exception:
849
+ pass
850
+ response = ""
851
+
733
852
  # 事件:模型调用后
734
853
  try:
735
854
  self.event_bus.emit(
@@ -758,10 +877,21 @@ class Agent:
758
877
  try:
759
878
  if not self.model:
760
879
  raise RuntimeError("Model not initialized")
761
- summary = self.model.chat_until_success(
762
- self.session.prompt + "\n" + SUMMARY_REQUEST_PROMPT
763
- ) # type: ignore
880
+ # 优先使用外部传入的 summary_prompt;如为空则回退到默认的会话摘要请求
881
+ safe_summary_prompt = self.summary_prompt or ""
882
+ if isinstance(safe_summary_prompt, str) and safe_summary_prompt.strip() != "":
883
+ prompt_to_use = safe_summary_prompt
884
+ else:
885
+ prompt_to_use = self.session.prompt + "\n" + SUMMARY_REQUEST_PROMPT
764
886
 
887
+ summary = self.model.chat_until_success(prompt_to_use) # type: ignore
888
+ # 防御: 可能返回空响应(None或空字符串),统一为空字符串并告警
889
+ if not summary:
890
+ try:
891
+ PrettyOutput.print("总结模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
892
+ except Exception:
893
+ pass
894
+ summary = ""
765
895
  return summary
766
896
  except Exception:
767
897
  PrettyOutput.print("总结对话历史失败", OutputType.ERROR)
@@ -820,6 +950,8 @@ class Agent:
820
950
  self._setup_system_prompt()
821
951
  # 重置会话
822
952
  self.session.clear_history()
953
+ # 重置 addon_prompt 跳过轮数计数器
954
+ self._addon_prompt_skip_rounds = 0
823
955
  # 广播清理历史后的事件
824
956
  try:
825
957
  self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
@@ -836,6 +968,8 @@ class Agent:
836
968
  except Exception:
837
969
  pass
838
970
  result = self.file_methodology_manager.handle_history_with_file_upload()
971
+ # 重置 addon_prompt 跳过轮数计数器
972
+ self._addon_prompt_skip_rounds = 0
839
973
  # 广播清理历史后的事件
840
974
  try:
841
975
  self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
@@ -882,13 +1016,17 @@ class Agent:
882
1016
 
883
1017
  if self.need_summary:
884
1018
 
885
- self.session.prompt = self.summary_prompt
1019
+ # 确保总结提示词非空:若为None或仅空白,则回退到默认提示词
1020
+ safe_summary_prompt = self.summary_prompt or ""
1021
+ if isinstance(safe_summary_prompt, str) and safe_summary_prompt.strip() == "":
1022
+ safe_summary_prompt = DEFAULT_SUMMARY_PROMPT
1023
+ # 注意:不要写回 session.prompt,避免 BEFORE_SUMMARY 事件回调修改/清空后导致使用空prompt
886
1024
  # 广播将要生成总结事件
887
1025
  try:
888
1026
  self.event_bus.emit(
889
1027
  BEFORE_SUMMARY,
890
1028
  agent=self,
891
- prompt=self.session.prompt,
1029
+ prompt=safe_summary_prompt,
892
1030
  auto_completed=auto_completed,
893
1031
  need_summary=self.need_summary,
894
1032
  )
@@ -897,7 +1035,15 @@ class Agent:
897
1035
 
898
1036
  if not self.model:
899
1037
  raise RuntimeError("Model not initialized")
900
- ret = self.model.chat_until_success(self.session.prompt) # type: ignore
1038
+ # 直接使用本地变量,避免受事件回调影响
1039
+ ret = self.model.chat_until_success(safe_summary_prompt) # type: ignore
1040
+ # 防御: 总结阶段模型可能返回空响应(None或空字符串),统一为空字符串并告警
1041
+ if not ret:
1042
+ try:
1043
+ PrettyOutput.print("总结阶段模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
1044
+ except Exception:
1045
+ pass
1046
+ ret = ""
901
1047
  result = ret
902
1048
 
903
1049
  # 广播完成总结事件
@@ -957,7 +1103,6 @@ class Agent:
957
1103
  {complete_prompt}
958
1104
  如果没有完成,请进行下一步操作:
959
1105
  - 仅包含一个操作
960
- - 不要询问用户是否继续,直接继续执行直至完成
961
1106
  - 如果信息不明确,请请求用户补充
962
1107
  - 如果执行过程中连续失败5次,请使用ask_user询问用户操作
963
1108
  - 操作列表:{action_handlers}{memory_prompts}
@@ -997,6 +1142,13 @@ class Agent:
997
1142
  )
998
1143
  except Exception:
999
1144
  pass
1145
+ # 如启用规划模式,先判断是否需要拆分并调度子任务
1146
+ if self.plan:
1147
+ try:
1148
+ self._maybe_plan_and_dispatch(self.session.prompt)
1149
+ except Exception:
1150
+ # 防御式处理,规划失败不影响主流程
1151
+ pass
1000
1152
  return self._main_loop()
1001
1153
  except Exception as e:
1002
1154
  PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
@@ -1041,7 +1193,7 @@ class Agent:
1041
1193
  return self._complete_task(auto_completed=False)
1042
1194
 
1043
1195
  if any(handler.can_handle(current_response) for handler in self.output_handler):
1044
- if self.user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
1196
+ if self.confirm_callback("检测到有工具调用,是否继续处理工具调用?", True):
1045
1197
  self.session.prompt = join_prompts([
1046
1198
  f"被用户中断,用户补充信息为:{user_input}",
1047
1199
  "用户同意继续工具调用。"
@@ -1104,6 +1256,56 @@ class Agent:
1104
1256
  temp_model.set_system_prompt(system_prompt)
1105
1257
  return temp_model
1106
1258
 
1259
+ def _build_child_agent_params(self, name: str, description: str) -> Dict[str, Any]:
1260
+ """构建子Agent参数,尽量继承父Agent配置,并确保子Agent非交互自动完成。"""
1261
+ use_tools_param: Optional[List[str]] = None
1262
+ try:
1263
+ tr = self.get_tool_registry()
1264
+ if isinstance(tr, ToolRegistry):
1265
+ selected_tools = tr.get_all_tools()
1266
+ use_tools_param = [t["name"] for t in selected_tools]
1267
+ except Exception:
1268
+ use_tools_param = None
1269
+
1270
+ return {
1271
+ "system_prompt": origin_agent_system_prompt,
1272
+ "name": name,
1273
+ "description": description,
1274
+ "model_group": self.model_group,
1275
+ "summary_prompt": self.summary_prompt,
1276
+ "auto_complete": True,
1277
+ "use_tools": use_tools_param,
1278
+ "execute_tool_confirm": self.execute_tool_confirm,
1279
+ "need_summary": self.need_summary,
1280
+ "auto_summary_rounds": self.auto_summary_rounds,
1281
+ "multiline_inputer": self.multiline_inputer,
1282
+ "use_methodology": self.use_methodology,
1283
+ "use_analysis": self.use_analysis,
1284
+ "force_save_memory": self.force_save_memory,
1285
+ "disable_file_edit": self.disable_file_edit,
1286
+ "files": self.files,
1287
+ "confirm_callback": self.confirm_callback,
1288
+ "non_interactive": True,
1289
+ "in_multi_agent": True,
1290
+ "plan": self.plan, # 继承父Agent的规划开关
1291
+ "plan_depth": self.plan_depth + 1, # 子Agent层数+1
1292
+ "plan_max_depth": self.plan_max_depth, # 继承上限
1293
+ }
1294
+
1295
+ def _maybe_plan_and_dispatch(self, task_text: str) -> None:
1296
+ """委托给 TaskPlanner 执行任务规划与子任务调度,保持向后兼容。"""
1297
+ try:
1298
+ if hasattr(self, "task_planner") and self.task_planner:
1299
+ # 优先使用初始化时注入的规划器
1300
+ self.task_planner.maybe_plan_and_dispatch(task_text) # type: ignore[attr-defined]
1301
+ else:
1302
+ # 防御式回退:临时创建规划器以避免因未初始化导致的崩溃
1303
+ from jarvis.jarvis_agent.task_planner import TaskPlanner
1304
+ TaskPlanner(self, plan_depth=self.plan_depth, plan_max_depth=self.plan_max_depth).maybe_plan_and_dispatch(task_text)
1305
+ except Exception:
1306
+ # 规划失败不影响主流程
1307
+ pass
1308
+
1107
1309
  def _filter_tools_if_needed(self, task: str):
1108
1310
  """如果工具数量超过阈值,使用大模型筛选相关工具"""
1109
1311
  tool_registry = self.get_tool_registry()
@@ -1268,7 +1470,7 @@ class Agent:
1268
1470
  f"并且存在3个以上标签重叠的记忆。\n"
1269
1471
  f"是否立即整理记忆库以优化性能和相关性?"
1270
1472
  )
1271
- if self.user_confirm(prompt, True):
1473
+ if self.confirm_callback(prompt, True):
1272
1474
  PrettyOutput.print(
1273
1475
  f"正在开始整理 '{scope_name}' ({memory_type}) 记忆库...",
1274
1476
  OutputType.INFO,
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Agent管理器模块,负责Agent的初始化和任务执行"""
3
- from typing import Optional
3
+ from typing import Optional, Callable
4
4
 
5
5
  import typer
6
6
 
@@ -11,10 +11,8 @@ from jarvis.jarvis_agent import (
11
11
  get_multiline_input,
12
12
  origin_agent_system_prompt,
13
13
  )
14
- from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
15
- from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
16
14
  from jarvis.jarvis_agent.task_manager import TaskManager
17
- from jarvis.jarvis_tools.registry import ToolRegistry
15
+ from jarvis.jarvis_utils.config import is_non_interactive, is_skip_predefined_tasks
18
16
 
19
17
 
20
18
  class AgentManager:
@@ -27,6 +25,10 @@ class AgentManager:
27
25
  restore_session: bool = False,
28
26
  use_methodology: Optional[bool] = None,
29
27
  use_analysis: Optional[bool] = None,
28
+ multiline_inputer: Optional[Callable[[str], str]] = None,
29
+ confirm_callback: Optional[Callable[[str, bool], bool]] = None,
30
+ non_interactive: Optional[bool] = None,
31
+ plan: Optional[bool] = None,
30
32
  ):
31
33
  self.model_group = model_group
32
34
  self.tool_group = tool_group
@@ -34,6 +36,11 @@ class AgentManager:
34
36
  self.use_methodology = use_methodology
35
37
  self.use_analysis = use_analysis
36
38
  self.agent: Optional[Agent] = None
39
+ # 可选:注入输入与确认回调,用于Web模式等前端替代交互
40
+ self.multiline_inputer = multiline_inputer
41
+ self.confirm_callback = confirm_callback
42
+ self.non_interactive = non_interactive
43
+ self.plan = plan
37
44
 
38
45
  def initialize(self) -> Agent:
39
46
  """初始化Agent"""
@@ -46,11 +53,13 @@ class AgentManager:
46
53
  self.agent = Agent(
47
54
  system_prompt=origin_agent_system_prompt,
48
55
  model_group=self.model_group,
49
- input_handler=[shell_input_handler, builtin_input_handler],
50
- output_handler=[ToolRegistry()], # type: ignore
51
56
  need_summary=False,
52
57
  use_methodology=self.use_methodology,
53
58
  use_analysis=self.use_analysis,
59
+ multiline_inputer=self.multiline_inputer,
60
+ confirm_callback=self.confirm_callback,
61
+ non_interactive=self.non_interactive,
62
+ plan=self.plan,
54
63
  )
55
64
 
56
65
  # 尝试恢复会话
@@ -72,8 +81,8 @@ class AgentManager:
72
81
  self.agent.run(task_content)
73
82
  raise typer.Exit(code=0)
74
83
 
75
- # 处理预定义任务
76
- if self.agent.first:
84
+ # 处理预定义任务(非交互模式下跳过;支持配置跳过加载)
85
+ if not is_non_interactive() and not is_skip_predefined_tasks() and self.agent.first:
77
86
  task_manager = TaskManager()
78
87
  tasks = task_manager.load_tasks()
79
88
  if tasks and (selected_task := task_manager.select_task(tasks)):