myagent-ai 1.15.89 → 1.15.90
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.
- package/package.json +1 -1
- package/web/api_server.py +155 -7
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -5701,15 +5701,23 @@ class ApiServer:
|
|
|
5701
5701
|
# 加载禁用技能列表
|
|
5702
5702
|
self._load_disabled_skills()
|
|
5703
5703
|
|
|
5704
|
-
#
|
|
5704
|
+
# 恢复被中断的任务:自动重新投递群聊任务
|
|
5705
5705
|
try:
|
|
5706
5706
|
tp = self._get_task_persistence()
|
|
5707
|
-
|
|
5708
|
-
if
|
|
5709
|
-
logger.warning(
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5707
|
+
interrupted = tp.get_all_tasks(status_filter=("running",))
|
|
5708
|
+
if interrupted:
|
|
5709
|
+
logger.warning(f"检测到 {len(interrupted)} 个上次未完成的任务,正在自动恢复...")
|
|
5710
|
+
for task in interrupted:
|
|
5711
|
+
try:
|
|
5712
|
+
await self._retry_interrupted_task(tp, task)
|
|
5713
|
+
except Exception as e:
|
|
5714
|
+
logger.error(f"自动恢复任务失败 ({task.get('task_id', '')}): {e}")
|
|
5715
|
+
# 恢复失败则标记为 failed
|
|
5716
|
+
tp.update_task_status(
|
|
5717
|
+
task.get("task_id", ""), "failed",
|
|
5718
|
+
metadata={"interrupted": True, "interrupt_reason": "自动恢复失败"},
|
|
5719
|
+
last_message=task.get("description", ""),
|
|
5720
|
+
)
|
|
5713
5721
|
# 清理超过 7 天的旧已完成任务
|
|
5714
5722
|
tp.cleanup_old_tasks(days=7)
|
|
5715
5723
|
except Exception as e:
|
|
@@ -5912,6 +5920,146 @@ class ApiServer:
|
|
|
5912
5920
|
logger.info(f"任务已删除: {task_id}")
|
|
5913
5921
|
return web.json_response({"ok": True, "task_id": task_id})
|
|
5914
5922
|
|
|
5923
|
+
async def _retry_interrupted_task(self, tp, task: dict):
|
|
5924
|
+
"""
|
|
5925
|
+
自动恢复被中断的群聊任务。
|
|
5926
|
+
重新投递消息到群聊,让 agent 重新处理。
|
|
5927
|
+
"""
|
|
5928
|
+
task_id = task.get("task_id", "")
|
|
5929
|
+
group_id = task.get("group_id", "")
|
|
5930
|
+
description = task.get("description", "")
|
|
5931
|
+
metadata = task.get("metadata", {})
|
|
5932
|
+
|
|
5933
|
+
if not group_id:
|
|
5934
|
+
logger.info(f"任务 {task_id} 无关联群聊,标记为 failed")
|
|
5935
|
+
tp.update_task_status(task_id, "failed", metadata={"interrupted": True})
|
|
5936
|
+
return
|
|
5937
|
+
|
|
5938
|
+
# 验证群聊仍然存在
|
|
5939
|
+
mgr = self._get_group_manager()
|
|
5940
|
+
group = mgr.get_group(group_id)
|
|
5941
|
+
if not group:
|
|
5942
|
+
logger.warning(f"任务 {task_id} 的群聊 {group_id} 已不存在,标记为 failed")
|
|
5943
|
+
tp.update_task_status(task_id, "failed", metadata={"interrupted": True})
|
|
5944
|
+
return
|
|
5945
|
+
|
|
5946
|
+
active_members = [m for m in group.members if not m.muted]
|
|
5947
|
+
if not active_members:
|
|
5948
|
+
logger.warning(f"任务 {task_id} 的群聊 {group_id} 无活跃成员,标记为 failed")
|
|
5949
|
+
tp.update_task_status(task_id, "failed", metadata={"interrupted": True})
|
|
5950
|
+
return
|
|
5951
|
+
|
|
5952
|
+
logger.info(f"正在恢复任务 {task_id}: 群聊[{group.name}] 描述[{description[:50]}...]")
|
|
5953
|
+
|
|
5954
|
+
# 保存恢复消息到群聊
|
|
5955
|
+
from groups.manager import GroupMessage
|
|
5956
|
+
import asyncio
|
|
5957
|
+
|
|
5958
|
+
recover_msg = GroupMessage(
|
|
5959
|
+
group_id=group_id,
|
|
5960
|
+
sender="user",
|
|
5961
|
+
sender_name="系统(任务恢复)",
|
|
5962
|
+
sender_avatar="🔄",
|
|
5963
|
+
content=f"[自动恢复中断任务]\n{description}",
|
|
5964
|
+
)
|
|
5965
|
+
mgr.add_message(recover_msg)
|
|
5966
|
+
|
|
5967
|
+
# 并行投递到所有成员 agent
|
|
5968
|
+
async def process_member(member):
|
|
5969
|
+
try:
|
|
5970
|
+
agent_path = member.agent_path
|
|
5971
|
+
agent_cfg = self._read_agent_config(agent_path)
|
|
5972
|
+
model_chain = self._build_model_chain(agent_cfg, agent_path)
|
|
5973
|
+
session_id = f"group_{group_id}_{agent_path}"
|
|
5974
|
+
|
|
5975
|
+
_, agent_system_prompt = self._build_agent_chat_context(agent_path, agent_cfg, description)
|
|
5976
|
+
|
|
5977
|
+
# 构建群聊上下文
|
|
5978
|
+
member_lines = []
|
|
5979
|
+
for m in group.members:
|
|
5980
|
+
mc = self._read_agent_config(m.agent_path)
|
|
5981
|
+
m_name = mc.get("name", m.agent_path) if mc else m.agent_path
|
|
5982
|
+
m_desc = mc.get("description", "") if mc else ""
|
|
5983
|
+
role_label = {"owner": "群主", "admin": "管理员"}.get(m.role, "成员")
|
|
5984
|
+
line = f" - {m_name} [{m.agent_path}] ({role_label})"
|
|
5985
|
+
if m_desc:
|
|
5986
|
+
line += f" — {m_desc}"
|
|
5987
|
+
member_lines.append(line)
|
|
5988
|
+
|
|
5989
|
+
my_name = agent_cfg.get("name", agent_path) if agent_cfg else agent_path
|
|
5990
|
+
my_role = {"owner": "群主", "admin": "管理员"}.get(member.role, "成员")
|
|
5991
|
+
my_desc = agent_cfg.get("description", "") if agent_cfg else ""
|
|
5992
|
+
|
|
5993
|
+
group_context = (
|
|
5994
|
+
f"## 群聊上下文\n"
|
|
5995
|
+
f"- 群名称: {group.name}\n"
|
|
5996
|
+
f"- 群ID: {group.id}\n"
|
|
5997
|
+
f"- 群描述: {group.description}\n"
|
|
5998
|
+
f"- 当前发言者: 系统(任务恢复)\n"
|
|
5999
|
+
f"- 你的身份: {my_name} ({my_role})"
|
|
6000
|
+
+ (f" — {my_desc}" if my_desc else "")
|
|
6001
|
+
+ f"\n- 群成员 ({len(group.members)}人):\n"
|
|
6002
|
+
+ "\n".join(member_lines)
|
|
6003
|
+
+ "\n\n注意:你只代表自己发言,回复时使用第一人称。"
|
|
6004
|
+
"如果消息不是跟你相关的,可以简短回复或不回复。"
|
|
6005
|
+
)
|
|
6006
|
+
|
|
6007
|
+
if agent_system_prompt:
|
|
6008
|
+
agent_system_prompt += "\n\n" + group_context
|
|
6009
|
+
else:
|
|
6010
|
+
agent_system_prompt = group_context
|
|
6011
|
+
|
|
6012
|
+
dept_context = self._build_dept_context(group_id, agent_path)
|
|
6013
|
+
if dept_context:
|
|
6014
|
+
agent_system_prompt += "\n\n" + dept_context
|
|
6015
|
+
|
|
6016
|
+
if model_chain and self.core.llm:
|
|
6017
|
+
response = await self._try_model_chain(
|
|
6018
|
+
model_chain, description, session_id,
|
|
6019
|
+
agent_path=agent_path, agent_system_prompt=agent_system_prompt,
|
|
6020
|
+
)
|
|
6021
|
+
else:
|
|
6022
|
+
response = await self.core.process_message(description, session_id)
|
|
6023
|
+
|
|
6024
|
+
avatar = "🤖"
|
|
6025
|
+
display_name = agent_path
|
|
6026
|
+
if agent_cfg:
|
|
6027
|
+
avatar = agent_cfg.get("avatar_emoji", "🤖") or "🤖"
|
|
6028
|
+
display_name = agent_cfg.get("name", agent_path)
|
|
6029
|
+
|
|
6030
|
+
agent_msg = GroupMessage(
|
|
6031
|
+
group_id=group_id,
|
|
6032
|
+
sender="agent",
|
|
6033
|
+
sender_name=display_name,
|
|
6034
|
+
sender_avatar=avatar,
|
|
6035
|
+
content=response,
|
|
6036
|
+
agent_path=agent_path,
|
|
6037
|
+
)
|
|
6038
|
+
mgr.add_message(agent_msg)
|
|
6039
|
+
return {"ok": True, "agent_path": agent_path}
|
|
6040
|
+
except Exception as e:
|
|
6041
|
+
logger.error(f"恢复任务处理失败 ({member.agent_path}): {e}")
|
|
6042
|
+
return {"ok": False, "agent_path": member.agent_path, "error": str(e)}
|
|
6043
|
+
|
|
6044
|
+
retry_tasks = [process_member(m) for m in active_members]
|
|
6045
|
+
retry_results = await asyncio.gather(*retry_tasks, return_exceptions=True)
|
|
6046
|
+
|
|
6047
|
+
final_results = []
|
|
6048
|
+
for r in retry_results:
|
|
6049
|
+
if isinstance(r, Exception):
|
|
6050
|
+
final_results.append({"ok": False, "agent_path": "unknown", "error": str(r)})
|
|
6051
|
+
else:
|
|
6052
|
+
final_results.append(r)
|
|
6053
|
+
|
|
6054
|
+
has_failure = any(not r.get("ok") for r in final_results)
|
|
6055
|
+
tp.update_task_status(
|
|
6056
|
+
task_id,
|
|
6057
|
+
"failed" if has_failure else "completed",
|
|
6058
|
+
metadata={"interrupted": True, "recovered": True},
|
|
6059
|
+
last_message=description[:500],
|
|
6060
|
+
)
|
|
6061
|
+
logger.info(f"任务 {task_id} 恢复完成: {'成功' if not has_failure else '部分失败'}")
|
|
6062
|
+
|
|
5915
6063
|
# ── 部门上下文构建(部长角色注入) ──
|
|
5916
6064
|
|
|
5917
6065
|
def _find_dept_by_group_id(self, tree, group_id):
|