myagent-ai 1.20.10 → 1.20.12

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.
@@ -544,6 +544,18 @@ class MainAgent(BaseAgent):
544
544
  text_delta_callback=None,
545
545
  ) -> AgentContext:
546
546
  """V2 内部循环逻辑 — 结构化输出 + 工具调度 + SSE 事件推送"""
547
+
548
+ # [v1.20.11] 安全发送 SSE 事件 — 兼容 sync/async 回调
549
+ async def _safe_sse(cb, event: dict):
550
+ """安全调用 stream_callback,兼容同步和异步回调。"""
551
+ try:
552
+ if asyncio.iscoroutinefunction(cb):
553
+ await cb(event)
554
+ else:
555
+ cb(event)
556
+ except Exception as e:
557
+ logger.debug(f"SSE 事件发送失败: {e}")
558
+
547
559
  max_iter = self.config.agent.max_iterations
548
560
  current_task_plan = ""
549
561
  all_tool_outputs = ""
@@ -1669,6 +1681,7 @@ class MainAgent(BaseAgent):
1669
1681
  _media_type = "audio" if tool_name == "playaudio" else "video"
1670
1682
  _embed_url = None
1671
1683
  _embed_title = params.get("title", "")
1684
+ _fallback_link = None # [v1.20.10] 无法嵌入时提供外部链接
1672
1685
 
1673
1686
  if _media_url:
1674
1687
  # 在线链接 — 提取嵌入式播放 URL
@@ -1679,6 +1692,21 @@ class MainAgent(BaseAgent):
1679
1692
  if _yt_match:
1680
1693
  _embed_url = f"https://www.youtube.com/embed/{_yt_match.group(1)}"
1681
1694
  _embed_title = _embed_title or "YouTube 视频"
1695
+ # YouTube Music 播放列表: https://music.youtube.com/playlist?list=xxx
1696
+ elif 'music.youtube.com' in _url_lower:
1697
+ _ym_match = re.search(r'list=([\w-]+)', _media_url)
1698
+ if _ym_match:
1699
+ _embed_url = f"https://music.youtube.com/embed?list={_ym_match.group(1)}&layout=full"
1700
+ _embed_title = _embed_title or "YouTube Music"
1701
+ else:
1702
+ # 单曲: https://music.youtube.com/watch?v=xxx
1703
+ _ymv_match = re.search(r'watch\?v=([\w-]+)', _media_url)
1704
+ if _ymv_match:
1705
+ _embed_url = f"https://music.youtube.com/embed/{_ymv_match.group(1)}"
1706
+ _embed_title = _embed_title or "YouTube Music"
1707
+ else:
1708
+ _fallback_link = _media_url
1709
+ _embed_title = _embed_title or "YouTube Music"
1682
1710
  # Bilibili: https://www.bilibili.com/video/BVxxx 或 b23.tv/xxx
1683
1711
  elif 'bilibili.com' in _url_lower or 'b23.tv' in _url_lower:
1684
1712
  _bv_match = re.search(r'bilibili\.com/video/(BV[\w]+)', _media_url)
@@ -1690,8 +1718,14 @@ class MainAgent(BaseAgent):
1690
1718
  _embed_title = _embed_title or "B站视频"
1691
1719
  # QQ音乐: https://y.qq.com/n/ryqq/songDetail/xxx
1692
1720
  elif 'y.qq.com' in _url_lower:
1693
- _embed_url = _media_url
1694
- _embed_title = _embed_title or "QQ音乐"
1721
+ # QQ音乐支持 outchain player
1722
+ _qq_match = re.search(r'songDetail/(\w+)', _media_url)
1723
+ if _qq_match:
1724
+ _embed_url = f"https://y.qq.com/n/ryqq/songDetail/{_qq_match.group(1)}"
1725
+ _embed_title = _embed_title or "QQ音乐"
1726
+ else:
1727
+ _embed_url = _media_url
1728
+ _embed_title = _embed_title or "QQ音乐"
1695
1729
  # 网易云音乐: https://music.163.com/song?id=xxx
1696
1730
  elif 'music.163.com' in _url_lower:
1697
1731
  _song_match = re.search(r'music\.163\.com.*[?&]id=(\d+)', _media_url)
@@ -1712,7 +1746,7 @@ class MainAgent(BaseAgent):
1712
1746
 
1713
1747
  if _embed_url and stream_callback:
1714
1748
  # 在线播放 — 发送 v2_media 事件让前端渲染嵌入播放器
