myagent-ai 1.20.4 → 1.20.5

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.
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  import asyncio
9
9
  import json
10
+ import os
10
11
  import re
11
12
  from typing import Any, Callable, Dict, List, Optional
12
13
 
@@ -76,6 +77,18 @@ class MainAgent(BaseAgent):
76
77
  - **发送文件给用户**: 用 `file_send` 工具(参数: file_path=文件路径, description=说明),当你生成或处理了文件需要返回给用户时使用
77
78
  - **播放音频**: 用 `playaudio` 工具(参数: url=音乐链接或file_path=本地文件路径),在聊天中内嵌播放音频(支持QQ音乐、YouTube音乐、本地MP3/WAV等),播放时自动关闭语音合成
78
79
  - **播放视频**: 用 `playvideo` 工具(参数: url=视频链接或file_path=本地文件路径),在聊天中内嵌播放视频(支持抖音、YouTube、B站、本地MP4等),播放时自动关闭语音合成
80
+ - **网页控制**: 用 `web_control` 工具在聊天中打开一个可控制的浏览器面板,可浏览网页、点击元素、填写表单、滚动页面、执行JS、管理Cookie等。
81
+ - 打开: `{"action": "open", "url": "https://example.com"}` — 首次使用会自动创建会话,后续可传 session_id 复用
82
+ - 导航: `{"action": "navigate", "url": "https://...", "session_id": "xxx"}`
83
+ - 获取内容: `{"action": "get_content", "what": "text|html|url|title|links|images|forms|inputs", "session_id": "xxx"}`
84
+ - 点击元素: `{"action": "click", "selector": "CSS选择器", "session_id": "xxx"}`
85
+ - 填写输入: `{"action": "fill", "selector": "CSS选择器", "value": "内容", "session_id": "xxx"}`
86
+ - 滚动页面: `{"action": "scroll", "direction": "up|down|top|bottom", "distance": 300, "session_id": "xxx"}`
87
+ - 执行JS: `{"action": "evaluate", "script": "JavaScript代码", "session_id": "xxx"}`
88
+ - 管理Cookie: `{"action": "set_cookies", "cookies": [{"name":"k","value":"v","domain":"example.com"}], "session_id": "xxx"}` 或 `{"action": "get_cookies", "session_id": "xxx"}`
89
+ - 等待: `{"action": "wait", "time": 1000}` 毫秒 或 `{"action": "wait", "selector": ".result", "timeout": 10}` 秒
90
+ - 关闭: `{"action": "close", "session_id": "xxx"}`
91
+ - 注意: 网页通过服务端代理加载,用户可在面板中手动操作。复杂交互建议先用 get_content 查看页面结构再用 click/fill。
79
92
  - **主动召回记忆**: 用 `recall_memory` 工具(参数: keyword=关键字, time_point=可选时间点如"2025-01", limit=数量默认5),根据关键字和时间搜索历史记忆
80
93
  4. 准备好内容后,最后,再检查输出格式,确保满足以下要求:
81
94
  <output>
@@ -1216,6 +1229,7 @@ class MainAgent(BaseAgent):
1216
1229
  tool_result = await self._execute_v2_tool(
1217
1230
  tool_name, parms, timeout, context, task_id,
1218
1231
  stream_callback=stream_callback,
1232
+ sent_files=_sent_files,
1219
1233
  )
1220
1234
 
1221
1235
  # 发送工具结果事件
@@ -1552,6 +1566,7 @@ class MainAgent(BaseAgent):
1552
1566
  context: AgentContext,
1553
1567
  task_id: str,
1554
1568
  stream_callback: Optional[Callable] = None,
1569
+ sent_files: Optional[List[Dict[str, Any]]] = None,
1555
1570
  ) -> Dict[str, Any]:
1556
1571
  """V2 工具执行"""
1557
1572
  result = {"success": False, "output": "", "error": ""}
@@ -1634,18 +1649,14 @@ class MainAgent(BaseAgent):
1634
1649
  # [v1.16.18] 使用当前作用域的 stream_callback(而非 context._stream_callback)
1635
1650
  _fresult = await _fskill.execute(_fpath, _fdesc, stream_callback=stream_callback)
1636
1651
  result = {"success": True, "output": json.dumps(_fresult, ensure_ascii=False, indent=2), "data": _fresult}
