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
@@ -8,11 +8,11 @@
8
8
  - 列出所有方法论
9
9
  """
10
10
 
11
- import argparse
12
11
  import hashlib
13
12
  import json
14
13
  import os
15
14
 
15
+ import typer
16
16
  import yaml # type: ignore
17
17
 
18
18
  from jarvis.jarvis_platform.registry import PlatformRegistry
@@ -22,8 +22,13 @@ from jarvis.jarvis_utils.methodology import (
22
22
  )
23
23
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
24
24
 
25
+ app = typer.Typer(help="方法论管理工具")
25
26
 
26
- def import_methodology(input_file):
27
+
28
+ @app.command("import")
29
+ def import_methodology(
30
+ input_file: str = typer.Argument(..., help="要导入的方法论文件路径")
31
+ ):
27
32
  """导入方法论文件(合并策略)"""
28
33
  try:
29
34
  # 加载现有方法论
@@ -56,9 +61,11 @@ def import_methodology(input_file):
56
61
  )
57
62
  except (json.JSONDecodeError, OSError) as e:
58
63
  PrettyOutput.print(f"导入失败: {str(e)}", OutputType.ERROR)
64
+ raise typer.Exit(code=1)
59
65
 
60
66
 
61
- def export_methodology(output_file):
67
+ @app.command("export")
68
+ def export_methodology(output_file: str = typer.Argument(..., help="导出文件路径")):
62
69
  """导出当前方法论到单个文件"""
63
70
  try:
64
71
  methodologies = _load_all_methodologies()
@@ -72,8 +79,10 @@ def export_methodology(output_file):
72
79
  )
73
80
  except (OSError, TypeError) as e:
74
81
  PrettyOutput.print(f"导出失败: {str(e)}", OutputType.ERROR)
82
+ raise typer.Exit(code=1)
75
83
 
76
84
 
85
+ @app.command("list")
77
86
  def list_methodologies():
78
87
  """列出所有方法论"""
79
88
  try:
@@ -83,14 +92,20 @@ def list_methodologies():
83
92
  PrettyOutput.print("没有找到方法论", OutputType.INFO)
84
93
  return
85
94
 
86
- PrettyOutput.print("可用方法论:", OutputType.INFO)
95
+ # 先拼接再统一打印,避免在循环中逐条输出造成信息稀疏
96
+ lines = ["可用方法论:"]
87
97
  for i, (problem_type, _) in enumerate(methodologies.items(), 1):
88
- PrettyOutput.print(f"{i}. {problem_type}", OutputType.INFO)
98
+ lines.append(f"{i}. {problem_type}")
99
+ PrettyOutput.print("\n".join(lines), OutputType.INFO)
89
100
  except (OSError, json.JSONDecodeError) as e:
90
101
  PrettyOutput.print(f"列出方法论失败: {str(e)}", OutputType.ERROR)
102
+ raise typer.Exit(code=1)
91
103
 
92
104
 
93
- def extract_methodology(input_file):
105
+ @app.command("extract")
106
+ def extract_methodology(
107
+ input_file: str = typer.Argument(..., help="要提取方法论的文本文件路径")
108
+ ):
94
109
  """从文本文件中提取方法论"""
95
110
  try:
96
111
  # 读取文本文件内容
@@ -129,23 +144,23 @@ def extract_methodology(input_file):
129
144
  """
130
145
 
131
146
  # 调用大模型平台提取方法论
132
- print("📥 正在提取方法论...")
147
+ PrettyOutput.print("正在提取方法论...", OutputType.INFO)
133
148
  try:
134
149
  response = platform.chat_until_success(prompt)
135
150
  except Exception as e:
136
- print("提取失败")
151
+ PrettyOutput.print("提取失败", OutputType.ERROR)
137
152
  PrettyOutput.print(f"提取方法论失败: {str(e)}", OutputType.ERROR)
138
- return
153
+ raise typer.Exit(code=1)
139
154
 
140
155
  # 提取YAML部分
141
156
  methodologies_start = response.find("<methodologies>") + len("<methodologies>")
142
157
  methodologies_end = response.find("</methodologies>")
143
158
  if methodologies_start == -1 or methodologies_end == -1:
