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.
- package/core/vnc_manager.py +6 -1
- package/package.json +1 -1
- package/skills/chromedev_mcp.py +58 -15
- package/web/ui/chat/chat.css +47 -0
- package/web/ui/chat/chat_main.js +41 -8
package/core/vnc_manager.py
CHANGED
|
@@ -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
package/skills/chromedev_mcp.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
858
|
-
|
|
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
|
-
|
|
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}")
|
package/web/ui/chat/chat.css
CHANGED
|
@@ -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;
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -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
|
-
? (
|
|
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
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
'
|
|
2969
|
-
'.toolbar
|
|
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
|
-
|
|
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
|
|