jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +458 -152
  3. jarvis/jarvis_agent/agent_manager.py +17 -13
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +329 -0
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +628 -55
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +34 -10
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +105 -9
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +20 -22
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  31. jarvis/jarvis_agent/task_analyzer.py +31 -6
  32. jarvis/jarvis_agent/task_manager.py +11 -27
  33. jarvis/jarvis_agent/tool_executor.py +2 -3
  34. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  35. jarvis/jarvis_agent/utils.py +5 -1
  36. jarvis/jarvis_agent/web_bridge.py +189 -0
  37. jarvis/jarvis_agent/web_output_sink.py +53 -0
  38. jarvis/jarvis_agent/web_server.py +786 -0
  39. jarvis/jarvis_c2rust/__init__.py +26 -0
  40. jarvis/jarvis_c2rust/cli.py +575 -0
  41. jarvis/jarvis_c2rust/collector.py +250 -0
  42. jarvis/jarvis_c2rust/constants.py +26 -0
  43. jarvis/jarvis_c2rust/library_replacer.py +1254 -0
  44. jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
  45. jarvis/jarvis_c2rust/loaders.py +207 -0
  46. jarvis/jarvis_c2rust/models.py +28 -0
  47. jarvis/jarvis_c2rust/optimizer.py +2157 -0
  48. jarvis/jarvis_c2rust/scanner.py +1681 -0
  49. jarvis/jarvis_c2rust/transpiler.py +2983 -0
  50. jarvis/jarvis_c2rust/utils.py +385 -0
  51. jarvis/jarvis_code_agent/build_validation_config.py +132 -0
  52. jarvis/jarvis_code_agent/code_agent.py +1371 -220
  53. jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
  54. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
  60. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
  61. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
  62. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
  63. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
  64. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
  65. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
  66. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
  67. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
  68. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  69. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
  70. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  71. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  72. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  73. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  74. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  75. jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
  76. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
  77. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
  78. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
  79. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  80. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  81. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
  82. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
  83. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  84. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
  85. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  86. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
  87. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
  88. jarvis/jarvis_code_agent/lint.py +501 -8
  89. jarvis/jarvis_code_agent/utils.py +141 -0
  90. jarvis/jarvis_code_analysis/code_review.py +493 -584
  91. jarvis/jarvis_data/config_schema.json +128 -12
  92. jarvis/jarvis_git_squash/main.py +4 -5
  93. jarvis/jarvis_git_utils/git_commiter.py +82 -75
  94. jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
  95. jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
  96. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  97. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  98. jarvis/jarvis_methodology/main.py +32 -48
  99. jarvis/jarvis_multi_agent/__init__.py +287 -55
  100. jarvis/jarvis_multi_agent/main.py +36 -4
  101. jarvis/jarvis_platform/base.py +524 -202
  102. jarvis/jarvis_platform/human.py +7 -8
  103. jarvis/jarvis_platform/kimi.py +30 -36
  104. jarvis/jarvis_platform/openai.py +88 -25
  105. jarvis/jarvis_platform/registry.py +26 -10
  106. jarvis/jarvis_platform/tongyi.py +24 -25
  107. jarvis/jarvis_platform/yuanbao.py +32 -43
  108. jarvis/jarvis_platform_manager/main.py +66 -77
  109. jarvis/jarvis_platform_manager/service.py +8 -13
  110. jarvis/jarvis_rag/cli.py +53 -55
  111. jarvis/jarvis_rag/embedding_manager.py +13 -18
  112. jarvis/jarvis_rag/llm_interface.py +8 -9
  113. jarvis/jarvis_rag/query_rewriter.py +10 -21
  114. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  115. jarvis/jarvis_rag/reranker.py +4 -5
  116. jarvis/jarvis_rag/retriever.py +28 -30
  117. jarvis/jarvis_sec/__init__.py +305 -0
  118. jarvis/jarvis_sec/agents.py +143 -0
  119. jarvis/jarvis_sec/analysis.py +276 -0
  120. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  121. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  122. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  123. jarvis/jarvis_sec/cli.py +139 -0
  124. jarvis/jarvis_sec/clustering.py +1439 -0
  125. jarvis/jarvis_sec/file_manager.py +427 -0
  126. jarvis/jarvis_sec/parsers.py +73 -0
  127. jarvis/jarvis_sec/prompts.py +268 -0
  128. jarvis/jarvis_sec/report.py +336 -0
  129. jarvis/jarvis_sec/review.py +453 -0
  130. jarvis/jarvis_sec/status.py +264 -0
  131. jarvis/jarvis_sec/types.py +20 -0
  132. jarvis/jarvis_sec/utils.py +499 -0
  133. jarvis/jarvis_sec/verification.py +848 -0
  134. jarvis/jarvis_sec/workflow.py +226 -0
  135. jarvis/jarvis_smart_shell/main.py +38 -87
  136. jarvis/jarvis_stats/cli.py +2 -2
  137. jarvis/jarvis_stats/stats.py +8 -8
  138. jarvis/jarvis_stats/storage.py +15 -21
  139. jarvis/jarvis_stats/visualizer.py +1 -1
  140. jarvis/jarvis_tools/clear_memory.py +3 -20
  141. jarvis/jarvis_tools/cli/main.py +21 -23
  142. jarvis/jarvis_tools/edit_file.py +1019 -132
  143. jarvis/jarvis_tools/execute_script.py +83 -25
  144. jarvis/jarvis_tools/file_analyzer.py +6 -9
  145. jarvis/jarvis_tools/generate_new_tool.py +14 -21
  146. jarvis/jarvis_tools/lsp_client.py +1552 -0
  147. jarvis/jarvis_tools/methodology.py +2 -3
  148. jarvis/jarvis_tools/read_code.py +1736 -35
  149. jarvis/jarvis_tools/read_symbols.py +140 -0
  150. jarvis/jarvis_tools/read_webpage.py +12 -13
  151. jarvis/jarvis_tools/registry.py +427 -200
  152. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  153. jarvis/jarvis_tools/rewrite_file.py +72 -158
  154. jarvis/jarvis_tools/save_memory.py +3 -15
  155. jarvis/jarvis_tools/search_web.py +18 -18
  156. jarvis/jarvis_tools/sub_agent.py +36 -43
  157. jarvis/jarvis_tools/sub_code_agent.py +25 -26
  158. jarvis/jarvis_tools/virtual_tty.py +55 -33
  159. jarvis/jarvis_utils/clipboard.py +7 -10
  160. jarvis/jarvis_utils/config.py +232 -45
  161. jarvis/jarvis_utils/embedding.py +8 -5
  162. jarvis/jarvis_utils/fzf.py +8 -8
  163. jarvis/jarvis_utils/git_utils.py +225 -36
  164. jarvis/jarvis_utils/globals.py +3 -3
  165. jarvis/jarvis_utils/http.py +1 -1
  166. jarvis/jarvis_utils/input.py +99 -48
  167. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  168. jarvis/jarvis_utils/methodology.py +52 -48
  169. jarvis/jarvis_utils/utils.py +819 -491
  170. jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
  171. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  172. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
  173. jarvis/jarvis_agent/config.py +0 -92
  174. jarvis/jarvis_agent/edit_file_handler.py +0 -296
  175. jarvis/jarvis_platform/ai8.py +0 -332
  176. jarvis/jarvis_tools/ask_user.py +0 -54
  177. jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
  178. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  179. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  180. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  181. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,6 @@ from jarvis.jarvis_utils.methodology import (
20
20
  _get_methodology_directory,
21
21
  _load_all_methodologies,
22
22
  )
