myagent-ai 1.18.3 → 1.18.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.
@@ -724,8 +724,13 @@ class VNCManager:
724
724
  )
725
725
 
726
726
  # 等待 VNC 端口就绪
727
- await asyncio.sleep(1)
727
+ await asyncio.sleep(1.5)
728
728
  if self._x11vnc_process.poll() is not None:
729
+ # [v1.18.4] x11vnc 可能 fork 到后台运行,父进程退出但子进程仍在监听端口
730
+ if self._is_port_listening(self.x11vnc_port):
731
+ logger.warning(f"x11vnc 父进程已退出但端口 {self.x11vnc_port} 仍在监听(fork 到后台),视为启动成功")
732
+ return True
733
+ # 端口未监听 = 真正的启动失败
729
734
  stderr = ""
730
735
  try:
731
736
  stderr = self._x11vnc_process.stderr.read().decode("utf-8", errors="replace")[:2000]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.18.3",
3
+ "version": "1.18.5",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -383,6 +383,11 @@ class MCPClient:
383
383
  pass
384
384
  except Exception:
385
385
  pass
386
+ finally:
387
+ # [v1.18.3] stdout EOF = MCP Server 或 Chrome 已断开
388
+ # 标记为未初始化,下次 call_tool 会自动重建
389
+ self._initialized = False
390
+ logger.warning("MCP stdout 读取结束(Server 或 Chrome 已断开)")
386
391
 
387
392
  async def _handshake(self) -> bool:
388
393
  """执行 MCP 初始化握手"""
@@ -552,7 +557,12 @@ class MCPClient:
552
557
  self._cleanup()
553
558
 
554
559
  def _cleanup(self):
555
- """清理子进程"""
560
+ """清理子进程(包括 Chrome/Chromium 浏览器)
561
+
562
+ [v1.18.3] 修复 'Target closed' 问题:
563
+ 终止 MCP Server 后,额外杀死残留的 Chrome 进程,
564
+ 避免下次启动时因锁文件冲突导致 'Target closed'。
565
+ """
556
566
  self._initialized = False
557
567
  if self._process:
558
568
  try:
@@ -569,6 +579,48 @@ class MCPClient:
569
579
  pass
570
580
  self._process = None
571
581
 
582
+ # [v1.18.3] 清理残留的 Chrome/Chromium 进程
583
+ # chrome-devtools-mcp 启动的 Chrome 可能不会随 MCP 进程一起退出
584
+ self._kill_stale_chrome()
585
+
586
+ def _kill_stale_chrome(self):
587
+ """清理残留的 Chrome/Chromium 进程
588
+
589
+ 查找并杀死由 chrome-devtools-mcp 启动的 Chrome 进程。
590
+ 这些进程可能导致下次启动时 'Target closed' 错误。
591
+ """
592
+ import signal as _sig
593
+ try:
594
+ # 查找 chrome-devtools-mcp 启动的 Chrome 进程
595
+ # 特征: 父进程是 node/npx (chrome-devtools-mcp)
596
+ result = subprocess.run(
597
+ ["pgrep", "-f", "chromium-browser.*--remote-debugging-port"],
598
+ capture_output=True, text=True, timeout=5,
599
+ )
600
+ if result.returncode == 0 and result.stdout.strip():
601
+ pids = result.stdout.strip().split("\n")
602
+ for pid_str in pids:
603
+ try:
604
+ pid = int(pid_str)
605
+ os.kill(pid, _sig.SIGTERM)
606
+ logger.debug(f"清理残留 Chrome 进程: PID={pid}")
607
+ except (ProcessLookupError, PermissionError, ValueError):
608
+ pass
609
+
610
+ # 等一小会让 Chrome 优雅退出
611
+ import time
612
+ time.sleep(0.5)
613
+
614
+ # 还没退就强杀
615
+ for pid_str in pids:
616
+ try:
617
+ pid = int(pid_str)
618
+ os.kill(pid, _sig.SIGKILL)
619
+ except (ProcessLookupError, PermissionError, ValueError):
620
+ pass
621
+ except Exception as e:
622
+ logger.debug(f"清理 Chrome 进程异常: {e}")
623
+
572
624
  def is_running(self) -> bool:
573
625
  """检查 MCP Server 是否正在运行"""