144
- print("响应格式无效")
159
+ PrettyOutput.print("响应格式无效", OutputType.ERROR)
145
160
  PrettyOutput.print(
146
161
  "大模型未返回有效的<methodologies>格式", OutputType.ERROR
147
162
  )
148
- return
163
+ raise typer.Exit(code=1)
149
164
 
150
165
  yaml_content = response[methodologies_start:methodologies_end].strip()
151
166
 
@@ -155,14 +170,14 @@ def extract_methodology(input_file):
155
170
  item["problem_type"]: item["content"] for item in data
156
171
  }
157
172
  except (yaml.YAMLError, KeyError, TypeError) as e:
158
- print("YAML解析失败")
173
+ PrettyOutput.print("YAML解析失败", OutputType.ERROR)
159
174
  PrettyOutput.print(f"YAML解析错误: {str(e)}", OutputType.ERROR)
160
- return
175
+ raise typer.Exit(code=1)
161
176
 
162
177
  if not extracted_methodologies:
163
- print("未提取到有效方法论")
178
+ PrettyOutput.print("未提取到有效方法论", OutputType.WARNING)
164
179
  return
165
- print("提取到有效方法论")
180
+ PrettyOutput.print("提取到有效方法论", OutputType.SUCCESS)
166
181
 
167
182
  # 加载现有方法论
168
183
  existing_methodologies = _load_all_methodologies()
@@ -190,9 +205,13 @@ def extract_methodology(input_file):
190
205
  )
191
206
  except Exception as e:
192
207
  PrettyOutput.print(f"提取失败: {str(e)}", OutputType.ERROR)
208
+ raise typer.Exit(code=1)
193
209
 
194
210
 
195
- def extract_methodology_from_url(url):
211
+ @app.command("extract-url")
212
+ def extract_methodology_from_url(
213
+ url: str = typer.Argument(..., help="要提取方法论的URL")
214
+ ):
196
215
  """从URL提取方法论"""
197
216
  try:
198
217
  # 获取平台实例
@@ -228,23 +247,23 @@ def extract_methodology_from_url(url):
228
247
  6. 内容字段使用|保留多行格式
