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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +289 -87
  3. jarvis/jarvis_agent/agent_manager.py +17 -8
  4. jarvis/jarvis_agent/edit_file_handler.py +374 -86
  5. jarvis/jarvis_agent/event_bus.py +1 -1
  6. jarvis/jarvis_agent/file_context_handler.py +79 -0
  7. jarvis/jarvis_agent/jarvis.py +601 -43
  8. jarvis/jarvis_agent/main.py +32 -2
  9. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  10. jarvis/jarvis_agent/run_loop.py +38 -5
  11. jarvis/jarvis_agent/share_manager.py +8 -1
  12. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  13. jarvis/jarvis_agent/task_analyzer.py +5 -2
  14. jarvis/jarvis_agent/task_planner.py +496 -0
  15. jarvis/jarvis_agent/utils.py +5 -1
  16. jarvis/jarvis_agent/web_bridge.py +189 -0
  17. jarvis/jarvis_agent/web_output_sink.py +53 -0
  18. jarvis/jarvis_agent/web_server.py +751 -0
  19. jarvis/jarvis_c2rust/__init__.py +26 -0
  20. jarvis/jarvis_c2rust/cli.py +613 -0
  21. jarvis/jarvis_c2rust/collector.py +258 -0
  22. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  23. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  24. jarvis/jarvis_c2rust/optimizer.py +960 -0
  25. jarvis/jarvis_c2rust/scanner.py +1681 -0
  26. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  27. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  28. jarvis/jarvis_code_agent/code_agent.py +1171 -94
  29. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  30. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  31. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  32. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  33. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  34. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  35. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  36. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  37. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  38. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  39. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  40. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  41. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  42. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  43. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  44. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  45. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  46. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  47. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  48. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  49. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  50. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  51. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  52. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  53. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  54. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  55. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  56. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  57. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  58. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  59. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  60. jarvis/jarvis_code_agent/lint.py +270 -8
  61. jarvis/jarvis_code_agent/utils.py +142 -0
  62. jarvis/jarvis_code_analysis/code_review.py +483 -569
  63. jarvis/jarvis_data/config_schema.json +97 -8
  64. jarvis/jarvis_git_utils/git_commiter.py +38 -26
  65. jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
  66. jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
  67. jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
  68. jarvis/jarvis_multi_agent/__init__.py +239 -25
  69. jarvis/jarvis_multi_agent/main.py +37 -1
  70. jarvis/jarvis_platform/base.py +103 -51
  71. jarvis/jarvis_platform/openai.py +26 -1
  72. jarvis/jarvis_platform/yuanbao.py +1 -1
  73. jarvis/jarvis_platform_manager/service.py +2 -2
  74. jarvis/jarvis_rag/cli.py +4 -4
  75. jarvis/jarvis_sec/__init__.py +3605 -0
  76. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  77. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  78. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  79. jarvis/jarvis_sec/cli.py +116 -0
  80. jarvis/jarvis_sec/report.py +257 -0
  81. jarvis/jarvis_sec/status.py +264 -0
  82. jarvis/jarvis_sec/types.py +20 -0
  83. jarvis/jarvis_sec/workflow.py +219 -0
  84. jarvis/jarvis_stats/cli.py +1 -1
  85. jarvis/jarvis_stats/stats.py +1 -1
  86. jarvis/jarvis_stats/visualizer.py +1 -1
  87. jarvis/jarvis_tools/cli/main.py +1 -0
  88. jarvis/jarvis_tools/execute_script.py +46 -9
  89. jarvis/jarvis_tools/generate_new_tool.py +3 -1
  90. jarvis/jarvis_tools/read_code.py +275 -12
  91. jarvis/jarvis_tools/read_symbols.py +141 -0
  92. jarvis/jarvis_tools/read_webpage.py +5 -3
  93. jarvis/jarvis_tools/registry.py +73 -35
  94. jarvis/jarvis_tools/search_web.py +15 -11
  95. jarvis/jarvis_tools/sub_agent.py +24 -42
  96. jarvis/jarvis_tools/sub_code_agent.py +14 -13
  97. jarvis/jarvis_tools/virtual_tty.py +1 -1
  98. jarvis/jarvis_utils/config.py +187 -35
  99. jarvis/jarvis_utils/embedding.py +3 -0
  100. jarvis/jarvis_utils/git_utils.py +181 -6
  101. jarvis/jarvis_utils/globals.py +3 -3
  102. jarvis/jarvis_utils/http.py +1 -1
  103. jarvis/jarvis_utils/input.py +78 -2
  104. jarvis/jarvis_utils/methodology.py +25 -19
  105. jarvis/jarvis_utils/utils.py +644 -359
  106. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
  107. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  108. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
  109. jarvis/jarvis_agent/config.py +0 -92
  110. jarvis/jarvis_tools/edit_file.py +0 -179
  111. jarvis/jarvis_tools/rewrite_file.py +0 -191
  112. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  113. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  114. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  115. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@