574
626
  return self._process is not None and self._process.poll() is None
@@ -849,17 +901,13 @@ class BrowserOpenSkill(Skill):
849
901
  f"请执行: apt install -y chromium-browser 或 apt install -y chromium,"
850
902
  f"安装后重试即可。"
851
903
  )
852
- # [v1.16.18] 检测 Chrome 崩溃,尝试重建 MCP 客户端并重试
904
+ # [v1.16.18→18.3] 检测 Chrome 崩溃,彻底清理后重建 MCP 客户端并重试
853
905
  _crash_keywords = ["Target closed", "Target destroyed", "Protocol error",
854
906
  "Browser closed", "Connection closed", "Session closed"]
855
907
  if any(kw in err_text for kw in _crash_keywords) and attempt < max_attempts - 1:
856
908
  logger.warning(f"Chrome 连接丢失 ({err_text[:100]}),正在重建 MCP 客户端并重试...")
857
- try:
858
- if client:
859
- client._cleanup()
860
- except Exception:
861
- pass
862
- _mcp_client = None
909
+ # [v1.18.3] 彻底清理所有 MCP 客户端(包括残留 Chrome 进程)
910
+ await rebuild_mcp_client()
863
911
  continue # 重试
864
912
  return SkillResult(success=False, error=f"导航失败: {err_text}")
865
913
 
@@ -921,18 +969,13 @@ class BrowserOpenSkill(Skill):
921
969
  message=f"已打开: {page_title} (文本 {len(text)} 字符, {len(links)} 个链接)",
922
970
  )
923
971
  except Exception as e:
924
- # [v1.16.18] 异常时也检测 Chrome 崩溃并重试
972
+ # [v1.16.18→18.3] 异常时也检测 Chrome 崩溃并彻底重建
925
973
  _err_str = str(e)
926
974
  _crash_keywords = ["Target closed", "Target destroyed", "Protocol error",
927
975
  "Browser closed", "Connection closed", "Session closed"]
928
976
  if any(kw in _err_str for kw in _crash_keywords) and attempt < max_attempts - 1:
929
977
  logger.warning(f"Chrome 异常 ({_err_str[:100]}),正在重建 MCP 客户端并重试...")
930
- try:
931
- if _mcp_client:
932
- _mcp_client._cleanup()
933
- except Exception:
934
- pass
935
- _mcp_client = None
978
+ await rebuild_mcp_client()
936
979
  continue # 重试
937
980
  logger.error(f"浏览器打开失败: {e}")
938
981
  return SkillResult(success=False, error=f"浏览器打开失败: {e}")
@@ -676,6 +676,53 @@ input,textarea,select{font:inherit}
676
676
  .msg-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}
677
677
  .msg-file-size{font-size:11px;color:var(--text3);flex-shrink:0}
678
678
 
679
+ /* [v1.17] 移动端文件附件适配 */
680
+ @media (max-width: 768px) {
681
+ .msg-file-item {
682
+ max-width: 100%;
683
+ padding: 10px 14px;
684
+ font-size: 14px;
685
+ gap: 10px;
686
+ min-height: 44px;
687
+ }
688
+ .msg-file-icon { font-size: 22px; }
689
+ .msg-file-name { font-size: 14px; }
690
+ .msg-attachments { gap: 10px; }
691
+ .msg-image-wrapper { max-width: 100%; }
692
+ }
693
+
694
+ /* [v1.17] 折叠文件内容样式 */
695
+ .file-content-collapse {
696
+ border: 1px solid var(--bg4);
697
+ border-radius: var(--radius-sm);
698
+ overflow: hidden;
699
+ }
700
+ .file-content-collapse summary {
701
+ padding: 8px 14px;
702
+ cursor: pointer;
703
+ font-size: 13px;
704
+ color: var(--text3);
705
+ background: var(--bg2);
706
+ user-select: none;
707
+ display: flex;
708
+ align-items: center;
709
+ gap: 6px;
710
+ }
711
+ .file-content-collapse summary:hover {
712
+ color: var(--text2);
713
+ background: var(--bg3);
714
+ }
715
+ .file-content-collapse[open] summary {
716
+ border-bottom: 1px solid var(--bg4);
717
+ }
718
+ .file-content-collapse .collapse-body {
719
+ padding: 10px 14px;
720
+ max-height: 400px;
721
+ overflow-y: auto;
722
+ font-size: 13px;
723
+ line-height: 1.5;
724
+ }
725
+
679
726
  .send-btn{
680
727
  width:36px;height:36px;border-radius:var(--radius-sm);
681
728
  background:var(--accent);color:#fff;
@@ -2779,9 +2779,15 @@ function _renderMessagesInner() {
2779
2779
  }
2780
2780
  }