1637
- # [v1.18.5] 追踪发送的文件,用于持久化到会话记忆
1638
- # [v1.18.9] 修复:_sent_files 可能不在作用域内,安全处理
1639
- try:
1640
- if _fresult.get("success") and _fresult.get("file_id"):
1641
- _sent_files.append({
1642
- "id": _fresult["file_id"],
1643
- "name": _fresult.get("name", ""),
1644
- "type": _fresult.get("type", ""),
1645
- "size": _fresult.get("size", 0),
1646
- })
1647
- except NameError:
1648
- pass # _sent_files 不在当前作用域,跳过文件追踪
1652
+ # [v1.18.5→21.1] 追踪发送的文件,用于持久化到会话记忆
1653
+ if sent_files is not None and _fresult.get("success") and _fresult.get("file_id"):
1654
+ sent_files.append({
1655
+ "id": _fresult["file_id"],
1656
+ "name": _fresult.get("name", ""),
1657
+ "type": _fresult.get("type", ""),
1658
+ "size": _fresult.get("size", 0),
1659
+ })
1649
1660
  except Exception as _fse:
1650
1661
  result = {"success": False, "error": f"文件发送失败: {_fse}"}
1651
1662
  logger.warning(f"[{task_id}] file_send 工具异常: {_fse}")
@@ -1727,17 +1738,14 @@ class MainAgent(BaseAgent):
1727
1738
  # 标记为媒体文件,前端渲染内嵌播放器
1728
1739
  _fresult["_media_type"] = _media_type
1729
1740
  result = {"success": True, "output": f"已发送{_media_type}文件: {_fpath.name}", "data": _fresult}
1730
- try:
1731
- if _fresult.get("file_id"):
1732
- _sent_files.append({
1733
- "id": _fresult["file_id"],
1734
- "name": _fresult.get("name", ""),
1735
- "type": _fresult.get("type", ""),
1736
- "size": _fresult.get("size", 0),
1737
- "_media_type": _media_type,
1738
- })
1739
- except NameError:
1740
- pass
1741
+ if sent_files is not None and _fresult.get("file_id"):
1742
+ sent_files.append({
1743
+ "id": _fresult["file_id"],
1744
+ "name": _fresult.get("name", ""),
1745
+ "type": _fresult.get("type", ""),
1746
+ "size": _fresult.get("size", 0),
1747
+ "_media_type": _media_type,
1748
+ })
1741
1749
  else:
1742
1750
  result = {"success": False, "error": _fresult.get("error", "文件发送失败")}
1743
1751
  else:
@@ -1747,12 +1755,138 @@ class MainAgent(BaseAgent):
1747
1755
  result = {"success": False, "error": f"播放工具异常: {_me}"}
1748
1756
  logger.warning(f"[{task_id}] {tool_name} 工具异常: {_me}")
1749
1757
 