229
248
  """
230
249
  # 调用大模型平台提取方法论
231
- print("📥 正在从URL提取方法论...")
250
+ PrettyOutput.print("正在从URL提取方法论...", OutputType.INFO)
232
251
  try:
233
252
  response = platform.chat_until_success(prompt)
234
253
  except Exception as e:
235
- print("提取失败")
254
+ PrettyOutput.print("提取失败", OutputType.ERROR)
236
255
  PrettyOutput.print(f"提取方法论失败: {str(e)}", OutputType.ERROR)
237
- return
256
+ raise typer.Exit(code=1)
238
257
 
239
258
  # 提取YAML部分
240
259
  methodologies_start = response.find("<methodologies>") + len("<methodologies>")
241
260
  methodologies_end = response.find("</methodologies>")
242
261
  if methodologies_start == -1 or methodologies_end == -1:
243
- print("响应格式无效")
262
+ PrettyOutput.print("响应格式无效", OutputType.ERROR)
244
263
  PrettyOutput.print(
245
264
  "大模型未返回有效的<methodologies>格式", OutputType.ERROR
246
265
  )
247
- return
266
+ raise typer.Exit(code=1)
248
267
 
249
268
  yaml_content = response[methodologies_start:methodologies_end].strip()
250
269
 
@@ -254,14 +273,14 @@ def extract_methodology_from_url(url):
254
273
  item["problem_type"]: item["content"] for item in data
255
274
  }
256
275
  except (yaml.YAMLError, KeyError, TypeError) as e:
257
- print("YAML解析失败")
276
+ PrettyOutput.print("YAML解析失败", OutputType.ERROR)
258
277
  PrettyOutput.print(f"YAML解析错误: {str(e)}", OutputType.ERROR)
259
- return
278
+ raise typer.Exit(code=1)
260
279
 
261
280
  if not extracted_methodologies:
262
- print("未提取到有效方法论")
281
+ PrettyOutput.print("未提取到有效方法论", OutputType.WARNING)
263
282
  return
264
- print("提取到有效方法论")
283
+ PrettyOutput.print("提取到有效方法论", OutputType.SUCCESS)
265
284
 
266
285
  # 加载现有方法论
267
286
  existing_methodologies = _load_all_methodologies()
@@ -289,46 +308,12 @@ def extract_methodology_from_url(url):
289
308
  )
290
309
  except Exception as e:
291
310
  PrettyOutput.print(f"从URL提取失败: {str(e)}", OutputType.ERROR)
311
+ raise typer.Exit(code=1)
292
312
 
293
313
 
294
- def main():
295
- """方法论管理工具主函数"""
296
- parser = argparse.ArgumentParser(description="方法论管理工具")
297
- subparsers = parser.add_subparsers(dest="command", required=True)
298
-
299
- # import命令
300
- import_parser = subparsers.add_parser("import", help="导入方法论文件(合并策略)")
301
- import_parser.add_argument("input_file", type=str, help="要导入的方法论文件路径")
302
-
303
- # export命令
304
- export_parser = subparsers.add_parser("export", help="导出当前方法论到单个文件")
305
- export_parser.add_argument("output_file", type=str, help="导出文件路径")
306
-
307
- # list命令
308
- subparsers.add_parser("list", help="列出所有方法论")
309
-
310
- # extract命令
311
- extract_parser = subparsers.add_parser("extract", help="从文本文件中提取方法论")
312
- extract_parser.add_argument(
313
- "input_file", type=str, help="要提取方法论的文本文件路径"
314
- )
315
-
316
- # extract-url命令
317
- extract_url_parser = subparsers.add_parser("extract-url", help="从URL提取方法论")
318
- extract_url_parser.add_argument("url", type=str, help="要提取方法论的URL")
319
-
320
- args = parser.parse_args()
321
-
322
- if args.command == "import":
323
- import_methodology(args.input_file)
324
- elif args.command == "export":
325
- export_methodology(args.output_file)
326
- elif args.command == "list":
327
- list_methodologies()
328
- elif args.command == "extract":
329
- extract_methodology(args.input_file)
330
- elif args.command == "extract-url":
331
- extract_methodology_from_url(args.url)
314
+ def main() -> None:
315
+ """Application entry point"""
316
+ app()
332
317
 
333
318
 
334
319
  if __name__ == "__main__":
@@ -1,22 +1,26 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import re
3
- from typing import Any, Dict, List, Tuple
3
+ from typing import Any, Dict, List, Optional, Tuple, Union
4
4
 
5
5
  import yaml
6
6
 
7
7
  from jarvis.jarvis_agent import Agent
8
8
  from jarvis.jarvis_agent.output_handler import OutputHandler
9
9
  from jarvis.jarvis_tools.registry import ToolRegistry
10
+ from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
11
+ from jarvis.jarvis_agent.rewrite_file_handler import RewriteFileHandler
10
12
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
11
13
  from jarvis.jarvis_utils.tag import ct, ot
12
14
 
13
15
 
14
16
  class MultiAgent(OutputHandler):
15
- def __init__(self, agents_config: List[Dict], main_agent_name: str):
17
+ def __init__(self, agents_config: List[Dict], main_agent_name: str, common_system_prompt: str = ""):
16
18
  self.agents_config = agents_config
17
- self.agents = {}
18
- self.init_agents()
19
+ self.agents_config_map = {c["name"]: c for c in agents_config}
20
+ self.agents: Dict[str, Agent] = {}
19
21
  self.main_agent_name = main_agent_name
22
+ self.original_question: Optional[str] = None
23
+ self.common_system_prompt: str = common_system_prompt
20
24
 
21
25
  def prompt(self) -> str:
22
26
  return f"""