2781
2781
 
2782
+ // [v1.17] 对历史消息中的长文件内容进行折叠
2783
+ const needCollapse = !msg.streaming && !isUser && !hasParts && !hasStreamingText && shouldCollapseContent(msg.content);
2784
+ const collapsedContent = needCollapse
2785
+ ? '<details class="file-content-collapse"><summary>📄 文件内容处理结果(点击展开)</summary><div class="collapse-body">' + content + '</div></details>'
2786
+ : content;
2787
+
2782
2788
  // Backward compat: single bubble for messages without parts (full width)
2783
2789
  const singleBubbleHtml = (!hasParts && !hasStreamingText)
2784
- ? (content ? `<div class="message-bubble msg-bubble-wrapper">${content}${ttsIndicator}</div>` : '')
2790
+ ? (collapsedContent ? `<div class="message-bubble msg-bubble-wrapper">${collapsedContent}${ttsIndicator}</div>` : '')
2785
2791
  : '';
2786
2792
 
2787
2793
  // ── Task Plan (historical view only — hidden during streaming, shown after completion) ──
@@ -2924,6 +2930,25 @@ function renderMarkdown(text) {
2924
2930
  }
2925
2931
  }
2926
2932
 
2933
+ // [v1.17] 检测消息内容是否应折叠(用于历史消息中的长文件内容)
2934
+ function shouldCollapseContent(content) {
2935
+ if (!content || typeof content !== 'string') return false;
2936
+ // 只折叠较长的内容(> 1500 字符)
2937
+ if (content.length < 1500) return false;
2938
+ // 检测文件读取/处理模式
2939
+ var filePatterns = [
2940
+ '已读取', '已读取文件', '文件内容', '📄',
2941
+ 'tool_call', 'tool_result', 'Extracted text',
2942
+ '读取完成', '文件读取', 'PDF 内容', 'Excel 内容',
2943
+ 'Document content', 'Sheet data'
2944
+ ];
2945
+ // 检测文件扩展名
2946
+ var fileExts = /\.(pdf|xlsx|docx|pptx|csv|doc|xls|ppt|txt|md|json|xml|html)/i;
2947
+ var hasFilePattern = filePatterns.some(function(p) { return content.indexOf(p) >= 0; });
2948
+ var hasFileExt = fileExts.test(content);
2949
+ return hasFilePattern || hasFileExt;
2950
+ }
2951
+
2927
2952
  // 高效的 HTML 转义(不创建 DOM 元素,避免大文本时性能问题)
2928
2953
  // ── [v1.16.12] 文件上传辅助函数 ──
