jarvis-ai-assistant 0.1.222__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 (162) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +1143 -245
  3. jarvis/jarvis_agent/agent_manager.py +97 -0
  4. jarvis/jarvis_agent/builtin_input_handler.py +12 -10
  5. jarvis/jarvis_agent/config_editor.py +57 -0
  6. jarvis/jarvis_agent/edit_file_handler.py +392 -99
  7. jarvis/jarvis_agent/event_bus.py +48 -0
  8. jarvis/jarvis_agent/events.py +157 -0
  9. jarvis/jarvis_agent/file_context_handler.py +79 -0
  10. jarvis/jarvis_agent/file_methodology_manager.py +117 -0
  11. jarvis/jarvis_agent/jarvis.py +1117 -147
  12. jarvis/jarvis_agent/main.py +78 -34
  13. jarvis/jarvis_agent/memory_manager.py +195 -0
  14. jarvis/jarvis_agent/methodology_share_manager.py +174 -0
  15. jarvis/jarvis_agent/prompt_manager.py +82 -0
  16. jarvis/jarvis_agent/prompts.py +46 -9
  17. jarvis/jarvis_agent/protocols.py +4 -1
  18. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  19. jarvis/jarvis_agent/run_loop.py +146 -0
  20. jarvis/jarvis_agent/session_manager.py +9 -9
  21. jarvis/jarvis_agent/share_manager.py +228 -0
  22. jarvis/jarvis_agent/shell_input_handler.py +23 -3
  23. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  24. jarvis/jarvis_agent/task_analyzer.py +212 -0
  25. jarvis/jarvis_agent/task_manager.py +154 -0
  26. jarvis/jarvis_agent/task_planner.py +496 -0
  27. jarvis/jarvis_agent/tool_executor.py +8 -4
  28. jarvis/jarvis_agent/tool_share_manager.py +139 -0
  29. jarvis/jarvis_agent/user_interaction.py +42 -0
  30. jarvis/jarvis_agent/utils.py +54 -0
  31. jarvis/jarvis_agent/web_bridge.py +189 -0
  32. jarvis/jarvis_agent/web_output_sink.py +53 -0
  33. jarvis/jarvis_agent/web_server.py +751 -0
  34. jarvis/jarvis_c2rust/__init__.py +26 -0
  35. jarvis/jarvis_c2rust/cli.py +613 -0
  36. jarvis/jarvis_c2rust/collector.py +258 -0
  37. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  38. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  39. jarvis/jarvis_c2rust/optimizer.py +960 -0
  40. jarvis/jarvis_c2rust/scanner.py +1681 -0
  41. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  42. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  43. jarvis/jarvis_code_agent/code_agent.py +1605 -178
  44. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  45. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  46. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  47. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  48. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  61. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  62. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  63. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  64. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  65. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  66. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  68. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  69. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  70. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  71. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  72. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  73. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  74. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  75. jarvis/jarvis_code_agent/lint.py +275 -13
  76. jarvis/jarvis_code_agent/utils.py +142 -0
  77. jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
  78. jarvis/jarvis_code_analysis/code_review.py +583 -548
  79. jarvis/jarvis_data/config_schema.json +339 -28
  80. jarvis/jarvis_git_squash/main.py +22 -13
  81. jarvis/jarvis_git_utils/git_commiter.py +171 -55
  82. jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
  83. jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
  84. jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
  85. jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
  86. jarvis/jarvis_methodology/main.py +48 -63
  87. jarvis/jarvis_multi_agent/__init__.py +302 -43
  88. jarvis/jarvis_multi_agent/main.py +70 -24
  89. jarvis/jarvis_platform/ai8.py +40 -23
  90. jarvis/jarvis_platform/base.py +210 -49
  91. jarvis/jarvis_platform/human.py +11 -1
  92. jarvis/jarvis_platform/kimi.py +82 -76
  93. jarvis/jarvis_platform/openai.py +73 -1
  94. jarvis/jarvis_platform/registry.py +8 -15
  95. jarvis/jarvis_platform/tongyi.py +115 -101
  96. jarvis/jarvis_platform/yuanbao.py +89 -63
  97. jarvis/jarvis_platform_manager/main.py +194 -132
  98. jarvis/jarvis_platform_manager/service.py +122 -86
  99. jarvis/jarvis_rag/cli.py +156 -53
  100. jarvis/jarvis_rag/embedding_manager.py +155 -12
  101. jarvis/jarvis_rag/llm_interface.py +10 -13
  102. jarvis/jarvis_rag/query_rewriter.py +63 -12
  103. jarvis/jarvis_rag/rag_pipeline.py +222 -40
  104. jarvis/jarvis_rag/reranker.py +26 -3
  105. jarvis/jarvis_rag/retriever.py +270 -14
  106. jarvis/jarvis_sec/__init__.py +3605 -0
  107. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  108. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  109. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  110. jarvis/jarvis_sec/cli.py +116 -0
  111. jarvis/jarvis_sec/report.py +257 -0
  112. jarvis/jarvis_sec/status.py +264 -0
  113. jarvis/jarvis_sec/types.py +20 -0
  114. jarvis/jarvis_sec/workflow.py +219 -0
  115. jarvis/jarvis_smart_shell/main.py +405 -137
  116. jarvis/jarvis_stats/__init__.py +13 -0
  117. jarvis/jarvis_stats/cli.py +387 -0
  118. jarvis/jarvis_stats/stats.py +711 -0
  119. jarvis/jarvis_stats/storage.py +612 -0
  120. jarvis/jarvis_stats/visualizer.py +282 -0
  121. jarvis/jarvis_tools/ask_user.py +1 -0
  122. jarvis/jarvis_tools/base.py +18 -2
  123. jarvis/jarvis_tools/clear_memory.py +239 -0
  124. jarvis/jarvis_tools/cli/main.py +220 -144
  125. jarvis/jarvis_tools/execute_script.py +52 -12
  126. jarvis/jarvis_tools/file_analyzer.py +17 -12
  127. jarvis/jarvis_tools/generate_new_tool.py +46 -24
  128. jarvis/jarvis_tools/read_code.py +277 -18
  129. jarvis/jarvis_tools/read_symbols.py +141 -0
  130. jarvis/jarvis_tools/read_webpage.py +86 -13
  131. jarvis/jarvis_tools/registry.py +294 -90
  132. jarvis/jarvis_tools/retrieve_memory.py +227 -0
  133. jarvis/jarvis_tools/save_memory.py +194 -0
  134. jarvis/jarvis_tools/search_web.py +62 -28
  135. jarvis/jarvis_tools/sub_agent.py +205 -0
  136. jarvis/jarvis_tools/sub_code_agent.py +217 -0
  137. jarvis/jarvis_tools/virtual_tty.py +330 -62
  138. jarvis/jarvis_utils/builtin_replace_map.py +4 -5
  139. jarvis/jarvis_utils/clipboard.py +90 -0
  140. jarvis/jarvis_utils/config.py +607 -50
  141. jarvis/jarvis_utils/embedding.py +3 -0
  142. jarvis/jarvis_utils/fzf.py +57 -0
  143. jarvis/jarvis_utils/git_utils.py +251 -29
  144. jarvis/jarvis_utils/globals.py +174 -17
  145. jarvis/jarvis_utils/http.py +58 -79
  146. jarvis/jarvis_utils/input.py +899 -153
  147. jarvis/jarvis_utils/methodology.py +210 -83
  148. jarvis/jarvis_utils/output.py +220 -137
  149. jarvis/jarvis_utils/utils.py +1906 -135
  150. jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
  151. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  152. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
  153. jarvis/jarvis_git_details/main.py +0 -265
  154. jarvis/jarvis_platform/oyi.py +0 -357
  155. jarvis/jarvis_tools/edit_file.py +0 -255
  156. jarvis/jarvis_tools/rewrite_file.py +0 -195
  157. jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
  158. jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
  159. /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
  160. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  161. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  162. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,154 @@