1715
- stream_callback({
1749
+ await _safe_sse(stream_callback, {
1716
1750
  "type": "v2_media",
1717
1751
  "data": {
1718
1752
  "media_type": _media_type,
@@ -1723,6 +1757,19 @@ class MainAgent(BaseAgent):
1723
1757
  })
1724
1758
  result = {"success": True, "output": f"已嵌入{_embed_title}播放器: {_media_url}"}
1725
1759
 
1760
+ elif _fallback_link and stream_callback:
1761
+ # [v1.20.10] 无法嵌入但提供外部链接 — 发送链接卡片
1762
+ await _safe_sse(stream_callback, {
1763
+ "type": "v2_media",
1764
+ "data": {
1765
+ "media_type": _media_type,
1766
+ "embed_url": "", # 空 embed_url 告诉前端渲染链接
1767
+ "title": _embed_title,
1768
+ "original_url": _fallback_link,
1769
+ }
1770
+ })
1771
+ result = {"success": True, "output": f"已提供{_embed_title}链接(不支持嵌入播放): {_fallback_link}"}
1772
+
1726
1773
  elif _media_file:
1727
1774
  # 本地文件 — 使用 file_send 发送文件,前端渲染内嵌播放器
1728
1775
  from pathlib import Path as _P
@@ -1777,7 +1824,7 @@ class MainAgent(BaseAgent):
1777
1824
  if _wc_url:
1778
1825
  _wc_session.current_url = _wc_url
1779
1826
  if stream_callback:
1780
- stream_callback({
1827
+ await _safe_sse(stream_callback, {
1781
1828
  "type": "v2_web_control",
1782
1829
  "data": {
1783
1830
  "action": "open",
@@ -1795,7 +1842,7 @@ class MainAgent(BaseAgent):
1795
1842
  elif _wc_action == "close":
1796
1843
  _wc_mgr.close_session(_wc_session_id)
1797
1844
  if stream_callback:
1798
- stream_callback({
1845
+ await _safe_sse(stream_callback, {
1799
1846
  "type": "v2_web_control",
1800
1847
  "data": {"action": "close", "session_id": _wc_session_id}
1801
1848
  })
@@ -1808,7 +1855,7 @@ class MainAgent(BaseAgent):
1808
1855
  else:
1809
1856
  _wc_session.current_url = _wc_url
1810
1857
  if stream_callback:
1811
- stream_callback({
1858
+ await _safe_sse(stream_callback, {
1812
1859
  "type": "v2_web_control",
1813
1860
  "data": {"action": "navigate", "session_id": _wc_session_id, "url": _wc_url}
1814
1861
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.20.10",
3
+ "version": "1.20.12",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -2825,15 +2825,28 @@ function _renderMessagesInner() {
2825
2825
  let embedUrl = m.embed_url || '';
2826
2826
  const title = m.title || (isAudio ? '在线音乐' : '在线视频');
2827
2827
  const origUrl = m.original_url || embedUrl;
2828
- if (!embedUrl) continue;
2828
+ if (!embedUrl && !origUrl) continue;
2829
+ // [v1.20.10] 如果 embed_url 为空,渲染链接卡片而非 iframe
2830
+ if (!embedUrl) {
2831
+ const icon = isAudio ? '🎵' : '🎬';
2832
+ parts.push('<div class="msg-media-embed msg-media-link-card" style="padding:10px 14px;border-radius:8px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);cursor:pointer" onclick="window.open(\'' + escapeHtml(origUrl) + '\',\'_blank\')">' +
2833
+ '<div class="msg-media-header"><span class="msg-media-icon">' + icon + '</span><span class="msg-media-label">' + escapeHtml(title) + '</span>' +
2834
+ '<span style="margin-left:auto;font-size:12px;opacity:0.6">点击在新窗口打开 ↗</span></div>' +
2835
+ '<div style="font-size:12px;opacity:0.5;margin-top:4px;word-break:break-all">' + escapeHtml(origUrl) + '</div>' +
2836
+ '</div>');
2837
+ continue;
2838
+ }
2829
2839
  // 前端 URL → 嵌入 URL 转换(历史回放时 embed_url 可能是原始 URL)
2830
- if (embedUrl && !embedUrl.includes('/embed/') && !embedUrl.includes('/player')) {
2840
+ if (embedUrl && !embedUrl.includes('/embed/') && !embedUrl.includes('/player') && !embedUrl.includes('/outchain/')) {
2831
2841
  const ytMatch = embedUrl.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
2832
2842
  if (ytMatch) { embedUrl = 'https://www.youtube.com/embed/' + ytMatch[1]; }
2833
2843
  const biliMatch = embedUrl.match(/bilibili\.com\/video\/(BV[\w]+)/);
2834
2844
  if (biliMatch) { embedUrl = 'https://player.bilibili.com/player.html?bvid=' + biliMatch[1] + '&autoplay=0'; }
2835
2845
  const neteaseMatch = embedUrl.match(/music\.163\.com.*[?&]id=(\d+)/);
2836
2846
  if (neteaseMatch) { embedUrl = 'https://music.163.com/outchain/player?type=2&id=' + neteaseMatch[1] + '&auto=0&height=66'; }
2847
+ // [v1.20.10] YouTube Music 播放列表 → embed
2848
+ const ymMatch = embedUrl.match(/music\.youtube\.com.*list=([\w-]+)/);
2849
+ if (ymMatch) { embedUrl = 'https://music.youtube.com/embed?list=' + ymMatch[1] + '&layout=full'; }
2837
2850
  }
2838
2851
  if (isAudio) {
2839
2852
  parts.push('<div class="msg-media-embed msg-media-audio">' +