1758
+ elif tool_name == "web_control":
1759
+ # [v1.21.0] 网页控制器 — 在聊天中打开可控制的浏览器面板
1760
+ try:
1761
+ from core.web_control import get_web_control_manager
1762
+ _wc_mgr = get_web_control_manager()
1763
+ _wc_action = params.get("action", "open")
1764
+ _wc_session_id = params.get("session_id", "").strip()
1765
+
1766
+ # 自动获取或创建会话
1767
+ _wc_session = None
1768
+ if _wc_session_id:
1769
+ _wc_session = _wc_mgr.get_session(_wc_session_id)
1770
+ if not _wc_session:
1771
+ _wc_session = _wc_mgr.create_session()
1772
+ _wc_session_id = _wc_session.session_id
1773
+
1774
+ if _wc_action == "open":
1775
+ # 打开面板 — 发送 SSE 事件让前端弹出控制面板
1776
+ _wc_url = params.get("url", "").strip()
1777
+ if _wc_url:
1778
+ _wc_session.current_url = _wc_url
1779
+ if stream_callback:
1780
+ stream_callback({
1781
+ "type": "v2_web_control",
1782
+ "data": {
1783
+ "action": "open",
1784
+ "session_id": _wc_session_id,
1785
+ "url": _wc_url,
1786
+ "panel_url": f"/api/web_control/panel?sid={_wc_session_id}",
1787
+ }
1788
+ })
1789
+ result = {
1790
+ "success": True,
1791
+ "output": f"已打开网页控制面板 (session: {_wc_session_id})" + (f",URL: {_wc_url}" if _wc_url else ""),
1792
+ "session_id": _wc_session_id,
1793
+ }
1794
+
1795
+ elif _wc_action == "close":
1796
+ _wc_mgr.close_session(_wc_session_id)
1797
+ if stream_callback:
1798
+ stream_callback({
1799
+ "type": "v2_web_control",
1800
+ "data": {"action": "close", "session_id": _wc_session_id}
1801
+ })
1802
+ result = {"success": True, "output": f"已关闭网页控制面板 (session: {_wc_session_id})"}
1803
+
1804
+ elif _wc_action == "navigate":
1805
+ _wc_url = params.get("url", "").strip()
1806
+ if not _wc_url:
1807
+ result = {"success": False, "error": "请提供 url 参数"}
1808
+ else:
1809
+ _wc_session.current_url = _wc_url
1810
+ if stream_callback:
1811
+ stream_callback({
1812
+ "type": "v2_web_control",
1813
+ "data": {"action": "navigate", "session_id": _wc_session_id, "url": _wc_url}
1814
+ })
1815
+ result = {"success": True, "output": f"正在导航到: {_wc_url}"}
1816
+
1817
+ elif _wc_action in ("set_cookies", "get_cookies"):
1818
+ # Cookie 操作 — 服务端直接处理
1819
+ if _wc_action == "set_cookies":
1820
+ _cookies = params.get("cookies", [])
1821
+ if isinstance(_cookies, str):
1822
+ import json as _jc
1823
+ try:
1824
+ _cookies = _jc.loads(_cookies)
1825
+ except:
1826
+ _cookies = []
1827
+ for _c in _cookies:
1828
+ if isinstance(_c, dict):
1829
+ _c_domain = _c.get("domain", "").lstrip(".")
1830
+ _c_name = _c.get("name", "")
1831
+ _c_value = _c.get("value", "")
1832
+ if _c_domain and _c_name:
1833
+ _wc_session.cookies[f"{_c_domain}::{_c_name}"] = _c_value
1834
+ result = {"success": True, "output": f"已设置 {len(_cookies)} 个 cookie", "total_cookies": len(_wc_session.cookies)}
1835
+ else:
1836
+ _cookie_list = []
1837
+ for _ck, _cv in _wc_session.cookies.items():
1838
+ _parts = _ck.split("::", 1)
1839
+ if len(_parts) == 2:
1840
+ _cookie_list.append({"domain": _parts[0], "name": _parts[1], "value": _cv})
1841
+ result = {"success": True, "output": json.dumps(_cookie_list, ensure_ascii=False), "cookies": _cookie_list}
1842
+
1843
+ else:
1844
+ # 其他操作(click, fill, scroll, evaluate, get_content, wait, screenshot)
1845
+ # 通过命令队列下发到客户端执行,阻塞等待结果
1846
+ _wc_params = {k: v for k, v in params.items() if k not in ("action", "session_id")}
1847
+ _wc_timeout = int(params.get("timeout", timeout))
1848
+ _wc_result = await _wc_mgr.queue_command(
1849
+ session_id=_wc_session_id,
1850
+ action=_wc_action,
1851
+ params=_wc_params,
1852
+ timeout=min(_wc_timeout, 60), # 最大 60 秒
1853
+ )
1854
+ result = _wc_result
1855
+
1856
+ except Exception as _wce:
1857
+ result = {"success": False, "error": f"网页控制器异常: {_wce}"}
1858
+ logger.warning(f"[{task_id}] web_control 工具异常: {_wce}")
1859
+
1750
1860
  elif self.skills:
1751
1861
  exec_result = await self.skills.execute(tool_name, **params)
1752
1862
  if exec_result is None:
1753
1863
  result["error"] = f"技能 {tool_name} 返回了空结果"
1754
1864
  else:
1755
1865
  result = exec_result.to_dict()
1866
+ # [v1.21.1] Skill 生成文件后自动通过 file_send 发送给前端
1867
+ if exec_result.success and exec_result.files:
1868
+ try:
1869
+ from skills.file_send import FileSendSkill
1870
+ _auto_fsend = FileSendSkill()
1871
+ for _auto_fpath in exec_result.files:
1872
+ if _auto_fpath and os.path.isfile(_auto_fpath):
1873
+ _auto_fres = await _auto_fsend.execute(
1874
+ _auto_fpath, stream_callback=stream_callback)
1875
+ if _auto_fres.get("success") and _auto_fres.get("file_id"):
1876
+ # 记录发送的 file_id
1877
+ if not result.get("_sent_file_ids"):
1878
+ result["_sent_file_ids"] = []
1879
+ result["_sent_file_ids"].append(_auto_fres["file_id"])
1880
+ # 追踪到 sent_files(持久化到会话记忆)
1881
+ if sent_files is not None:
1882
+ sent_files.append({
1883
+ "id": _auto_fres["file_id"],
1884
+ "name": _auto_fres.get("name", ""),
1885
+ "type": _auto_fres.get("type", ""),
1886
+ "size": _auto_fres.get("size", 0),
1887
+ })
1888
+ except Exception as _afe:
1889
+ logger.warning(f"[{task_id}] 自动 file_send 失败: {_afe}")
1756
1890
  else:
1757
1891
  result["error"] = f"未知工具: {tool_name}"
1758
1892