23
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
24
23
 
25
24
  app = typer.Typer(help="方法论管理工具")
26
25
 
@@ -55,12 +54,9 @@ def import_methodology(
55
54
  indent=2,
56
55
  )
57
56
 
58
- PrettyOutput.print(
59
- f"成功导入 {len(import_data)} 个方法论(总计 {len(merged_data)} 个)",
60
- OutputType.SUCCESS,
61
- )
62
- except (json.JSONDecodeError, OSError) as e:
63
- PrettyOutput.print(f"导入失败: {str(e)}", OutputType.ERROR)
57
+ print(f"✅ 成功导入 {len(import_data)} 个方法论(总计 {len(merged_data)} 个)")
58
+ except (ValueError, OSError) as e:
59
+ print(f"❌ 导入失败: {str(e)}")
64
60
  raise typer.Exit(code=1)
65
61
 
66
62
 
@@ -73,12 +69,9 @@ def export_methodology(output_file: str = typer.Argument(..., help="导出文件
73
69
  with open(output_file, "w", encoding="utf-8") as f:
74
70
  json.dump(methodologies, f, ensure_ascii=False, indent=2)
75
71
 
76
- PrettyOutput.print(
77
- f"成功导出 {len(methodologies)} 个方法论到 {output_file}",
78
- OutputType.SUCCESS,
79
- )
72
+ print(f"✅ 成功导出 {len(methodologies)} 个方法论到 {output_file}")
80
73
  except (OSError, TypeError) as e:
81
- PrettyOutput.print(f"导出失败: {str(e)}", OutputType.ERROR)
74
+ print(f"导出失败: {str(e)}")
82
75
  raise typer.Exit(code=1)
83
76
 
84
77
 
@@ -89,16 +82,17 @@ def list_methodologies():
89
82
  methodologies = _load_all_methodologies()
90
83
 
91
84
  if not methodologies:
92
- PrettyOutput.print("没有找到方法论", OutputType.INFO)
85
+ print("ℹ️ 没有找到方法论")
93
86
  return
94
87
 
95
88
  # 先拼接再统一打印,避免在循环中逐条输出造成信息稀疏
96
89
  lines = ["可用方法论:"]
97
90
  for i, (problem_type, _) in enumerate(methodologies.items(), 1):
98
91
  lines.append(f"{i}. {problem_type}")
99
- PrettyOutput.print("\n".join(lines), OutputType.INFO)
100
- except (OSError, json.JSONDecodeError) as e:
101
- PrettyOutput.print(f"列出方法论失败: {str(e)}", OutputType.ERROR)
92
+ joined_lines = '\n'.join(lines)
93
+ print(f"ℹ️ {joined_lines}")
94
+ except (OSError, ValueError) as e:
95
+ print(f"❌ 列出方法论失败: {str(e)}")
102
96
  raise typer.Exit(code=1)
103
97
 
104
98
 
@@ -144,22 +138,20 @@ def extract_methodology(
144
138
  """
145
139
 
146
140
  # 调用大模型平台提取方法论
147
- PrettyOutput.print("正在提取方法论...", OutputType.INFO)
141
+ print("ℹ️ 正在提取方法论...")
148
142
  try:
149
143
  response = platform.chat_until_success(prompt)
150
144
  except Exception as e:
151
- PrettyOutput.print("提取失败", OutputType.ERROR)
152
- PrettyOutput.print(f"提取方法论失败: {str(e)}", OutputType.ERROR)
145
+ print("提取失败")
146
+ print(f"提取方法论失败: {str(e)}")
153
147
  raise typer.Exit(code=1)
154
148
 
155
149
  # 提取YAML部分
156
150
  methodologies_start = response.find("<methodologies>") + len("<methodologies>")
157
151
  methodologies_end = response.find("</methodologies>")
158
152
  if methodologies_start == -1 or methodologies_end == -1:
159
- PrettyOutput.print("响应格式无效", OutputType.ERROR)
160
- PrettyOutput.print(
161
- "大模型未返回有效的<methodologies>格式", OutputType.ERROR
162
- )
153
+ print("响应格式无效")
154
+ print("❌ 大模型未返回有效的<methodologies>格式")
163
155
  raise typer.Exit(code=1)
164
156
 
165
157
  yaml_content = response[methodologies_start:methodologies_end].strip()
@@ -170,14 +162,14 @@ def extract_methodology(
170
162
  item["problem_type"]: item["content"] for item in data
171
163
  }
172
164
  except (yaml.YAMLError, KeyError, TypeError) as e:
173
- PrettyOutput.print("YAML解析失败", OutputType.ERROR)
174
- PrettyOutput.print(f"YAML解析错误: {str(e)}", OutputType.ERROR)
165
+ print("YAML解析失败")
166
+ print(f"YAML解析错误: {str(e)}")
175
167
  raise typer.Exit(code=1)
176
168
 
177
169
  if not extracted_methodologies:
178
- PrettyOutput.print("未提取到有效方法论", OutputType.WARNING)
170
+ print("⚠️ 未提取到有效方法论")
179
171
  return
180
- PrettyOutput.print("提取到有效方法论", OutputType.SUCCESS)
172
+ print("提取到有效方法论")
181
173
 
182
174
  # 加载现有方法论
183
175
  existing_methodologies = _load_all_methodologies()
@@ -199,12 +191,9 @@ def extract_methodology(
199
191
  indent=2,
200
192
  )
201
193
 
202
- PrettyOutput.print(
203
- f"成功从文件提取 {len(extracted_methodologies)} 个方法论(总计 {len(merged_data)} 个)",
204
- OutputType.SUCCESS,
205
- )
194
+ print(f"✅ 成功从文件提取 {len(extracted_methodologies)} 个方法论(总计 {len(merged_data)} 个)")
206
195
  except Exception as e:
207
- PrettyOutput.print(f"提取失败: {str(e)}", OutputType.ERROR)
196
+ print(f"提取失败: {str(e)}")
208
197
  raise typer.Exit(code=1)
209
198
 
210
199
 
@@ -247,22 +236,20 @@ def extract_methodology_from_url(
247
236
  6. 内容字段使用|保留多行格式
248
237
  """
249
238
  # 调用大模型平台提取方法论
250
- PrettyOutput.print("正在从URL提取方法论...", OutputType.INFO)
239
+ print("ℹ️ 正在从URL提取方法论...")
251
240
  try:
252
241
  response = platform.chat_until_success(prompt)
253
242
  except Exception as e:
254
- PrettyOutput.print("提取失败", OutputType.ERROR)
255
- PrettyOutput.print(f"提取方法论失败: {str(e)}", OutputType.ERROR)
243
+ print("提取失败")
244
+ print(f"提取方法论失败: {str(e)}")
256
245
  raise typer.Exit(code=1)
257
246
 
258
247
  # 提取YAML部分
259
248
  methodologies_start = response.find("<methodologies>") + len("<methodologies>")
260
249
  methodologies_end = response.find("</methodologies>")
261
250
  if methodologies_start == -1 or methodologies_end == -1:
262
- PrettyOutput.print("响应格式无效", OutputType.ERROR)
263
- PrettyOutput.print(
264
- "大模型未返回有效的<methodologies>格式", OutputType.ERROR
265
- )
251
+ print("响应格式无效")
252
+ print("❌ 大模型未返回有效的<methodologies>格式")
266
253
  raise typer.Exit(code=1)
267
254
 
268
255
  yaml_content = response[methodologies_start:methodologies_end].strip()
@@ -273,14 +260,14 @@ def extract_methodology_from_url(
273
260
  item["problem_type"]: item["content"] for item in data
274
261
  }
275
262
  except (yaml.YAMLError, KeyError, TypeError) as e:
276
- PrettyOutput.print("YAML解析失败", OutputType.ERROR)
277
- PrettyOutput.print(f"YAML解析错误: {str(e)}", OutputType.ERROR)
263
+ print("YAML解析失败")
264
+ print(f"YAML解析错误: {str(e)}")
278
265
  raise typer.Exit(code=1)
279
266
 
280
267
  if not extracted_methodologies:
281
- PrettyOutput.print("未提取到有效方法论", OutputType.WARNING)
268
+ print("⚠️ 未提取到有效方法论")
282
269
  return
283
- PrettyOutput.print("提取到有效方法论", OutputType.SUCCESS)
270
+ print("提取到有效方法论")
284
271
 
285
272
  # 加载现有方法论
286
273
  existing_methodologies = _load_all_methodologies()
@@ -302,12 +289,9 @@ def extract_methodology_from_url(
302
289
  indent=2,
303
290
  )
304
291
 
305
- PrettyOutput.print(
306
- f"成功从URL提取 {len(extracted_methodologies)} 个方法论(总计 {len(merged_data)} 个)",
307
- OutputType.SUCCESS,
308
- )
292
+ print(f"✅ 成功从URL提取 {len(extracted_methodologies)} 个方法论(总计 {len(merged_data)} 个)")
309
293
  except Exception as e:
310
- PrettyOutput.print(f"从URL提取失败: {str(e)}", OutputType.ERROR)
294
+ print(f"从URL提取失败: {str(e)}")
311
295
  raise typer.Exit(code=1)
312
296
 
313
297
 
@@ -1,25 +1,42 @@
1
1
  # -*- coding: utf-8 -*-
2
+ from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
2
3
  import re
3
4
  from typing import Any, Dict, List, Optional, Tuple, Union
4
5
 
5
- import yaml
6
-
7
6
  from jarvis.jarvis_agent import Agent
8
7
  from jarvis.jarvis_agent.output_handler import OutputHandler
9
8
  from jarvis.jarvis_tools.registry import ToolRegistry
10
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
11
9
  from jarvis.jarvis_utils.tag import ct, ot
12
10
 
13
11
 
14
12
  class MultiAgent(OutputHandler):
15
- def __init__(self, agents_config: List[Dict], main_agent_name: str):
13
+ def __init__(self, agents_config: List[Dict], main_agent_name: str, common_system_prompt: str = ""):
16
14
  self.agents_config = agents_config
17
15
  self.agents_config_map = {c["name"]: c for c in agents_config}
18
16
  self.agents: Dict[str, Agent] = {}
19
17
  self.main_agent_name = main_agent_name
20
18
  self.original_question: Optional[str] = None
19
+ self.common_system_prompt: str = common_system_prompt
21
20
 
22
21
  def prompt(self) -> str:
22
+ _multiline_example_msg = """ {
23
+ "content": |||
24
+ 第一行:直接换行,无需 \\n
25
+ 第二行:包含"双引号",无需转义
26
+ 第三行:包含'单引号',直接写
27
+ 第四行:支持缩进保留
28
+ |||
29
+ }
30
+
31
+ 或使用 ``` 代替 |||:
32
+ {
33
+ "content": ```
34
+ 第一行:直接换行,无需 \\n
35
+ 第二行:包含"双引号",无需转义
36
+ 第三行:包含'单引号',直接写
37
+ 第四行:支持缩进保留
38
+ ```
39
+ }"""
23
40
  return f"""
24
41
  # 多智能体消息发送
25
42
 
@@ -30,22 +47,25 @@ class MultiAgent(OutputHandler):
30
47
  - **明确性原则**:清晰表达意图、需求和期望结果
31
48
  - **上下文保留**:在消息中包含足够的背景信息
32
49
 
33
- ### 消息格式标准
50
+ ### 消息格式标准(Jsonnet)
34
51
  ```
35
52
  {ot("SEND_MESSAGE")}
36
- to: 智能体名称 # 目标智能体名称
37
- content: |2
38
- # 消息主题
39
- ## 背景信息
40
- [提供必要的上下文和背景]
41
- ## 具体需求
42
- [明确表达期望完成的任务]
43
- ## 相关资源
44
- [列出相关文档、数据或工具]
45
- ## 期望结果
46
- [描述期望的输出格式和内容]
47
- ## 下一步计划
48
- [描述下一步的计划和行动]
53
+ {{
54
+ "to": "智能体名称",
55
+ "content": |||
56
+ # 消息主题
57
+ ## 背景信息
58
+ [提供必要的上下文和背景]
59
+ ## 具体需求
60
+ [明确表达期望完成的任务]
61
+ ## 相关资源
62
+ [列出相关文档、数据或工具]
63
+ ## 期望结果
64
+ [描述期望的输出格式和内容]
65
+ ## 下一步计划
66
+ [描述下一步的计划和行动]
67
+ |||
68
+ }}
49
69
  {ct("SEND_MESSAGE")}
50
70
  ```
51
71
 
@@ -53,31 +73,186 @@ content: |2
53
73
 
54
74
  ```
55
75
  {ot("SEND_MESSAGE")}
56
- to: 智能体名称 # 目标智能体名称
57
- content: |2
58
- # 消息主题
59
- ## 任务结果
60
- [任务完成结果,用于反馈]
76
+ {{
77
+ "to": "智能体名称",
78
+ "content": |||
79
+ # 消息主题
80
+ ## 任务结果
81
+ [任务完成结果,用于反馈]
82
+ |||
83
+ }}
61
84
  {ct("SEND_MESSAGE")}
62
85
  ```
63
86
 
87
+ **Jsonnet 格式说明**:
88
+ - 可以使用双引号 "..." 或单引号 '...' 包裹字符串
89
+ - **多行字符串推荐使用 ||| 或 ``` 分隔符包裹**,直接换行无需转义,支持保留缩进
90
+ 示例:
91
+ {_multiline_example_msg}
92
+ - 支持尾随逗号
93
+
64
94
  ## 可用智能体资源
65
95
  {chr(10).join([f"- **{c['name']}**: {c.get('description', '')}" for c in self.agents_config])}
66
96
  """
67
97
 
68
98
  def can_handle(self, response: str) -> bool:
69
- return len(self._extract_send_msg(response)) > 0
99
+ # 只要检测到 SEND_MESSAGE 起始标签即认为可处理,
100
+ # 即便内容有误也由 handle 返回明确错误与修复指导
101
+ return ot("SEND_MESSAGE") in response
70
102
 
71
103
  def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
72
- send_messages = self._extract_send_msg(response)
73
- if len(send_messages) > 1:
104
+ """
105
+ 处理 SEND_MESSAGE。若存在格式/解析/字段/目标等问题,返回明确错误原因与修复指导。
106
+ """
107
+ # 优先使用解析器获取“正确路径”结果
108
+ parsed = self._extract_send_msg(response)
109
+ if len(parsed) == 1:
110
+ msg = parsed[0]
111
+ # 字段校验
112
+ to_val = msg.get("to")
113
+ content_val = msg.get("content")
114
+ missing = []
115
+ if not to_val:
116
+ missing.append("to")
117
+ if content_val is None or (isinstance(content_val, str) and content_val.strip() == ""):
118
+ # 允许空格/空行被视为缺失
119
+ missing.append("content")
120
+ if missing:
121
+ guidance = (
122
+ "SEND_MESSAGE 字段缺失或为空:"
123
+ + ", ".join(missing)
124
+ + "\n修复建议:\n"
125
+ "- 必须包含 to 和 content 字段\n"
126
+ "- to: 目标智能体名称(字符串)\n"
127
+ "- content: 发送内容(字符串)\n"
128
+ "示例:\n"
129
+ f"{ot('SEND_MESSAGE')}\n"
130
+ '{{\n "to": "目标Agent名称",\n "content": "这里填写要发送的消息内容"\n}}\n'
131
+ f"{ct('SEND_MESSAGE')}"
132
+ )
133
+ return False, guidance
134
+ # 类型校验
135
+ if not isinstance(to_val, str):
136
+ return False, "SEND_MESSAGE 字段类型错误:to 必须为字符串。修复建议:将 to 改为字符串,如 to: ChapterPolisher"
137
+ if not isinstance(content_val, str):
138
+ return False, "SEND_MESSAGE 字段类型错误:content 必须为字符串。修复建议:将 content 改为字符串"
139
+ # 目标校验
140
+ if to_val not in self.agents_config_map:
141
+ available = ", ".join(self.agents_config_map.keys())
142
+ return (
143
+ False,
144
+ f"目标智能体不存在:'{to_val}' 不在可用列表中。\n"
145
+ f"可用智能体:[{available}]\n"
146
+ "修复建议:\n"
147
+ "- 将 to 修改为上述可用智能体之一\n"
148
+ "- 或检查配置中是否遗漏了该智能体的定义"
149
+ )
150
+ # 通过校验,交给上层发送
151
+ return True, {"to": to_val, "content": content_val}
152
+ elif len(parsed) > 1:
153
+ return (
154
+ False,
155
+ "检测到多个 SEND_MESSAGE 块。一次只能发送一个消息。\n修复建议:合并消息或分多轮发送,每轮仅保留一个 SEND_MESSAGE 块。"
156
+ )
157
+ # 未成功解析,进行诊断并返回可操作指导
158
+ try:
159
+ normalized = response.replace("\r\n", "\n").replace("\r", "\n")
160
+ except Exception:
161
+ normalized = response
162
+ ot_tag = ot("SEND_MESSAGE")
163
+ ct_tag = ct("SEND_MESSAGE")
164
+ has_open = ot_tag in normalized
165
+ has_close = ct_tag in normalized
166
+ if has_open and not has_close:
167
+ return (
168
+ False,
169
+ f"检测到 {ot_tag} 但缺少结束标签 {ct_tag}。\n"
170
+ "修复建议:在消息末尾补充结束标签,并确认标签各自独占一行。\n"
171
+ "示例:\n"
172
+ f"{ot_tag}\n"
173
+ "to: 目标Agent名称\n"
174
+ "content: |2\n"
175
+ " 这里填写要发送的消息内容\n"
176
+ f"{ct_tag}"
177
+ )
178
+ # 尝试提取原始块并指出 JSON 问题
179
+ import re as _re
180
+ pattern = _re.compile(
181
+ rf"{_re.escape(ot_tag)}[ \t]*\n(.*?)(?:\n)?[ \t]*{_re.escape(ct_tag)}",
182
+ _re.DOTALL,
183
+ )
184
+ blocks = pattern.findall(normalized)
185
+ if not blocks:
186
+ alt_pattern = _re.compile(
187
+ rf"{_re.escape(ot_tag)}[ \t]*(.*?)[ \t]*{_re.escape(ct_tag)}",
188
+ _re.DOTALL,
189
+ )
190
+ blocks = alt_pattern.findall(normalized)
191
+ if not blocks:
192
+ return (
193
+ False,
194
+ "SEND_MESSAGE 格式错误:未能识别完整的消息块。\n"
195
+ "修复建议:确保起止标签在单独行上,且中间内容为合法的 JSON,包含 to 与 content 字段。"
196
+ )
197
+ raw = blocks[0]
198
+ try:
199
+ msg_obj = json_loads(raw)
200
+ if not isinstance(msg_obj, dict):
201
+ return (
202
+ False,
203
+ "SEND_MESSAGE 内容必须为 JSON 对象(键值对)。\n"
204
+ "修复建议:使用 to 与 content 字段构成的对象。\n"
205
+ "示例:\n"
206
+ f"{ot('SEND_MESSAGE')}\n"
207
+ '{{\n "to": "目标Agent名称",\n "content": "这里填写要发送的消息内容"\n}}\n'
208
+ f"{ct('SEND_MESSAGE')}"
209
+ )
210
+ missing_keys = [k for k in ("to", "content") if k not in msg_obj]
211
+ if missing_keys:
212
+ return (
213
+ False,
214
+ "SEND_MESSAGE 缺少必要字段:" + ", ".join(missing_keys) + "\n"
215
+ "修复建议:补充缺失字段。\n"
216
+ "示例:\n"
217
+ f"{ot('SEND_MESSAGE')}\n"
218
+ '{{\n "to": "目标Agent名称",\n "content": "这里填写要发送的消息内容"\n}}\n'
219
+ f"{ct('SEND_MESSAGE')}"
220
+ )
221
+ # 针对值类型的提示(更细)
222
+ if not isinstance(msg_obj.get("to"), str):
223
+ return False, "SEND_MESSAGE 字段类型错误:to 必须为字符串。"
224
+ if not isinstance(msg_obj.get("content"), str):
225
+ return False, "SEND_MESSAGE 字段类型错误:content 必须为字符串。"
226
+ # 若到此仍未返回,说明结构基本正确,但 _extract_send_msg 未命中,给出泛化建议
227
+ return (
228
+ False,
229
+ "SEND_MESSAGE 格式可能存在缩进或空白字符问题,导致未被系统识别。\n"
230
+ "修复建议:\n"
231
+ "- 确保起止标签各占一行\n"
232
+ "- 标签与内容之间保留换行\n"
233
+ "- 使用正确的 Jsonnet 格式\n"
234
+ "示例:\n"
235
+ f"{ot('SEND_MESSAGE')}\n"
236
+ '{{\n "to": "目标Agent名称",\n "content": "这里填写要发送的消息内容"\n}}\n'
237
+ f"{ct('SEND_MESSAGE')}"
238
+ )
239
+ except Exception as e:
74
240
  return (
75
241
  False,
76
- "Send multiple messages, please only send one message at a time.",
242
+ f"SEND_MESSAGE Jsonnet 解析失败:{str(e)}\n"
243
+ "修复建议:\n"
244
+ "- 检查 Jsonnet 格式是否正确(引号、逗号、大括号)\n"
245
+ "- 可以使用双引号 \"...\" 或单引号 '...' 包裹字符串\n"
246
+ "- 对于多行内容,推荐使用 ||| 或 ``` 分隔符包裹,直接换行无需转义,支持保留缩进\n"
247
+ "示例:\n"
248
+ f"{ot('SEND_MESSAGE')}\n"
249
+ '{{\n "to": "目标Agent名称",\n "content": "这里填写要发送的消息内容"\n}}\n'
250
+ f"{ct('SEND_MESSAGE')}\n"
251
+ "或使用多行字符串(推荐使用 ||| 或 ``` 分隔符):\n"
252
+ f"{ot('SEND_MESSAGE')}\n"
253
+ '{{\n "to": "目标Agent名称",\n "content": |||\n多行消息内容\n可以包含换行\n包含"双引号"和\'单引号\'都无需转义\n更清晰易读\n |||\n}}\n'
254
+ f"{ct('SEND_MESSAGE')}"
77
255
  )
78
- if len(send_messages) == 0:
79
- return False, ""
80
- return True, send_messages[0]
81
256
 
82
257
  def name(self) -> str:
83
258
  return "SEND_MESSAGE"
@@ -89,16 +264,38 @@ content: |2
89
264
  Args:
90
265
  content: The content containing send message
91
266
  """
92
- if ot("SEND_MESSAGE") in content and ct("SEND_MESSAGE") not in content:
93
- content += "\n" + ct("SEND_MESSAGE")
94
- data = re.findall(
95
- ot("SEND_MESSAGE") + r"\n(.*?)\n" + ct("SEND_MESSAGE"), content, re.DOTALL
267
+ # Normalize line endings to handle CRLF/CR cases to ensure robust matching
268
+ try:
269
+ normalized = content.replace("\r\n", "\n").replace("\r", "\n")
270
+ except Exception:
271
+ normalized = content
272
+
273
+ ot_tag = ot("SEND_MESSAGE")
274
+ ct_tag = ct("SEND_MESSAGE")
275
+
276
+ # Auto-append closing tag if missing
277
+ if ot_tag in normalized and ct_tag not in normalized:
278
+ normalized += "\n" + ct_tag
279
+
280
+ # Use robust regex with DOTALL; escape tags to avoid regex meta issues
281
+ pattern = re.compile(
282
+ rf"{re.escape(ot_tag)}[ \t]*\n(.*?)(?:\n)?[ \t]*{re.escape(ct_tag)}",
283
+ re.DOTALL,
96
284
  )
285
+ data = pattern.findall(normalized)
286
+ # Fallback: handle cases without explicit newlines around closing tag
287
+ if not data:
288
+ alt_pattern = re.compile(
289
+ rf"{re.escape(ot_tag)}[ \t]*(.*?)[ \t]*{re.escape(ct_tag)}",
290
+ re.DOTALL,
291
+ )
292
+ data = alt_pattern.findall(normalized)
293
+
97
294
  ret = []
98
295
  for item in data:
99
296
  try:
100
- msg = yaml.safe_load(item)
101
- if "to" in msg and "content" in msg:
297
+ msg = json_loads(item)
298
+ if isinstance(msg, dict) and "to" in msg and "content" in msg:
102
299
  ret.append(msg)
103
300
  except Exception:
104
301
  continue
@@ -112,6 +309,20 @@ content: |2
112
309
  return None
113
310
 
114
311
  config = self.agents_config_map[name].copy()
312
+ # 标记为多智能体运行,避免在非交互模式下自动开启 auto_complete
313
+ config.setdefault("in_multi_agent", True)
314
+ # 非主智能体统一禁用自动补全,防止多智能体并行时误触发自动交互
315
+ if name != self.main_agent_name:
316
+ config["auto_complete"] = False
317
+
318
+ # Prepend common system prompt if configured
319
+ common_sp = getattr(self, "common_system_prompt", "")
320
+ if common_sp:
321
+ existing_sp = config.get("system_prompt", "")
322
+ if existing_sp:
323
+ config["system_prompt"] = f"{common_sp}\n\n{existing_sp}"
324
+ else:
325
+ config["system_prompt"] = common_sp
115
326
 
116
327
  if name != self.main_agent_name and self.original_question:
117
328
  system_prompt = config.get("system_prompt", "")
@@ -119,18 +330,7 @@ content: |2
119
330
  f"{system_prompt}\n\n# 原始问题\n{self.original_question}"
120
331
  )
121
332
 
122
- output_handler = config.get("output_handler", [])
123
- if len(output_handler) == 0:
124
- output_handler = [
125
- ToolRegistry(),
126
- self,
127
- ]
128
- else:
129
- if not any(isinstance(h, MultiAgent) for h in output_handler):
130
- output_handler.append(self)
131
- config["output_handler"] = output_handler
132
-
133
- agent = Agent(**config)
333
+ agent = Agent(output_handler=[ToolRegistry(), self], **config)
134
334
  self.agents[name] = agent
135
335
  return agent
136
336
 
@@ -151,12 +351,38 @@ content: |2
151
351
 
152
352
  if not isinstance(msg, Dict):
153
353
  # Should not happen if agent.run() returns str or Dict
154
- PrettyOutput.print(f"未知消息类型: {type(msg)}", OutputType.WARNING)
354
+ print(f"⚠️ 未知消息类型: {type(msg)}")
155
355
  break
156
356
 
357
+ # Generate a brief summary via direct model call to avoid run-loop recursion
358
+ # 如果在配置中显式设置了 summary_on_send=False,则不生成摘要
359
+ sender_config = self.agents_config_map.get(last_agent_name, {})
360
+ summary_on_send = sender_config.get("summary_on_send", True)
361
+ summary_text = ""
362
+ if summary_on_send:
363
+ try:
364
+ # 参照 Agent.generate_summary 的实现思路:基于当前 session.prompt 追加请求提示,直接调用底层模型
365
+ multi_agent_summary_prompt = """
366
+ 请基于当前会话,为即将发送给其他智能体的协作交接写一段摘要,包含:
367
+ - 已完成的主要工作与产出
368
+ - 关键决策及其理由
369
+ - 已知的约束/风险/边界条件
370
+ - 未解决的问题与待澄清点
371
+ - 下一步建议与对目标智能体的具体请求
372
+ 要求:
373
+ - 仅输出纯文本,不包含任何指令或工具调用
374
+ - 使用简洁的要点式表述
375
+ """.strip()
376
+ summary_any: Any = agent.model.chat_until_success( # type: ignore[attr-defined]
377
+ f"{agent.session.prompt}\n{multi_agent_summary_prompt}"
378
+ )
379
+ summary_text = summary_any.strip() if isinstance(summary_any, str) else ""
380
+ except Exception:
381
+ summary_text = ""
157
382
  prompt = f"""
158
383
  Please handle this message:
159
384
  from: {last_agent_name}
385
+ summary_of_sender_work: {summary_text}
160
386
  content: {msg['content']}
161
387
  """
162
388
  to_agent_name = msg.get("to")
@@ -164,9 +390,7 @@ content: {msg['content']}
164
390
  return "消息中未指定 `to` 字段"
165
391
 
166
392
  if to_agent_name not in self.agents_config_map:
167
- PrettyOutput.print(
168
- f"未找到智能体 {to_agent_name},正在重试...", OutputType.WARNING
169
- )
393
+ print(f"⚠️ 未找到智能体 {to_agent_name},正在重试...")
170
394
  agent = self._get_agent(last_agent_name)
171
395
  if not agent:
172
396
  return f"智能体 {last_agent_name} 未找到"
@@ -175,14 +399,22 @@ content: {msg['content']}
175
399
  )
176
400
  continue
177
401
 
178
- PrettyOutput.print(
179
- f"{last_agent_name} 正在向 {to_agent_name} 发送消息...", OutputType.INFO
180
- )
402
+ print(f"ℹ️ {last_agent_name} 正在向 {to_agent_name} 发送消息...")
403
+
404
+ # Keep a reference to the sender before switching to the receiver
405
+ sender_agent = agent
181
406
 
182
407
  agent = self._get_agent(to_agent_name)
183
408
  if not agent:
184
409
  return f"智能体 {to_agent_name} 未找到"
185
410
 
411
+ # Check if the sending agent should be cleared
412
+ sender_config = self.agents_config_map.get(last_agent_name, {})
413
+ if sender_config.get("clear_after_send_message"):
414
+ if sender_agent:
415
+ print(f"ℹ️ 清除智能体 {last_agent_name} 在发送消息后的历史记录...")
416
+ sender_agent.clear_history()
417
+
186
418
  last_agent_name = agent.name
187
419
  msg = agent.run(prompt)
188
420
  return ""