2929
2954
  function formatFileSize(bytes) {
@@ -2949,6 +2974,7 @@ function _getFileIcon(name) {
2949
2974
 
2950
2975
  // [v1.16.17→19] 文件查看器 — 新窗口打开文件 + 下载按钮
2951
2976
  // [v1.16.19] 支持更多文件类型内联预览,非预览类型直接触发下载
2977
+ // [v1.17] 移动端优化:全屏、更大按钮/字体、触摸友好
2952
2978
  function openFileViewer(fileId, src, fileName) {
2953
2979
  if (!src && !fileId) return;
2954
2980
  if (!src && fileId) src = '/api/file/' + fileId;
@@ -2962,18 +2988,20 @@ function openFileViewer(fileId, src, fileName) {
2962
2988
  var isText = /\.(txt|md|csv|json|xml|html?|css|js|ts|py|java|c|cpp|go|rs|rb|php|sh|yaml|yml|toml|ini|cfg|conf|log|sql|lua|vim)$/i.test(fileName || src);
2963
2989
  var canInline = isImage || isPdf || isVideo || isAudio;
2964
2990
 
2965
- var html = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>' + escapeHtml(fileName || '文件查看') + '</title>' +
2966
- '<style>*{margin:0;padding:0;box-sizing:border-box}body{background:#1a1a2e;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,sans-serif;display:flex;flex-direction:column;height:100vh;overflow:hidden}' +
2967
- '.toolbar{display:flex;align-items:center;padding:10px 16px;background:#16213e;gap:12px;border-bottom:1px solid #2a2a4a;flex-shrink:0}' +
2968
- '.toolbar .fname{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px}' +
2969
- '.toolbar button{padding:6px 16px;border:none;border-radius:6px;cursor:pointer;font-size:13px;background:#4a4a8a;color:#fff;transition:background .2s}' +
2991
+ // [v1.17] 检测是否移动端,移动端使用全屏模式
2992
+ var _isMobile = isMobile();
2993
+ var html = '<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no"><title>' + escapeHtml(fileName || '文件查看') + '</title>' +
2994
+ '<style>*{margin:0;padding:0;box-sizing:border-box}html,body{width:100%;height:100%}body{background:#1a1a2e;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,sans-serif;display:flex;flex-direction:column;height:100vh;height:100dvh;overflow:hidden}' +
2995
+ '.toolbar{display:flex;align-items:center;padding:' + (_isMobile ? '12px 14px' : '10px 16px') + ';background:#16213e;gap:' + (_isMobile ? '10px' : '12px') + ';border-bottom:1px solid #2a2a4a;flex-shrink:0}' +
2996
+ '.toolbar .fname{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:' + (_isMobile ? '15px' : '14px') + '}' +
2997
+ '.toolbar button{padding:' + (_isMobile ? '8px 20px' : '6px 16px') + ';border:none;border-radius:6px;cursor:pointer;font-size:' + (_isMobile ? '15px' : '13px') + ';background:#4a4a8a;color:#fff;transition:background .2s;min-height:44px;display:inline-flex;align-items:center;justify-content:center}' +
2970
2998
  '.toolbar button:hover{background:#6a6aaa}' +
2971
2999
  '.toolbar .close-btn{background:#c0392b}.toolbar .close-btn:hover{background:#e74c3c}' +
2972
3000
  '.viewer{flex:1;display:flex;align-items:center;justify-content:center;overflow:auto;padding:16px;background:#0f0f23}' +
2973
3001
  '.viewer img{max-width:100%;max-height:100%;object-fit:contain;border-radius:4px}' +
2974
3002
  '.viewer iframe{width:100%;height:100%;border:none;background:#fff}' +
2975
3003
  '.viewer video,.viewer audio{max-width:100%;max-height:100%}' +
2976
- '.viewer .no-preview{display:flex;flex-direction:column;align-items:center;gap:16px;color:#999}' +
3004
+ '.viewer .no-preview{display:flex;flex-direction:column;align-items:center;gap:16px;color:#999;padding:20px}' +
2977
3005
  '.viewer .no-preview .icon{font-size:64px;opacity:0.5}' +
2978
3006
  '.viewer .no-preview .hint{font-size:14px;text-align:center;line-height:1.6}' +
2979
3007
  '.viewer pre{background:#1e1e3a;border-radius:8px;padding:16px;width:100%;height:100%;overflow:auto;font-size:13px;line-height:1.5;color:#d4d4d4;white-space:pre-wrap;word-break:break-all;border:1px solid #2a2a4a}' +
@@ -3005,10 +3033,15 @@ function openFileViewer(fileId, src, fileName) {
3005
3033
  }
3006
3034
  html += '</div></body></html>';
3007
3035
 
3008
- var w = window.open('', '_blank', 'width=900,height=700');
3036
+ // [v1.17] 移动端使用全屏,桌面端使用固定尺寸
3037
+ var w = window.open('', '_blank', _isMobile ? 'width=100%,height=100%' : 'width=900,height=700');
3009
3038
  if (w) {
3010
3039
  w.document.write(html);
3011
3040
  w.document.close();
3041
+ // [v1.17] 移动端尝试全屏
3042
+ if (_isMobile) {
3043
+ try { w.moveTo(0, 0); w.resizeTo(screen.width, screen.height); } catch(e) {}
3044
+ }
3012
3045
  }
3013
3046
  }
3014
3047