7
7
  "JARVIS_MCP": {
8
8
  "type": "array",
9
9
  "description": "MCP工具配置列表",
10
+ "default": [],
10
11
  "items": {
11
12
  "type": "object",
12
13
  "oneOf": [
@@ -182,11 +183,42 @@
182
183
  "description": "执行工具前是否需要确认",
183
184
  "default": false
184
185
  },
186
+ "JARVIS_TOOL_FILTER_THRESHOLD": {
187
+ "type": "number",
188
+ "description": "AI工具筛选阈值:当可用工具数量超过此值时触发AI筛选",
189
+ "default": 30
190
+ },
191
+ "JARVIS_PLAN_ENABLED": {
192
+ "type": "boolean",
193
+ "description": "是否默认启用任务规划。当 Agent 初始化时 plan 参数未指定,将从此配置加载。默认 true。",
194
+ "default": true
195
+ },
196
+ "JARVIS_PLAN_MAX_DEPTH": {
197
+ "type": "number",
198
+ "description": "任务规划的最大层数。用于限制 plan 模式的递归拆分深度。仅在启用规划时生效(通过 CLI --plan/--no-plan 控制),默认 2。",
199
+ "default": 2
200
+ },
201
+ "JARVIS_SCRIPT_EXECUTION_TIMEOUT": {
202
+ "type": "number",
203
+ "description": "脚本执行的超时时间(秒),仅在非交互模式下生效。",
204
+ "default": 300
205
+ },
206
+ "JARVIS_AUTO_SUMMARY_ROUNDS": {
207
+ "type": "number",
208
+ "description": "基于对话轮次的自动总结阈值(达到该轮次后自动总结并清理历史)",
209
+ "default": 50
210
+ },
185
211
  "JARVIS_CONFIRM_BEFORE_APPLY_PATCH": {
186
212
  "type": "boolean",
187
213
  "description": "应用补丁前是否需要确认",
188
214
  "default": false
189
215
  },
216
+ "JARVIS_PATCH_FORMAT": {
217
+ "type": "string",
218
+ "enum": ["all", "search", "search_range"],
219
+ "description": "补丁格式处理模式:all 同时支持 SEARCH 与 SEARCH_START/SEARCH_END;search 仅允许精确片段匹配;search_range 仅允许范围匹配。",
220
+ "default": "all"
221
+ },
190
222
  "JARVIS_DATA_PATH": {
191
223
  "type": "string",
192
224
  "description": "Jarvis数据存储目录路径",
@@ -195,7 +227,7 @@
195
227
  "JARVIS_PRETTY_OUTPUT": {
196
228
  "type": "boolean",
197
229
  "description": "是否启用美化输出",
198
- "default": false
230
+ "default": true
199
231
  },
200
232
  "JARVIS_USE_METHODOLOGY": {
201
233
  "type": "boolean",
@@ -257,12 +289,12 @@
257
289
  },
258
290
  "JARVIS_CENTRAL_METHODOLOGY_REPO": {
259
291
  "type": "string",
260
- "description": "中心方法论Git仓库地址,该仓库会自动添加到方法论加载路径中",
292
+ "description": "中心方法论仓库路径或Git仓库地址。支持本地目录(含git子路径):若为本地目录将直接加入方法论加载路径;若为Git URL则会克隆到数据目录后加载。",
261
293
  "default": ""
262
294
  },
263
295
  "JARVIS_CENTRAL_TOOL_REPO": {
264
296
  "type": "string",
265
- "description": "中心工具库Git仓库地址,该仓库会自动克隆到数据目录并加载其中的工具",
297
+ "description": "中心工具仓库路径或Git仓库地址。支持本地目录(含git子路径):若为本地目录将直接加载其中的工具;若为Git URL则会克隆到数据目录并加载。",
266
298
  "default": ""
267
299
  },
268
300
  "JARVIS_PRINT_PROMPT": {
@@ -270,31 +302,71 @@
270
302
  "description": "是否打印提示",
271
303
  "default": false
272
304
  },
305
+ "JARVIS_PRINT_ERROR_TRACEBACK": {
306
+ "type": "boolean",
307
+ "description": "是否在错误输出时打印回溯调用链",
308
+ "default": false
309
+ },
273
310
  "JARVIS_ENABLE_STATIC_ANALYSIS": {
274
311
  "type": "boolean",
275
312
  "description": "是否启用静态代码分析",
276
313
  "default": true
277
314
  },
315
+ "JARVIS_ENABLE_BUILD_VALIDATION": {
316
+ "type": "boolean",
317
+ "description": "是否启用构建验证。在代码编辑后自动验证代码能否成功编译/构建,确保编辑不会破坏项目构建。",
318
+ "default": true
319
+ },
320
+ "JARVIS_BUILD_VALIDATION_TIMEOUT": {
321
+ "type": "number",
322
+ "description": "构建验证的超时时间(秒)。当构建验证执行时间超过此值时,将终止验证并报告超时。",
323
+ "default": 30
324
+ },
325
+ "JARVIS_ENABLE_IMPACT_ANALYSIS": {
326
+ "type": "boolean",
327
+ "description": "是否启用编辑影响范围分析。分析代码编辑的影响范围,识别可能受影响的文件、函数、测试等,帮助评估编辑风险。",
328
+ "default": true
329
+ },
278
330
  "JARVIS_FORCE_SAVE_MEMORY": {
279
331
  "type": "boolean",
280
332
  "description": "是否强制保存记忆",
281
- "default": true
333
+ "default": false
282
334
  },
283
335
  "JARVIS_ENABLE_GIT_JCA_SWITCH": {
284
336
  "type": "boolean",
285
337
  "description": "在初始化环境前检测Git仓库并提示可切换到代码开发模式(jca)",
286
- "default": false
338
+ "default": true
287
339
  },
288
340
  "JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR": {
289
341
  "type": "boolean",
290
342
  "description": "在进入默认通用代理前,列出可用配置(agent/multi_agent/roles)供选择",
291
- "default": false
343
+ "default": true
292
344
  },
293
345
  "JARVIS_IMMEDIATE_ABORT": {
294
346
  "type": "boolean",
295
347
  "description": "是否启用立即中断:在对话迭代中检测到中断信号时立即返回",
296
348
  "default": false
297
349
  },
350
+ "JARVIS_SAVE_SESSION_HISTORY": {
351
+ "type": "boolean",
352
+ "description": "是否保存会话记录",
353
+ "default": false
354
+ },
355
+ "JARVIS_SKIP_PREDEFINED_TASKS": {
356
+ "type": "boolean",
357
+ "description": "是否跳过预定义任务加载(不读取 pre-command 列表)",
358
+ "default": false
359
+ },
360
+ "JARVIS_ADDON_PROMPT_THRESHOLD": {
361
+ "type": "number",
362
+ "description": "附加提示的触发阈值(字符数)。当消息长度超过此值时,会自动添加默认的附加提示",
363
+ "default": 1024
364
+ },
365
+ "JARVIS_ENABLE_INTENT_RECOGNITION": {
366
+ "type": "boolean",
367
+ "description": "是否启用意图识别功能。用于智能上下文推荐中的LLM意图提取和语义分析",
368
+ "default": true
369
+ },
298
370
  "JARVIS_GIT_CHECK_MODE": {
299
371
  "type": "string",
300
372
  "enum": ["strict", "warn"],
@@ -341,7 +413,23 @@
341
413
  "JARVIS_RAG_GROUPS": {
342
414
  "type": "array",
343
415
  "description": "预定义的RAG配置组",
344
- "default": [],
416
+ "default": [
417
+ {
418
+ "text": {
419
+ "embedding_model": "BAAI/bge-m3",
420
+ "rerank_model": "BAAI/bge-reranker-v2-m3",
421
+ "use_bm25": true,
422
+ "use_rerank": true
423
+ }
424
+ },
425
+ {
426
+ "code": {
427
+ "embedding_model": "Qodo/Qodo-Embed-1-1.5B",
428
+ "use_bm25": false,
429
+ "use_rerank": false
430
+ }
431
+ }
432
+ ],
345
433
  "items": {
346
434
  "type": "object",
347
435
  "additionalProperties": {
@@ -421,7 +509,8 @@
421
509
  "required": [
422
510
  "template"
423
511
  ]
424
- }
512
+ },
513
+ "default": {}
425
514
  },
426
515
  "OPENAI_API_KEY": {
427
516
  "type": "string",
@@ -177,30 +177,35 @@ commit信息
177
177
  platform_name = None
178
178
  model_name = None
179
179
 
180
- if (
181
- agent_from_args
182
- and hasattr(agent_from_args, "model")
183
- and getattr(agent_from_args, "model", None)
184
- ):
185
- try:
186
- platform_name = agent_from_args.model.platform_name()
187
- model_name = agent_from_args.model.name()
188
- if not model_group and hasattr(
189
- agent_from_args.model, "model_group"
190
- ):
191
- model_group = agent_from_args.model.model_group
192
- except Exception:
193
- # 安全回退到后续逻辑
194
- platform_name = None
195
- model_name = None
196
-
197
- # 如果未能从agent获取到,再根据 model_group 获取
198
- if not platform_name:
180
+ # 优先根据 model_group 获取(确保配置一致性)
181
+ # 如果 model_group 存在,强制使用它来解析,避免使用 agent.model 中可能不一致的值
182
+ if model_group:
199
183
  platform_name = get_normal_platform_name(model_group)
200
- if not model_name:
201
184
  model_name = get_normal_model_name(model_group)
202
-
203
- # If no explicit parameters, try to get from existing global agent
185
+ else:
186
+ # 如果没有提供 model_group,尝试从传入的 agent 获取
187
+ if (
188
+ agent_from_args
189
+ and hasattr(agent_from_args, "model")
190
+ and getattr(agent_from_args, "model", None)
191
+ ):
192
+ try:
193
+ platform_name = agent_from_args.model.platform_name()
194
+ model_name = agent_from_args.model.name()
195
+ if hasattr(agent_from_args.model, "model_group"):
196
+ model_group = agent_from_args.model.model_group
197
+ except Exception:
198
+ # 安全回退到后续逻辑
199
+ platform_name = None
200
+ model_name = None
201
+
202
+ # 如果仍未获取到,使用配置文件中的默认值(传入 None 会读取默认配置)
203
+ if not platform_name:
204
+ platform_name = get_normal_platform_name(None)
205
+ if not model_name:
206
+ model_name = get_normal_model_name(None)
207
+
208
+ # 最后的回退:尝试从全局 agent 获取(仅当仍未获取到时)
204
209
  if not platform_name:
205
210
  agent = get_agent(current_agent_name)
206
211
  if (
@@ -208,10 +213,17 @@ commit信息
208
213
  and hasattr(agent, "model")
209
214
  and getattr(agent, "model", None)
210
215
  ):
211
- platform_name = agent.model.platform_name()
212
- model_name = agent.model.name()
213
- if not model_group and hasattr(agent.model, "model_group"):
214
- model_group = agent.model.model_group
216
+ try:
217
+ platform_name = agent.model.platform_name()
218
+ model_name = agent.model.name()
219
+ if not model_group and hasattr(agent.model, "model_group"):
220
+ model_group = agent.model.model_group
221
+ except Exception:
222
+ # 如果全局 agent 也无法获取,使用配置文件默认值
223
+ if not platform_name:
224
+ platform_name = get_normal_platform_name(None)
225
+ if not model_name:
226
+ model_name = get_normal_model_name(None)
215
227
 
216
228
  # Create a new platform instance
217
229
  if platform_name:
@@ -5,7 +5,7 @@ import time
5
5
  from typing import Any, Callable, Dict, List, Optional
6
6
  from urllib.parse import parse_qs, urlencode, urljoin
7
7
 
8
- import requests
8
+ import requests # type: ignore[import-untyped]
9
9
 
10
10
  from jarvis.jarvis_mcp import McpClient
11
11
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -568,7 +568,7 @@ class SSEMcpClient(McpClient):
568
568
  if self.sse_response:
569
569
  try:
570
570
  self.sse_response.close()
571
- except:
571
+ except Exception:
572
572
  pass
573
573
 
574
574
  # 关闭HTTP会话
@@ -296,6 +296,6 @@ class StdioMcpClient(McpClient):
296
296
  self._send_notification("notifications/exit", {})
297
297
  # 等待进程结束
298
298
  self.process.wait(timeout=1)
299
- except:
299
+ except Exception:
300
300
  # 如果进程没有正常退出,强制终止
301
301
  self.process.kill()
@@ -203,7 +203,7 @@ tags:
203
203
 
204
204
  # 解析响应
205
205
  import re
206
- import yaml
206
+ import yaml # type: ignore[import-untyped]
207
207
 
208
208
  # 提取 <merged_memory> 标签内的内容
209
209
  yaml_match = re.search(
@@ -7,17 +7,20 @@ import yaml
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
19
  self.agents_config_map = {c["name"]: c for c in agents_config}
18
20
  self.agents: Dict[str, Agent] = {}
19
21
  self.main_agent_name = main_agent_name
20
22
  self.original_question: Optional[str] = None
23
+ self.common_system_prompt: str = common_system_prompt
21
24
 
22
25
  def prompt(self) -> str:
23
26
  return f"""
@@ -66,18 +69,168 @@ content: |2
66
69
  """
67
70
 
68
71
  def can_handle(self, response: str) -> bool:
69
- return len(self._extract_send_msg(response)) > 0
72
+ # 只要检测到 SEND_MESSAGE 起始标签即认为可处理,
73
+ # 即便内容有误也由 handle 返回明确错误与修复指导
74
+ return ot("SEND_MESSAGE") in response
70
75
 
71
76
  def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
72
- send_messages = self._extract_send_msg(response)
73
- 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:
167
+ return (
168
+ False,
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 未命中,给出泛化建议
74
206
  return (
75
207
  False,
76
- "Send multiple messages, please only send one message at a time.",
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')}"
77
233
  )
78
- if len(send_messages) == 0:
79
- return False, ""
80
- return True, send_messages[0]
81
234
 
82
235
  def name(self) -> str:
83
236
  return "SEND_MESSAGE"
@@ -89,16 +242,38 @@ content: |2
89
242
  Args:
90
243
  content: The content containing send message
91
244
  """
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
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,
96
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
+
97
272
  ret = []
98
273
  for item in data:
99
274
  try:
100
275
  msg = yaml.safe_load(item)
101
- if "to" in msg and "content" in msg:
276
+ if isinstance(msg, dict) and "to" in msg and "content" in msg:
102
277
  ret.append(msg)
103
278
  except Exception:
104
279
  continue
@@ -112,6 +287,20 @@ content: |2
112
287
  return None
113
288
 
114
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}"
302
+ else:
303
+ config["system_prompt"] = common_sp
115
304
 
116
305
  if name != self.main_agent_name and self.original_question:
117
306
  system_prompt = config.get("system_prompt", "")
@@ -119,18 +308,7 @@ content: |2
119
308
  f"{system_prompt}\n\n# 原始问题\n{self.original_question}"
120
309
  )
121
310
 
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)
311
+ agent = Agent(output_handler=[ToolRegistry(), EditFileHandler(), RewriteFileHandler(), self],**config)
134
312
  self.agents[name] = agent
135
313
  return agent
136
314
 
@@ -154,9 +332,35 @@ content: |2
154
332
  PrettyOutput.print(f"未知消息类型: {type(msg)}", OutputType.WARNING)
155
333
  break
156
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 = ""
157
360
  prompt = f"""
158
361
  Please handle this message:
159
362
  from: {last_agent_name}
363
+ summary_of_sender_work: {summary_text}
160
364
  content: {msg['content']}
161
365
  """
162
366
  to_agent_name = msg.get("to")
@@ -179,10 +383,20 @@ content: {msg['content']}
179
383
  f"{last_agent_name} 正在向 {to_agent_name} 发送消息...", OutputType.INFO
180
384
  )
181
385
 
386
+ # Keep a reference to the sender before switching to the receiver
387
+ sender_agent = agent
388
+
182
389
  agent = self._get_agent(to_agent_name)
183
390
  if not agent:
184
391
  return f"智能体 {to_agent_name} 未找到"
185
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
+
186
400
  last_agent_name = agent.name
187
401
  msg = agent.run(prompt)
188
402
  return ""