@@ -65,18 +69,168 @@ content: |2
65
69
  """
66
70
 
67
71
  def can_handle(self, response: str) -> bool:
68
- return len(self._extract_send_msg(response)) > 0
72
+ # 只要检测到 SEND_MESSAGE 起始标签即认为可处理,
73
+ # 即便内容有误也由 handle 返回明确错误与修复指导
74
+ return ot("SEND_MESSAGE") in response
69
75
 
70
76
  def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
71
- send_messages = self._extract_send_msg(response)
72
- if len(send_messages) > 1:
77
+ """
78
+ 处理 SEND_MESSAGE。若存在格式/解析/字段/目标等问题,返回明确错误原因与修复指导。
79
+ """
80
+ # 优先使用解析器获取“正确路径”结果
81
+ parsed = self._extract_send_msg(response)
82
+ if len(parsed) == 1:
83
+ msg = parsed[0]
84
+ # 字段校验
85
+ to_val = msg.get("to")
86
+ content_val = msg.get("content")
87
+ missing = []
88
+ if not to_val:
89
+ missing.append("to")
90
+ if content_val is None or (isinstance(content_val, str) and content_val.strip() == ""):
91
+ # 允许空格/空行被视为缺失
92
+ missing.append("content")
93
+ if missing:
94
+ guidance = (
95
+ "SEND_MESSAGE 字段缺失或为空:"
96
+ + ", ".join(missing)
97
+ + "\n修复建议:\n"
98
+ "- 必须包含 to 和 content 字段\n"
99
+ "- to: 目标智能体名称(字符串)\n"
100
+ "- content: 发送内容,建议使用多行块 |2 保持格式\n"
101
+ "示例:\n"
102
+ f"{ot('SEND_MESSAGE')}\n"
103
+ "to: 目标Agent名称\n"
104
+ "content: |2\n"
105
+ " 这里填写要发送的消息内容\n"
106
+ f"{ct('SEND_MESSAGE')}"
107
+ )
108
+ return False, guidance
109
+ # 类型校验
110
+ if not isinstance(to_val, str):
111
+ return False, "SEND_MESSAGE 字段类型错误:to 必须为字符串。修复建议:将 to 改为字符串,如 to: ChapterPolisher"
112
+ if not isinstance(content_val, str):
113
+ return False, "SEND_MESSAGE 字段类型错误:content 必须为字符串。修复建议:将 content 改为字符串或使用多行块 content: |2"
114
+ # 目标校验
115
+ if to_val not in self.agents_config_map:
116
+ available = ", ".join(self.agents_config_map.keys())
117
+ return (
118
+ False,
119
+ f"目标智能体不存在:'{to_val}' 不在可用列表中。\n"
120
+ f"可用智能体:[{available}]\n"
121
+ "修复建议:\n"
122
+ "- 将 to 修改为上述可用智能体之一\n"
123
+ "- 或检查配置中是否遗漏了该智能体的定义"
124
+ )
125
+ # 通过校验,交给上层发送
126
+ return True, {"to": to_val, "content": content_val}
127
+ elif len(parsed) > 1:
128
+ return (
129
+ False,
130
+ "检测到多个 SEND_MESSAGE 块。一次只能发送一个消息。\n修复建议:合并消息或分多轮发送,每轮仅保留一个 SEND_MESSAGE 块。"
131
+ )
132
+ # 未成功解析,进行诊断并返回可操作指导
133
+ try:
134
+ normalized = response.replace("\r\n", "\n").replace("\r", "\n")
135
+ except Exception:
136
+ normalized = response
137
+ ot_tag = ot("SEND_MESSAGE")
138
+ ct_tag = ct("SEND_MESSAGE")
139
+ has_open = ot_tag in normalized
140
+ has_close = ct_tag in normalized
141
+ if has_open and not has_close:
142
+ return (
143
+ False,
144
+ f"检测到 {ot_tag} 但缺少结束标签 {ct_tag}。\n"
145
+ "修复建议:在消息末尾补充结束标签,并确认标签各自独占一行。\n"
146
+ "示例:\n"
147
+ f"{ot_tag}\n"
148
+ "to: 目标Agent名称\n"
149
+ "content: |2\n"
150
+ " 这里填写要发送的消息内容\n"
151
+ f"{ct_tag}"
152
+ )
153
+ # 尝试提取原始块并指出 YAML 问题
154
+ import re as _re
155
+ pattern = _re.compile(
156
+ rf"{_re.escape(ot_tag)}[ \t]*\n(.*?)(?:\n)?[ \t]*{_re.escape(ct_tag)}",
157
+ _re.DOTALL,
158
+ )
159
+ blocks = pattern.findall(normalized)
160
+ if not blocks:
161
+ alt_pattern = _re.compile(
162
+ rf"{_re.escape(ot_tag)}[ \t]*(.*?)[ \t]*{_re.escape(ct_tag)}",
163
+ _re.DOTALL,
164
+ )
165
+ blocks = alt_pattern.findall(normalized)
166
+ if not blocks:
73
167
  return (
74
168
  False,
75
- f"Send multiple messages, please only send one message at a time.",
169
+ "SEND_MESSAGE 格式错误:未能识别完整的消息块。\n"
170
+ "修复建议:确保起止标签在单独行上,且中间内容为合法的 YAML,包含 to 与 content 字段。"
171
+ )
172
+ raw = blocks[0]
173
+ try:
174
+ msg_obj = yaml.safe_load(raw)
175
+ if not isinstance(msg_obj, dict):
176
+ return (
177
+ False,
178
+ "SEND_MESSAGE 内容必须为 YAML 对象(键值对)。\n"
179
+ "修复建议:使用 to 与 content 字段构成的对象。\n"
180
+ "示例:\n"
181
+ f"{ot('SEND_MESSAGE')}\n"
182
+ "to: 目标Agent名称\n"
183
+ "content: |2\n"
184
+ " 这里填写要发送的消息内容\n"
185
+ f"{ct('SEND_MESSAGE')}"
186
+ )
187
+ missing_keys = [k for k in ("to", "content") if k not in msg_obj]
188
+ if missing_keys:
189
+ return (
190
+ False,
191
+ "SEND_MESSAGE 缺少必要字段:" + ", ".join(missing_keys) + "\n"
192
+ "修复建议:补充缺失字段。\n"
193
+ "示例:\n"
194
+ f"{ot('SEND_MESSAGE')}\n"
195
+ "to: 目标Agent名称\n"
196
+ "content: |2\n"
197
+ " 这里填写要发送的消息内容\n"
198
+ f"{ct('SEND_MESSAGE')}"
199
+ )
200
+ # 针对值类型的提示(更细)
201
+ if not isinstance(msg_obj.get("to"), str):
202
+ return False, "SEND_MESSAGE 字段类型错误:to 必须为字符串。"
203
+ if not isinstance(msg_obj.get("content"), str):
204
+ return False, "SEND_MESSAGE 字段类型错误:content 必须为字符串,建议使用多行块 |2。"
205
+ # 若到此仍未返回,说明结构基本正确,但 _extract_send_msg 未命中,给出泛化建议
206
+ return (
207
+ False,
208
+ "SEND_MESSAGE 格式可能存在缩进或空白字符问题,导致未被系统识别。\n"
209
+ "修复建议:\n"
210
+ "- 确保起止标签各占一行\n"
211
+ "- 标签与内容之间保留换行\n"
212
+ "- 使用 content: |2 并保证 YAML 缩进一致\n"
213
+ "示例:\n"
214
+ f"{ot('SEND_MESSAGE')}\n"
215
+ "to: 目标Agent名称\n"
216
+ "content: |2\n"
217
+ " 这里填写要发送的消息内容\n"
218
+ f"{ct('SEND_MESSAGE')}"
219
+ )
220
+ except Exception as e:
221
+ return (
222
+ False,
223
+ f"SEND_MESSAGE YAML 解析失败:{str(e)}\n"
224
+ "修复建议:\n"
225
+ "- 检查冒号、缩进与引号是否正确\n"
226
+ "- 使用 content: |2 多行块以避免缩进歧义\n"
227
+ "示例:\n"
228
+ f"{ot('SEND_MESSAGE')}\n"
229
+ "to: 目标Agent名称\n"
230
+ "content: |2\n"
231
+ " 这里填写要发送的消息内容\n"
232
+ f"{ct('SEND_MESSAGE')}"
76
233
  )
77
- if len(send_messages) == 0:
78
- return False, ""
79
- return True, send_messages[0]
80
234
 
81
235
  def name(self) -> str:
82
236
  return "SEND_MESSAGE"
@@ -88,56 +242,161 @@ content: |2
88
242
  Args:
89
243
  content: The content containing send message
90
244
  """
