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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/web/api_server.py +155 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.15.89",
3
+ "version": "1.15.90",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/api_server.py CHANGED
@@ -5701,15 +5701,23 @@ class ApiServer:
5701
5701
  # 加载禁用技能列表
5702
5702
  self._load_disabled_skills()
5703
5703
 
5704
- # 恢复被中断的任务(将 running 状态的标记为 failed)
5704
+ # 恢复被中断的任务:自动重新投递群聊任务
5705
5705
  try:
5706
5706
  tp = self._get_task_persistence()
5707
- recovered = tp.recover_interrupted_tasks()
5708
- if recovered:
5709
- logger.warning(
5710
- f"检测到 {len(recovered)} 个上次未完成的任务,已标记为失败。"
5711
- f"可在任务记录中查看并重试。"
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):