1
+ # -*- coding: utf-8 -*-
2
+ """任务管理模块,负责加载和选择预定义任务"""
3
+ import os
4
+ from typing import Dict
5
+
6
+ import yaml # type: ignore
7
+ from prompt_toolkit import prompt
8
+ from rich.table import Table
9
+ from rich.console import Console
10
+
11
+ from jarvis.jarvis_agent import (
12
+ OutputType,
13
+ PrettyOutput,
14
+ get_multiline_input,
15
+ user_confirm,
16
+ )
17
+ from jarvis.jarvis_agent.utils import join_prompts
18
+ from jarvis.jarvis_utils.config import get_data_dir
19
+ from jarvis.jarvis_utils.fzf import fzf_select
20
+
21
+
22
+ class TaskManager:
23
+ """任务管理器,负责预定义任务的加载和选择"""
24
+
25
+ @staticmethod
26
+ def load_tasks() -> Dict[str, str]:
27
+ """Load tasks from .jarvis files in user home and current directory."""
28
+ tasks: Dict[str, str] = {}
29
+
30
+ # Check pre-command in data directory
31
+ data_dir = get_data_dir()
32
+ pre_command_path = os.path.join(data_dir, "pre-command")
33
+ if os.path.exists(pre_command_path):
34
+ PrettyOutput.print(
35
+ f"从{pre_command_path}加载预定义任务...", OutputType.INFO
36
+ )
37
+ try:
38
+ with open(
39
+ pre_command_path, "r", encoding="utf-8", errors="ignore"
40
+ ) as f:
41
+ user_tasks = yaml.safe_load(f)
42
+ if isinstance(user_tasks, dict):
43
+ for name, desc in user_tasks.items():
44
+ if desc:
45
+ tasks[str(name)] = str(desc)
46
+ PrettyOutput.print(
47
+ f"预定义任务加载完成 {pre_command_path}", OutputType.SUCCESS
48
+ )
49
+ except (yaml.YAMLError, OSError):
50
+ PrettyOutput.print(
51
+ f"预定义任务加载失败 {pre_command_path}", OutputType.ERROR
52
+ )
53
+
54
+ # Check .jarvis/pre-command in current directory
55
+ pre_command_path = ".jarvis/pre-command"
56
+ if os.path.exists(pre_command_path):
57
+ abs_path = os.path.abspath(pre_command_path)
58
+ PrettyOutput.print(f"从{abs_path}加载预定义任务...", OutputType.INFO)
59
+ try:
60
+ with open(
61
+ pre_command_path, "r", encoding="utf-8", errors="ignore"
62
+ ) as f:
63
+ local_tasks = yaml.safe_load(f)
64
+ if isinstance(local_tasks, dict):
65
+ for name, desc in local_tasks.items():
66
+ if desc:
67
+ tasks[str(name)] = str(desc)
68
+ PrettyOutput.print(
69
+ f"预定义任务加载完成 {pre_command_path}", OutputType.SUCCESS
70
+ )
71
+ except (yaml.YAMLError, OSError):
72
+ PrettyOutput.print(
73
+ f"预定义任务加载失败 {pre_command_path}", OutputType.ERROR
74
+ )
75
+
76
+ return tasks
77
+
78
+ @staticmethod
79
+ def select_task(tasks: Dict[str, str]) -> str:
80
+ """Let user select a task from the list or skip. Returns task description if selected."""
81
+ if not tasks:
82
+ return ""
83
+
84
+ task_names = list(tasks.keys())
85
+ # 使用 rich.Table 展示预定义任务
86
+ table = Table(show_header=True, header_style="bold magenta")
87
+ table.add_column("No.", style="cyan", no_wrap=True)
88
+ table.add_column("任务名", style="bold")
89
+ for i, name in enumerate(task_names, 1):
90
+ table.add_row(str(i), name)
91
+ Console().print(table)
92
+ PrettyOutput.print("[0] 跳过预定义任务", OutputType.INFO)
93
+
94
+ # Try fzf selection first (with numbered options and a skip option)
95
+ fzf_list = [f"{0:>3} | 跳过预定义任务"] + [
96
+ f"{i:>3} | {name}" for i, name in enumerate(task_names, 1)
97
+ ]
98
+ selected_str = fzf_select(fzf_list, prompt="选择一个任务编号 (ESC跳过) > ")
99
+ if selected_str:
100
+ try:
101
+ num_part = selected_str.split("|", 1)[0].strip()
102
+ idx = int(num_part)
103
+ if idx == 0:
104
+ return ""
105
+ if 1 <= idx <= len(task_names):
106
+ selected_task = tasks[task_names[idx - 1]]
107
+ PrettyOutput.print(f"将要执行任务:\n {selected_task}", OutputType.INFO)
108
+ # 询问是否需要补充信息
109
+ need_additional = user_confirm("需要为此任务添加补充信息吗?", default=False)
110
+ if need_additional:
111
+ additional_input = get_multiline_input("请输入补充信息:")
112
+ if additional_input:
113
+ selected_task = join_prompts([selected_task, f"补充信息:\n{additional_input}"])
114
+ return selected_task
115
+ except Exception:
116
+ # 如果解析失败,则回退到手动输入
117
+ pass
118
+
119
+ while True:
120
+ try:
121
+ choice_str = prompt(
122
+ "\n请选择一个任务编号(0 或者直接回车跳过预定义任务):"
123
+ ).strip()
124
+ if not choice_str:
125
+ return ""
126
+
127
+ choice = int(choice_str)
128
+ if choice == 0:
129
+ return ""
130
+ if 1 <= choice <= len(task_names):
131
+ selected_task = tasks[task_names[choice - 1]]
132
+ PrettyOutput.print(
133
+ f"将要执行任务:\n {selected_task}", OutputType.INFO
134
+ )
135
+ # 询问是否需要补充信息
136
+ need_additional = user_confirm(
137
+ "需要为此任务添加补充信息吗?", default=False
138
+ )
139
+ if need_additional:
140
+ additional_input = get_multiline_input("请输入补充信息:")
141
+ if additional_input:
142
+ selected_task = join_prompts([
143
+ selected_task,
144
+ f"补充信息:\n{additional_input}"
145
+ ])
146
+ return selected_task
147
+ PrettyOutput.print(
148
+ "无效的选择。请选择列表中的一个号码。", OutputType.WARNING
149
+ )
150
+
151
+ except (KeyboardInterrupt, EOFError):
152
+ return ""
153
+ except ValueError as val_err:
154
+ PrettyOutput.print(f"选择任务失败: {str(val_err)}", OutputType.ERROR)
@@ -0,0 +1,496 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ TaskPlanner: 任务规划与子任务调度器
4
+
5
+ 职责:
6
+ - 判断是否需要拆分任务
7
+ - 解析 <PLAN> YAML 列表
8
+ - 为每个子任务创建子Agent并执行
9
+ - 汇总所有子任务执行结果并写回父Agent上下文(包含 <PLAN>/<SUB_TASK_RESULTS>/<RESULT_SUMMARY>)
10
+ """
11
+
12
+ from typing import Any, List
13
+ import re
14
+
15
+ import yaml # type: ignore
16
+
17
+ from jarvis.jarvis_agent.utils import join_prompts
18
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
19
+
20
+
21
+ class TaskPlanner:
22
+ """将 Agent 的任务规划逻辑封装为独立类,便于维护与复用。"""
23
+
24
+ def __init__(self, agent: Any, plan_depth: int = 0, plan_max_depth: int = 2) -> None:
25
+ """
26
+ 参数:
27
+ agent: 父Agent实例(须提供以下能力)
28
+ - _create_temp_model(system_prompt: str) -> BasePlatform
29
+ - _build_child_agent_params(name: str, description: str) -> dict
30
+ - name, session, plan 等属性
31
+ plan_depth: 当前规划深度(由外部在构造时传入)
32
+ plan_max_depth: 规划最大深度(由外部在构造时传入)
33
+ """
34
+ self.agent = agent
35
+ try:
36
+ self.plan_depth = int(plan_depth)
37
+ except Exception:
38
+ self.plan_depth = 0
39
+ try:
40
+ self.plan_max_depth = int(plan_max_depth)
41
+ except Exception:
42
+ self.plan_max_depth = 2
43
+
44
+ def _print_plan_status(
45
+ self,
46
+ subtasks: List[str],
47
+ current_index: int,
48
+ is_starting: bool = True,
49
+ ) -> None:
50
+ """
51
+ 打印当前计划状态
52
+
53
+ 参数:
54
+ subtasks: 当前计划的所有子任务列表
55
+ current_index: 当前任务索引(从1开始,0表示还未开始)
56
+ is_starting: True表示任务开始,False表示任务完成
57
+ """
58
+ if not subtasks:
59
+ return
60
+
61
+ status_lines = ["📋 当前计划状态:"]
62
+ status_lines.append("─" * 60)
63
+
64
+ for idx, task in enumerate(subtasks, 1):
65
+ if current_index == 0:
66
+ # 全局视图:所有任务都是待执行
67
+ status_lines.append(f"⏳ [{idx}] {task}")
68
+ elif idx < current_index:
69
+ # 已完成的任务
70
+ status_lines.append(f"✅ [{idx}] {task}")
71
+ elif idx == current_index:
72
+ if is_starting:
73
+ # 当前正在执行的任务
74
+ status_lines.append(f"🔄 [{idx}] {task} ← 当前节点")
75
+ else:
76
+ # 刚完成的任务
77
+ status_lines.append(f"✅ [{idx}] {task} ← 刚完成")
78
+ else:
79
+ # 待执行的任务
80
+ status_lines.append(f"⏳ [{idx}] {task}")
81
+
82
+ status_lines.append("─" * 60)
83
+ if current_index == 0:
84
+ status_lines.append(f"总任务数: {len(subtasks)},准备开始执行")
85
+ elif is_starting:
86
+ status_lines.append(f"进度: {current_index - 1}/{len(subtasks)} 已完成,正在执行第 {current_index} 个")
87
+ else:
88
+ status_lines.append(f"进度: {current_index}/{len(subtasks)} 已完成")
89
+
90
+ PrettyOutput.print("\n".join(status_lines), OutputType.INFO)
91
+
92
+ def _evaluate_plan_adjustment(
93
+ self,
94
+ task_text: str,
95
+ original_plan: List[str],
96
+ completed_tasks: List[str],
97
+ completed_results: List[str],
98
+ remaining_tasks: List[str],
99
+ ) -> Any:
100
+ """
101
+ 评估计划是否需要调整
102
+
103
+ 参数:
104
+ task_text: 原始任务描述
105
+ original_plan: 原始完整计划
106
+ completed_tasks: 已完成的子任务列表
107
+ completed_results: 已完成子任务的结果列表
108
+ remaining_tasks: 剩余待执行的子任务列表
109
+
110
+ 返回:
111
+ dict: 包含 need_adjust 和 adjusted_plan 的字典,如果不需要调整则返回 None
112
+ """
113
+ try:
114
+ evaluation_sys = (
115
+ "你是一个任务计划评估助手。请根据已完成子任务的结果,评估剩余计划是否需要调整。\n"
116
+ "当需要调整时,仅按以下结构输出:\n"
117
+ "<PLAN_ADJUSTMENT>\n"
118
+ "need_adjust: true\n"
119
+ "reason: \"调整原因说明\"\n"
120
+ "adjusted_plan:\n"
121
+ " - 调整后的剩余子任务1\n"
122
+ " - 调整后的剩余子任务2\n"
123
+ "</PLAN_ADJUSTMENT>\n"
124
+ "注意:adjusted_plan 必须是有效的 YAML 列表,仅包含字符串项;只能调整当前层级的剩余计划,不能修改已完成的子任务。\n"
125
+ "当不需要调整时,仅输出:\n"
126
+ "<PLAN_ADJUSTMENT>\n"
127
+ "need_adjust: false\n"
128
+ "</PLAN_ADJUSTMENT>\n"
129
+ "禁止输出任何额外解释。"
130
+ )
131
+
132
+ completed_results_text = "\n".join(completed_results) if completed_results else "无"
133
+ remaining_tasks_text = "\n".join(f"- {t}" for t in remaining_tasks) if remaining_tasks else "无"
134
+
135
+ eval_prompt = (
136
+ f"原始任务:\n{task_text}\n\n"
137
+ f"原始完整计划:\n" + "\n".join(f"- {t}" for t in original_plan) + "\n\n"
138
+ "已完成的子任务:\n" + "\n".join(f"- {t}" for t in completed_tasks) + "\n\n"
139
+ f"已完成子任务的结果:\n{completed_results_text}\n\n"
140
+ f"剩余待执行的子任务:\n{remaining_tasks_text}\n\n"
141
+ "请评估剩余计划是否需要调整。如果需要调整,请提供调整后的剩余子任务列表(只能调整剩余部分,不能修改已完成的子任务)。"
142
+ )
143
+
144
+ # 直接使用agent的大模型接口(将系统提示词合并到prompt中)
145
+ full_prompt = f"{evaluation_sys}\n\n{eval_prompt}"
146
+ if hasattr(self.agent, "model") and hasattr(self.agent.model, "chat_until_success"):
147
+ eval_resp = self.agent.model.chat_until_success(full_prompt) # type: ignore
148
+ else:
149
+ # 回退到临时模型
150
+ temp_model = self.agent._create_temp_model(evaluation_sys)
151
+ eval_resp = temp_model.chat_until_success(eval_prompt) # type: ignore
152
+
153
+ if not eval_resp:
154
+ return None
155
+
156
+ text = str(eval_resp).strip()
157
+ # 解析 <PLAN_ADJUSTMENT> 块
158
+ m = re.search(
159
+ r"<\s*PLAN_ADJUSTMENT\s*>\s*(.*?)\s*<\s*/\s*PLAN_ADJUSTMENT\s*>",
160
+ text,
161
+ re.IGNORECASE | re.DOTALL,
162
+ )
163
+ if m:
164
+ block = m.group(1)
165
+ try:
166
+ data = yaml.safe_load(block)
167
+ if isinstance(data, dict):
168
+ need_adjust = data.get("need_adjust", False)
169
+ if need_adjust:
170
+ adjusted_plan = data.get("adjusted_plan", [])
171
+ reason = data.get("reason", "")
172
+ if adjusted_plan and isinstance(adjusted_plan, list):
173
+ # 验证调整后的计划是有效的字符串列表
174
+ valid_plan = []
175
+ for item in adjusted_plan:
176
+ if isinstance(item, str):
177
+ s = item.strip()
178
+ if s:
179
+ valid_plan.append(s)
180
+ if valid_plan:
181
+ PrettyOutput.print(
182
+ f"计划评估:需要调整。原因:{reason}",
183
+ OutputType.INFO
184
+ )
185
+ return {
186
+ "need_adjust": True,
187
+ "reason": reason,
188
+ "adjusted_plan": valid_plan,
189
+ }
190
+ else:
191
+ return {"need_adjust": False}
192
+ except Exception as e:
193
+ PrettyOutput.print(
194
+ f"解析计划调整结果失败: {e}", OutputType.WARNING
195
+ )
196
+ return None
197
+ return None
198
+ except Exception as e:
199
+ PrettyOutput.print(f"评估计划调整失败: {e}", OutputType.WARNING)
200
+ return None
201
+
202
+ def maybe_plan_and_dispatch(self, task_text: str) -> None:
203
+ """
204
+ 当启用 agent.plan 时,调用临时模型评估是否需要拆分任务并执行子任务。
205
+ - 若模型返回 <DONT_NEED/>,则直接返回不做任何修改;
206
+ - 若返回 <SUB_TASK> 块,则解析每行以“- ”开头的子任务,逐个创建子Agent执行;
207
+ - 将子任务与结果以结构化块写回到 agent.session.prompt,随后由主循环继续处理。
208
+ """
209
+ if not getattr(self.agent, "plan", False):
210
+ return
211
+
212
+ # 深度限制检查:当当前规划深度已达到或超过上限时,禁止继续规划
213
+ try:
214
+ current_depth = int(self.plan_depth)
215
+ except Exception:
216
+ current_depth = 0
217
+ try:
218
+ max_depth = int(self.plan_max_depth)
219
+ except Exception:
220
+ max_depth = 2
221
+
222
+ if current_depth >= max_depth:
223
+ PrettyOutput.print(
224
+ f"已达到任务规划最大深度({max_depth}),本层不再进行规划。", OutputType.INFO
225
+ )
226
+ return
227
+
228
+ try:
229
+ PrettyOutput.print("任务规划启动,评估是否需要拆分...", OutputType.INFO)
230
+ planning_sys = (
231
+ "你是一个任务规划助手。请判断是否需要拆分任务。\n"
232
+ "当需要拆分时,仅按以下结构输出:\n"
233
+ "<PLAN>\n- 子任务1\n- 子任务2\n</PLAN>\n"
234
+ "示例:\n"
235
+ "<PLAN>\n- 分析当前任务,提取需要修改的文件列表\n- 修改配置默认值并更新相关 schema\n- 更新文档中对该默认值的描述\n</PLAN>\n"
236
+ "注意:必须拆分为独立可完成的任务;不要将步骤拆分太细,一般不要超过4个步骤;子任务应具备明确的输入与可验证的输出;若超过4步将被判定为拆分失败并重试。\n"
237
+ "要求:<PLAN> 内必须是有效 YAML 列表,仅包含字符串项;禁止输出任何额外解释。\n"
238
+ "当不需要拆分时,仅输出:\n<DONT_NEED/>\n"
239
+ "禁止输出任何额外解释。"
240
+ )
241
+ temp_model = self.agent._create_temp_model(planning_sys)
242
+ plan_prompt = f"任务:\n{task_text}\n\n请严格按要求只输出结构化标签块。"
243
+ plan_resp = temp_model.chat_until_success(plan_prompt) # type: ignore
244
+ if not plan_resp:
245
+ PrettyOutput.print("任务规划模型未返回有效响应。", OutputType.WARNING)
246
+ return
247
+ except Exception as e:
248
+ # 规划失败不影响主流程
249
+ PrettyOutput.print(f"任务规划失败: {e}", OutputType.ERROR)
250
+ return
251
+
252
+ text = str(plan_resp).strip()
253
+ # 不需要拆分
254
+ if re.search(r"<\s*DONT_NEED\s*/\s*>", text, re.IGNORECASE):
255
+ PrettyOutput.print("任务规划完成:无需拆分。", OutputType.SUCCESS)
256
+ return
257
+
258
+ # 解析 <SUB_TASK> 块
259
+ m = re.search(
260
+ r"<\s*PLAN\s*>\s*(.*?)\s*<\s*/\s*PLAN\s*>",
261
+ text,
262
+ re.IGNORECASE | re.DOTALL,
263
+ )
264
+ subtasks: List[str] = []
265
+ if m:
266
+ block = m.group(1)
267
+ try:
268
+ data = yaml.safe_load(block)
269
+ if isinstance(data, list):
270
+ for item in data:
271
+ if isinstance(item, str):
272
+ s = item.strip()
273
+ if s:
274
+ subtasks.append(s)
275
+ else:
276
+ PrettyOutput.print("任务规划提示:无需拆分。", OutputType.INFO)
277
+ except Exception:
278
+ PrettyOutput.print("任务规划提示:无需拆分。", OutputType.INFO)
279
+ else:
280
+ PrettyOutput.print("任务规划提示:无需拆分。", OutputType.INFO)
281
+
282
+ # 若子任务数量超过上限,则视为拆分失败并进行一次重试
283
+ max_steps = 4
284
+ if len(subtasks) > max_steps:
285
+ PrettyOutput.print(
286
+ f"任务拆分产生 {len(subtasks)} 个子任务,超过上限 {max_steps},视为拆分失败,正在重试一次...",
287
+ OutputType.WARNING,
288
+ )
289
+ try:
290
+ retry_prompt = (
291
+ f"{plan_prompt}\n"
292
+ "附加约束:子任务数量不要超过4个,务必合并可合并的步骤;保持每个子任务独立可完成且具有可验证的输出。"
293
+ )
294
+ plan_resp = temp_model.chat_until_success(retry_prompt) # type: ignore
295
+ text = str(plan_resp).strip()
296
+ m = re.search(
297
+ r"<\s*PLAN\s*>\s*(.*?)\s*<\s*/\s*PLAN\s*>",
298
+ text,
299
+ re.IGNORECASE | re.DOTALL,
300
+ )
301
+ subtasks = []
302
+ if m:
303
+ block = m.group(1)
304
+ try:
305
+ data = yaml.safe_load(block)
306
+ if isinstance(data, list):
307
+ for item in data:
308
+ if isinstance(item, str):
309
+ s = item.strip()
310
+ if s:
311
+ subtasks.append(s)
312
+ except Exception:
313
+ pass
314
+ except Exception as e:
315
+ PrettyOutput.print(f"重试任务拆分失败: {e}", OutputType.ERROR)
316
+
317
+ if len(subtasks) > max_steps:
318
+ PrettyOutput.print(
319
+ "重试后仍超过子任务上限,放弃拆分,交由主流程处理。",
320
+ OutputType.WARNING,
321
+ )
322
+ return
323
+
324
+ if not subtasks:
325
+ # 无有效子任务,直接返回
326
+ PrettyOutput.print("任务规划提示:无需拆分。", OutputType.INFO)
327
+ return
328
+
329
+ PrettyOutput.print(f"任务已拆分为 {len(subtasks)} 个子任务:", OutputType.SUCCESS)
330
+ for i, st in enumerate(subtasks, 1):
331
+ PrettyOutput.print(f" {i}. {st}", OutputType.INFO)
332
+
333
+ # 保存初始计划,用于评估时的参考
334
+ original_plan = subtasks.copy()
335
+
336
+ # 打印全局视图(完整初始计划)
337
+ PrettyOutput.print("\n" + "=" * 60, OutputType.INFO)
338
+ PrettyOutput.print("📊 全局计划视图", OutputType.INFO)
339
+ PrettyOutput.print("=" * 60, OutputType.INFO)
340
+ self._print_plan_status(subtasks, 0, is_starting=True) # 0表示还未开始执行
341
+ PrettyOutput.print("=" * 60 + "\n", OutputType.INFO)
342
+
343
+ # 执行子任务
344
+ executed_subtask_block_lines: List[str] = ["<PLAN>"]
345
+ executed_subtask_block_lines += [f"- {t}" for t in subtasks]
346
+ executed_subtask_block_lines.append("</PLAN>")
347
+
348
+ results_lines: List[str] = []
349
+ completed_count = 0 # 已完成的任务数量(用于编号)
350
+ i = 0
351
+ while i < len(subtasks):
352
+ st = subtasks[i]
353
+ completed_count += 1
354
+ i += 1
355
+ try:
356
+ # 打印子任务开始时的计划状态
357
+ self._print_plan_status(subtasks, completed_count, is_starting=True)
358
+
359
+ # 使用已完成数量显示进度,更准确
360
+ remaining_count = len(subtasks) - i + 1
361
+ PrettyOutput.print(
362
+ f"\n🚀 开始执行子任务 {completed_count} (剩余 {remaining_count} 个): {st}",
363
+ OutputType.INFO
364
+ )
365
+ child_kwargs = self.agent._build_child_agent_params(
366
+ name=f"{self.agent.name}-child-{completed_count}",
367
+ description=f"子任务执行器: {st}",
368
+ )
369
+ # 使用父Agent的类创建子Agent,避免循环依赖
370
+ child = self.agent.__class__(**child_kwargs)
371
+ # 构造子任务执行提示,包含父任务与前置子任务结果,避免背景缺失
372
+ subtask_block_text = "\n".join(executed_subtask_block_lines)
373
+ if results_lines:
374
+ prev_results_block = "<PREVIOUS_SUB_TASK_RESULTS>\n" + "\n".join(results_lines) + "\n</PREVIOUS_SUB_TASK_RESULTS>"
375
+ else:
376
+ prev_results_block = "<PREVIOUS_SUB_TASK_RESULTS />"
377
+ child_prompt = join_prompts([
378
+ f"原始任务:\n{task_text}",
379
+ f"子任务规划:\n{subtask_block_text}",
380
+ f"前置子任务执行结果:\n{prev_results_block}",
381
+ f"当前子任务:{st}",
382
+ "请基于原始任务背景与前置结果执行当前子任务,避免重复工作;如需依赖前置产物请直接复用;如需为后续子任务提供数据,请妥善保存(可使用工具保存文件或记忆)。"
383
+ ])
384
+ child_result = child.run(child_prompt)
385
+ result_text = "" if child_result is None else str(child_result)
386
+ # 防止极端长输出导致污染,这里不做截断,交由上层摘要策略控制
387
+ results_lines.append(f"- 子任务{completed_count}: {st}\n 结果: {result_text}")
388
+
389
+ # 打印子任务完成时的计划状态
390
+ self._print_plan_status(subtasks, completed_count, is_starting=False)
391
+
392
+ PrettyOutput.print(
393
+ f"\n✅ 子任务 {completed_count} 执行完成 (剩余 {remaining_count - 1} 个)。",
394
+ OutputType.SUCCESS
395
+ )
396
+
397
+ # 除了最后一步,每步完成后评估计划是否需要调整
398
+ if i < len(subtasks):
399
+ try:
400
+ adjustment = self._evaluate_plan_adjustment(
401
+ task_text=task_text,
402
+ original_plan=original_plan,
403
+ completed_tasks=subtasks[:i],
404
+ completed_results=results_lines,
405
+ remaining_tasks=subtasks[i:],
406
+ )
407
+ if adjustment and adjustment.get("need_adjust", False):
408
+ adjusted_plan = adjustment.get("adjusted_plan", [])
409
+ if adjusted_plan and isinstance(adjusted_plan, list):
410
+ # 检查调整后的计划是否超过限制
411
+ max_steps = 4
412
+ total_after_adjust = i + len(adjusted_plan)
413
+ if total_after_adjust > max_steps:
414
+ PrettyOutput.print(
415
+ f"调整后的计划包含 {total_after_adjust} 个子任务,超过上限 {max_steps},拒绝调整",
416
+ OutputType.WARNING
417
+ )
418
+ else:
419
+ # 更新后续子任务列表(保留已完成的部分)
420
+ subtasks = subtasks[:i] + adjusted_plan
421
+ # 更新已执行的子任务块
422
+ executed_subtask_block_lines = ["<PLAN>"]
423
+ executed_subtask_block_lines += [f"- {t}" for t in subtasks]
424
+ executed_subtask_block_lines.append("</PLAN>")
425
+ PrettyOutput.print(
426
+ f"\n🔄 计划已调整,剩余 {len(adjusted_plan)} 个子任务:",
427
+ OutputType.INFO
428
+ )
429
+ for j, adjusted_task in enumerate(adjusted_plan, 1):
430
+ PrettyOutput.print(
431
+ f" {j}. {adjusted_task}", OutputType.INFO
432
+ )
433
+ # 打印调整后的计划状态(当前任务已完成,下一个任务待执行)
434
+ self._print_plan_status(subtasks, completed_count, is_starting=False)
435
+ except Exception as e:
436
+ # 评估失败不影响主流程
437
+ PrettyOutput.print(
438
+ f"计划评估失败: {e},继续执行原计划", OutputType.WARNING
439
+ )
440
+ except Exception as e:
441
+ results_lines.append(f"- 子任务{completed_count}: {st}\n 结果: 执行失败,原因: {e}")
442
+ PrettyOutput.print(
443
+ f"子任务 {completed_count} 执行失败: {e}",
444
+ OutputType.ERROR
445
+ )
446
+
447
+ subtask_block = "\n".join(executed_subtask_block_lines)
448
+ results_block = "<SUB_TASK_RESULTS>\n" + "\n".join(results_lines) + "\n</SUB_TASK_RESULTS>"
449
+
450
+ PrettyOutput.print("所有子任务执行完毕,正在整合结果...", OutputType.INFO)
451
+ # 先对所有子任务结果进行简要自动汇总,便于父Agent继续整合
452
+ summary_block = "<RESULT_SUMMARY>\n无摘要(将直接使用结果详情继续)\n</RESULT_SUMMARY>"
453
+ try:
454
+ summarizing_sys = (
455
+ "你是一个任务结果整合助手。请根据提供的原始任务、子任务清单与子任务执行结果,"
456
+ "生成简明扼要的汇总与关键结论,突出已完成项、遗留风险与下一步建议。"
457
+ "严格仅输出以下结构:\n"
458
+ "<RESULT_SUMMARY>\n"
459
+ "…你的简要汇总…\n"
460
+ "</RESULT_SUMMARY>\n"
461
+ "不要输出其他任何解释。"
462
+ )
463
+ temp_model2 = self.agent._create_temp_model(summarizing_sys)
464
+ sum_prompt = (
465
+ f"原始任务:\n{task_text}\n\n"
466
+ f"子任务规划:\n{subtask_block}\n\n"
467
+ f"子任务执行结果:\n{results_block}\n\n"
468
+ "请按要求仅输出汇总块。"
469
+ )
470
+ sum_resp = temp_model2.chat_until_success(sum_prompt) # type: ignore
471
+ if isinstance(sum_resp, str) and sum_resp.strip():
472
+ s = sum_resp.strip()
473
+ if not re.search(r"<\s*RESULT_SUMMARY\s*>", s, re.IGNORECASE):
474
+ s = f"<RESULT_SUMMARY>\n{s}\n</RESULT_SUMMARY>"
475
+ summary_block = s
476
+ except Exception:
477
+ # 汇总失败不影响主流程,继续使用默认占位
478
+ pass
479
+
480
+ # 合并回父Agent的 prompt,父Agent将基于汇总与详情继续执行
481
+ try:
482
+ self.agent.session.prompt = join_prompts(
483
+ [
484
+ f"原始任务:\n{task_text}",
485
+ f"子任务规划:\n{subtask_block}",
486
+ f"子任务结果汇总:\n{summary_block}",
487
+ f"子任务执行结果:\n{results_block}",
488
+ "请基于上述子任务结果整合并完成最终输出。",
489
+ ]
490
+ )
491
+ except Exception:
492
+ # 回退拼接
493
+ self.agent.session.prompt = (
494
+ f"{task_text}\n\n{subtask_block}\n\n{summary_block}\n\n{results_block}\n\n"
495
+ "请基于上述子任务结果整合并完成最终输出。"
496
+ )
@@ -41,9 +41,13 @@ def execute_tool_call(response: str, agent: "Agent") -> Tuple[bool, Any]:
41
41
  if not agent.execute_tool_confirm or user_confirm(
42
42
  f"需要执行{tool_to_execute.name()}确认执行?", True
43
43
  ):
44
- print(f"🔧 正在执行{tool_to_execute.name()}...")
45
- result = tool_to_execute.handle(response, agent)
46
- print(f"✅ {tool_to_execute.name()}执行完成")
47
- return result
44
+ try:
45
+ print(f"🔧 正在执行{tool_to_execute.name()}...")
46
+ result = tool_to_execute.handle(response, agent)
47
+ print(f"✅ {tool_to_execute.name()}执行完成")
48
+ return result
49
+ except Exception as e:
50
+ PrettyOutput.print(f"工具执行失败: {str(e)}", OutputType.ERROR)
51
+ return False, str(e)
48
52
 
49
53
  return False, ""