91
- data = re.findall(
92
- ot("SEND_MESSAGE") + r"\n(.*?)\n" + ct("SEND_MESSAGE"), content, re.DOTALL
245
+ # Normalize line endings to handle CRLF/CR cases to ensure robust matching
246
+ try:
247
+ normalized = content.replace("\r\n", "\n").replace("\r", "\n")
248
+ except Exception:
249
+ normalized = content
250
+
251
+ ot_tag = ot("SEND_MESSAGE")
252
+ ct_tag = ct("SEND_MESSAGE")
253
+
254
+ # Auto-append closing tag if missing
255
+ if ot_tag in normalized and ct_tag not in normalized:
256
+ normalized += "\n" + ct_tag
257
+
258
+ # Use robust regex with DOTALL; escape tags to avoid regex meta issues
259
+ pattern = re.compile(
260
+ rf"{re.escape(ot_tag)}[ \t]*\n(.*?)(?:\n)?[ \t]*{re.escape(ct_tag)}",
261
+ re.DOTALL,
93
262
  )
263
+ data = pattern.findall(normalized)
264
+ # Fallback: handle cases without explicit newlines around closing tag
265
+ if not data:
266
+ alt_pattern = re.compile(
267
+ rf"{re.escape(ot_tag)}[ \t]*(.*?)[ \t]*{re.escape(ct_tag)}",
268
+ re.DOTALL,
269
+ )
270
+ data = alt_pattern.findall(normalized)
271
+
94
272
  ret = []
95
273
  for item in data:
96
274
  try:
97
275
  msg = yaml.safe_load(item)
98
- if "to" in msg and "content" in msg:
276
+ if isinstance(msg, dict) and "to" in msg and "content" in msg:
99
277
  ret.append(msg)
100
- except Exception as e:
278
+ except Exception:
101
279
  continue
102
280
  return ret
103
281
 
104
- def init_agents(self):
105
- for config in self.agents_config:
106
- output_handler = config.get("output_handler", [])
107
- if len(output_handler) == 0:
108
- output_handler = [
109
- ToolRegistry(),
110
- self,
111
- ]
282
+ def _get_agent(self, name: str) -> Union[Agent, None]:
283
+ if name in self.agents:
284
+ return self.agents[name]
285
+
286
+ if name not in self.agents_config_map:
287
+ return None
288
+
289
+ config = self.agents_config_map[name].copy()
290
+ # 标记为多智能体运行,避免在非交互模式下自动开启 auto_complete
291
+ config.setdefault("in_multi_agent", True)
292
+ # 非主智能体统一禁用自动补全,防止多智能体并行时误触发自动交互
293
+ if name != self.main_agent_name:
294
+ config["auto_complete"] = False
295
+
296
+ # Prepend common system prompt if configured
297
+ common_sp = getattr(self, "common_system_prompt", "")
298
+ if common_sp:
299
+ existing_sp = config.get("system_prompt", "")
300
+ if existing_sp:
301
+ config["system_prompt"] = f"{common_sp}\n\n{existing_sp}"
112
302
  else:
113
- output_handler.append(self)
114
- config["output_handler"] = output_handler
115
- agent = Agent(**config)
116
- self.agents[config["name"]] = agent
303
+ config["system_prompt"] = common_sp
304
+
305
+ if name != self.main_agent_name and self.original_question:
306
+ system_prompt = config.get("system_prompt", "")
307
+ config["system_prompt"] = (
308
+ f"{system_prompt}\n\n# 原始问题\n{self.original_question}"
309
+ )
310
+
311
+ agent = Agent(output_handler=[ToolRegistry(), EditFileHandler(), RewriteFileHandler(), self],**config)
312
+ self.agents[name] = agent
313
+ return agent
117
314
 
118
315
  def run(self, user_input: str) -> str:
119
- last_agent = self.main_agent_name
120
- msg = self.agents[self.main_agent_name].run(user_input)
316
+ self.original_question = user_input
317
+ last_agent_name = self.main_agent_name
318
+
319
+ agent = self._get_agent(self.main_agent_name)
320
+ if not agent:
321
+ # This should not happen if main_agent_name is correctly configured
322
+ return f"主智能体 {self.main_agent_name} 未找到"
323
+
324
+ msg: Any = agent.run(user_input)
325
+
121
326
  while msg:
122
327
  if isinstance(msg, str):
123
328
  return msg
124
- elif isinstance(msg, Dict):
125
- prompt = f"""
329
+
330
+ if not isinstance(msg, Dict):
331
+ # Should not happen if agent.run() returns str or Dict
332
+ PrettyOutput.print(f"未知消息类型: {type(msg)}", OutputType.WARNING)
333
+ break
334
+
335
+ # Generate a brief summary via direct model call to avoid run-loop recursion
336
+ # 如果在配置中显式设置了 summary_on_send=False,则不生成摘要
337
+ sender_config = self.agents_config_map.get(last_agent_name, {}) if hasattr(self, "agents_config_map") else {}
338
+ summary_on_send = sender_config.get("summary_on_send", True)
339
+ summary_text = ""
340
+ if summary_on_send:
341
+ try:
342
+ # 参照 Agent.generate_summary 的实现思路:基于当前 session.prompt 追加请求提示,直接调用底层模型
343
+ multi_agent_summary_prompt = """
344
+ 请基于当前会话,为即将发送给其他智能体的协作交接写一段摘要,包含:
345
+ - 已完成的主要工作与产出
346
+ - 关键决策及其理由
347
+ - 已知的约束/风险/边界条件
348
+ - 未解决的问题与待澄清点
349
+ - 下一步建议与对目标智能体的具体请求
350
+ 要求:
351
+ - 仅输出纯文本,不包含任何指令或工具调用
352
+ - 使用简洁的要点式表述
353
+ """.strip()
354
+ summary_any: Any = agent.model.chat_until_success( # type: ignore[attr-defined]
355
+ f"{agent.session.prompt}\n{multi_agent_summary_prompt}"
356
+ )
357
+ summary_text = summary_any.strip() if isinstance(summary_any, str) else ""
358
+ except Exception:
359
+ summary_text = ""
360
+ prompt = f"""
126
361
  Please handle this message:
127
- from: {last_agent}
362
+ from: {last_agent_name}
363
+ summary_of_sender_work: {summary_text}
128
364
  content: {msg['content']}
129
365
  """
130
- if msg["to"] not in self.agents:
131
- PrettyOutput.print(
132
- f"未找到智能体 {msg['to']},正在重试...", OutputType.WARNING
133
- )
134
- msg = self.agents[last_agent].run(
135
- f"未找到智能体 {msg['to']},可用智能体列表: {self.agents.keys()}"
136
- )
137
- continue
366
+ to_agent_name = msg.get("to")
367
+ if not to_agent_name:
368
+ return "消息中未指定 `to` 字段"
369
+
370
+ if to_agent_name not in self.agents_config_map:
138
371
  PrettyOutput.print(
139
- f"{last_agent} 正在向 {msg['to']} 发送消息...", OutputType.INFO
372
+ f"未找到智能体 {to_agent_name},正在重试...", OutputType.WARNING
373
+ )
374
+ agent = self._get_agent(last_agent_name)
375
+ if not agent:
376
+ return f"智能体 {last_agent_name} 未找到"
377
+ msg = agent.run(
378
+ f"未找到智能体 {to_agent_name},可用智能体列表: {list(self.agents_config_map.keys())}"
140
379
  )
141
- last_agent = self.agents[msg["to"]].name
142
- msg = self.agents[msg["to"]].run(prompt)
380
+ continue
381
+
382
+ PrettyOutput.print(
383
+ f"{last_agent_name} 正在向 {to_agent_name} 发送消息...", OutputType.INFO
384
+ )
385
+
386
+ # Keep a reference to the sender before switching to the receiver
387
+ sender_agent = agent
388
+
389
+ agent = self._get_agent(to_agent_name)
390
+ if not agent:
391
+ return f"智能体 {to_agent_name} 未找到"
392
+
393
+ # Check if the sending agent should be cleared
394
+ sender_config = self.agents_config_map.get(last_agent_name, {})
395
+ if sender_config.get("clear_after_send_message"):
396
+ if sender_agent:
397
+ PrettyOutput.print(f"清除智能体 {last_agent_name} 在发送消息后的历史记录...", OutputType.INFO)
398
+ sender_agent.clear_history()
399
+
400
+ last_agent_name = agent.name
401
+ msg = agent.run(prompt)
143
402
  return ""