diff --git a/crates/flock-tools/src/sandbox_core/e2b/builder.rs b/crates/flock-tools/src/sandbox_core/e2b/builder.rs index 9f66a2aa..147c0f7f 100644 --- a/crates/flock-tools/src/sandbox_core/e2b/builder.rs +++ b/crates/flock-tools/src/sandbox_core/e2b/builder.rs @@ -34,15 +34,22 @@ which Xvfb || echo "Xvfb missing" which fluxbox || echo "fluxbox missing" which x11vnc || echo "x11vnc missing" which websockify || echo "websockify missing" +which chromium || echo "chromium (system) missing" +which google-chrome || echo "google-chrome missing" +which google-chrome-stable || echo "google-chrome-stable missing" + +echo "=== Debug: Testing Chrome Execution & Shared Libraries ===" +google-chrome-stable --version 2>&1 || echo "Chrome version check failed" +ldd /usr/bin/google-chrome-stable | grep "not found" || echo "All shared library dependencies satisfied" echo "==========================================" export DISPLAY=:0 rm -f /tmp/.X0-lock -setsid Xvfb :0 -screen 0 1280x800x24 >/tmp/xvfb.log 2>&1 & +setsid Xvfb :0 -screen 0 1280x800x24 -ac +extension GLX +render -noreset >/tmp/xvfb.log 2>&1 & sleep 1 setsid fluxbox >/tmp/fluxbox.log 2>&1 & -sleep 1 -setsid x11vnc -display :0 -forever -shared -nopw -rfbport 5900 >/tmp/x11vnc.log 2>&1 & +sleep 2 +setsid x11vnc -display :0 -forever -shared -nopw -rfbport 5900 -noxrecord -noxfixes -noxdamage >/tmp/x11vnc.log 2>&1 & sleep 1 setsid websockify --web /usr/share/novnc 0.0.0.0:6080 127.0.0.1:5900 >/tmp/websockify.log 2>&1 & sleep 1 @@ -51,27 +58,44 @@ sleep 1 std::fs::write(&start_script_path, lf_content) .context("Failed to write start_command.sh")?; - // 1. 写入 Dockerfile - let dockerfile_content = r#"FROM e2bdev/desktop:latest -USER root -# 复制启动脚本并赋予执行权限 -COPY start_command.sh /start_command.sh -RUN chmod +x /start_command.sh - -# 安装缺少的 VNC 窗口管理器和 noVNC 代理组件 -RUN apt-get update && apt-get install -y fluxbox websockify novnc - -# 直接使用已有的 python3 和 pip 安装 Playwright 及其浏览器依赖 -RUN pip install playwright && \ - playwright install chromium && \ - playwright install-deps chromium - -USER user -"#; + // 写入 Dockerfile + // 策略:使用官方 deb 包安装 Google Chrome Stable(完整 GUI,原生 X11 支持,VNC 可见) + // 绕过 Debian 下 apt 直接装 chromium 不存在的问题,同时也避免了 playwright install 软连接失效问题。 + let dockerfile_content = "FROM e2bdev/desktop:latest\n\ +USER root\n\ +\n\ +# 复制启动脚本\n\ +COPY start_command.sh /start_command.sh\n\ +RUN chmod +x /start_command.sh\n\ +\n\ +# 安装 VNC 桌面组件\n\ +RUN apt-get update && apt-get install -y --no-install-recommends fluxbox websockify novnc xterm wget gnupg && rm -rf /var/lib/apt/lists/*\n\ +\n\ +# CJK 字体(可选)\n\ +RUN apt-get update && apt-get install -y --no-install-recommends fonts-wqy-zenhei fonts-wqy-microhei 2>/dev/null; apt-get install -y --no-install-recommends fonts-noto-cjk 2>/dev/null; rm -rf /var/lib/apt/lists/*; true\n\ +\n\ +# 安装 Google Chrome 稳定版(含完整 GUI,支持 X11,VNC 中可见)\n\ +RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \\\n\ +&& echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list \\\n\ +&& apt-get update \\\n\ +&& apt-get install -y --no-install-recommends google-chrome-stable \\\n\ +&& rm -rf /var/lib/apt/lists/*\n\ +\n\ +# 创建 chromium 软链接,使得支持 chromium 命令的工具能自动调用它\n\ +RUN ln -sf /usr/bin/google-chrome-stable /usr/local/bin/chromium \\\n\ +&& ln -sf /usr/bin/google-chrome-stable /usr/local/bin/google-chrome\n\ +\n\ +# 安装 Playwright Python 库(仅库本身,不下载 playwright 浏览器)\n\ +# 运行时由 PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH 指向 google-chrome-stable\n\ +RUN pip3 install playwright\n\ +ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable\n\ +\n\ +USER user\n\ +"; std::fs::write(&dockerfile_path, dockerfile_content) .context("Failed to write Dockerfile")?; - on_log("环境准备完毕,开始调用 E2B CLI 构建镜像 (E2B 2.0 在云端构建,不需要本地安装 Docker)...\n".to_string()); + on_log("Environment setup complete. Invoking E2B CLI to build the image (E2B 2.0 builds in the cloud; no local Docker installation is required)...\n".to_string()); // 3. 执行 npx @e2b/cli template create // 在 Windows 下需要调用 npx.cmd @@ -88,6 +112,10 @@ USER user .arg("/start_command.sh") .arg("--ready-cmd") .arg("python3 -c \"import socket; s = socket.socket(); s.connect(('127.0.0.1', 6080))\"") + .arg("--cpu-count") + .arg("2") + .arg("--memory-mb") + .arg("2048") .arg(name) .env("E2B_API_KEY", api_key) .current_dir(&builder_dir) @@ -137,23 +165,15 @@ USER user anyhow::bail!("E2B template build failed with exit code: {}.\nError details:\n{}", status, full_output); } - on_log("构建成功!正在解析 Template ID...\n".to_string()); + on_log("Build succeeded! Parsing Template ID...\n".to_string()); // 解析出 template ID - let re = regex::Regex::new(r"(?i)\b(?:id|template_id)[:\s]+([a-z0-9]{20})\b").unwrap(); + let re = regex::Regex::new(r"(?i)\b(?:id|template_id)[\s:]+([a-z0-9]{20})\b").unwrap(); if let Some(cap) = re.captures(&full_output) { let id = cap.get(1).unwrap().as_str().to_string(); + on_log(format!("Template ID successfully parsed: {}\n", id)); return Ok(id); } - // 回退解析:寻找任何非 "dockerfile" 的 20 位字母数字组合作为 ID - let re_fallback = regex::Regex::new(r"\b([a-z0-9]{20})\b").unwrap(); - for cap in re_fallback.captures_iter(&full_output) { - let matched = cap.get(1).unwrap().as_str(); - if matched != "dockerfile" && matched != "template" { - return Ok(matched.to_string()); - } - } - - anyhow::bail!("构建成功,但未能从 E2B CLI 输出日志中提取出生成的 template_id") + anyhow::bail!("Template build succeeded, but failed to extract the generated templateID from E2B CLI logs") } diff --git a/crates/flock-tools/src/sandbox_core/e2b/exec.rs b/crates/flock-tools/src/sandbox_core/e2b/exec.rs index 4006e17f..ad030117 100644 --- a/crates/flock-tools/src/sandbox_core/e2b/exec.rs +++ b/crates/flock-tools/src/sandbox_core/e2b/exec.rs @@ -37,12 +37,18 @@ pub async fn execute_command( let body_str = serde_json::to_string(&payload)?; + // 拼接 5 字节的 Connect 协议信封头部 (1 字节 flag + 4 字节大端序长度) + let mut req_body = Vec::new(); + req_body.push(0x00); + req_body.extend_from_slice(&(body_str.len() as u32).to_be_bytes()); + req_body.extend_from_slice(body_str.as_bytes()); + let resp = client.post(&url) .header("X-API-Key", api_key) .header("Connect-Protocol-Version", "1") .header("Content-Type", "application/connect+json") .header(reqwest::header::USER_AGENT, "flock-agent") - .body(body_str) + .body(req_body) .send() .await?; @@ -56,53 +62,48 @@ pub async fn execute_command( let mut stderr_accum = String::new(); let mut exit_code = 0; - use futures::StreamExt; - let mut body = resp.bytes_stream(); - let mut buffer = Vec::new(); - - while let Some(chunk_res) = body.next().await { - let chunk = chunk_res?; - buffer.extend_from_slice(&chunk); - - while buffer.len() >= 5 { - let flags = buffer[0]; - let length = u32::from_be_bytes([buffer[1], buffer[2], buffer[3], buffer[4]]) as usize; - - if buffer.len() >= 5 + length { - let payload_bytes = &buffer[5..5 + length]; - - if flags == 0x00 { - if let Ok(val) = serde_json::from_slice::(payload_bytes) { - if let Some(event) = val.get("event") { - if let Some(data) = event.get("data") { - if let Some(stdout_b64) = data.get("stdout").and_then(|v| v.as_str()) { - if let Ok(decoded) = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, stdout_b64) { - if let Ok(s) = String::from_utf8(decoded) { - stdout_accum.push_str(&s); - } + let response_bytes = resp.bytes().await?; + let mut buffer = response_bytes.to_vec(); + let mut idx = 0; + + while idx + 5 <= buffer.len() { + let flags = buffer[idx]; + let length = u32::from_be_bytes([buffer[idx+1], buffer[idx+2], buffer[idx+3], buffer[idx+4]]) as usize; + + if idx + 5 + length <= buffer.len() { + let payload_bytes = &buffer[idx+5..idx+5+length]; + + if flags == 0x00 { + if let Ok(val) = serde_json::from_slice::(payload_bytes) { + if let Some(event) = val.get("event") { + if let Some(data) = event.get("data") { + if let Some(stdout_b64) = data.get("stdout").and_then(|v| v.as_str()) { + if let Ok(decoded) = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, stdout_b64) { + if let Ok(s) = String::from_utf8(decoded) { + stdout_accum.push_str(&s); } } - if let Some(stderr_b64) = data.get("stderr").and_then(|v| v.as_str()) { - if let Ok(decoded) = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, stderr_b64) { - if let Ok(s) = String::from_utf8(decoded) { - stderr_accum.push_str(&s); - } + } + if let Some(stderr_b64) = data.get("stderr").and_then(|v| v.as_str()) { + if let Ok(decoded) = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, stderr_b64) { + if let Ok(s) = String::from_utf8(decoded) { + stderr_accum.push_str(&s); } } } - if let Some(end) = event.get("end") { - if let Some(code) = end.get("exitCode").or_else(|| end.get("exit_code")).and_then(|v| v.as_i64()) { - exit_code = code as i32; - } + } + if let Some(end) = event.get("end") { + if let Some(code) = end.get("exitCode").or_else(|| end.get("exit_code")).and_then(|v| v.as_i64()) { + exit_code = code as i32; } } } } - - buffer.drain(0..5 + length); - } else { - break; } + + idx += 5 + length; + } else { + break; } } @@ -121,7 +122,7 @@ pub async fn execute_command( /// 直接返回固定格式 URL,不检查端口,不拉起任何进程。 pub fn get_vnc_url(sandbox_id: &str) -> String { format!( - "https://6080-{}.e2b.app/vnc.html?autoconnect=true&resize=scale&skip-preview-warning=true&skip_preview_warning=true", + "https://6080-{}.e2b.app/vnc.html?autoconnect=true&resize=scale&path=websockify", sandbox_id ) } diff --git a/crates/flock-tools/src/sandbox_core/e2b/lifecycle.rs b/crates/flock-tools/src/sandbox_core/e2b/lifecycle.rs index 4a9ba854..d63e88b1 100644 --- a/crates/flock-tools/src/sandbox_core/e2b/lifecycle.rs +++ b/crates/flock-tools/src/sandbox_core/e2b/lifecycle.rs @@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize}; pub struct E2BCreateSandboxRequest { #[serde(rename = "templateID")] pub template_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -46,12 +48,13 @@ pub async fn create_sandbox(cfg: &SandboxConfig) -> anyhow::Result { .unwrap_or("") .trim(); if template_id.is_empty() { - template_id = "jj17rl441iqdc6ea5mkb"; // 使用您发布的公共模版 ID 作为默认兜底 + template_id = "aa5z6s0zm7qv5g3oay5v"; // 使用您发布的公共模版 ID 作为默认兜底 } let client = reqwest::Client::new(); let payload = E2BCreateSandboxRequest { template_id: template_id.to_string(), + timeout: Some(300), }; crate::emit_info(&flock_core::tr( diff --git a/crates/flock-tools/src/sandbox_core/vnc_helper.rs b/crates/flock-tools/src/sandbox_core/vnc_helper.rs index 3625df7c..e33badd8 100644 --- a/crates/flock-tools/src/sandbox_core/vnc_helper.rs +++ b/crates/flock-tools/src/sandbox_core/vnc_helper.rs @@ -19,7 +19,22 @@ pub async fn ensure_vnc_running_in_sandbox(db: &DbManager, sandbox_id: &str) -> let check_cmd = format!("python3 -c \"import socket; s = socket.socket(); s.connect(('127.0.0.1', {}))\"", WEBSOCKIFY_PORT); let (_, exit_code) = execute_command_in_sandbox(db, sandbox_id, &check_cmd).await.unwrap_or(("-1".to_string(), -1)); if exit_code == 0 { - crate::emit_info(&flock_core::tr("检测到 VNC 桌面服务已经在运行。", "Detected VNC desktop service is already running.")); + // websockify 在跑,额外检查 fluxbox 是否存活(防止桌面黑屏) + let fluxbox_check = "pgrep -x fluxbox >/dev/null 2>&1 && echo ok || echo missing"; + let (fluxbox_out, _) = execute_command_in_sandbox(db, sandbox_id, fluxbox_check) + .await + .unwrap_or(("missing".to_string(), 1)); + if fluxbox_out.trim() == "ok" { + crate::emit_info(&flock_core::tr("检测到 VNC 桌面服务已经在运行。", "Detected VNC desktop service is already running.")); + return Ok(()); + } + // fluxbox 未运行,只补启 fluxbox + crate::emit_info(&flock_core::tr("VNC 端口在线但桌面管理器未运行,正在补启 fluxbox...", "VNC port active but window manager not running, restarting fluxbox...")); + let restart_fluxbox = format!( + "setsid nohup env DISPLAY={display} fluxbox >/tmp/fluxbox.log 2>&1 & sleep 2", + display = DISPLAY_ID + ); + let _ = execute_command_in_sandbox(db, sandbox_id, &restart_fluxbox).await; return Ok(()); } @@ -27,18 +42,18 @@ pub async fn ensure_vnc_running_in_sandbox(db: &DbManager, sandbox_id: &str) -> let launch_cmd = format!("sh -c '\ if command -v start-vnc >/dev/null 2>&1; then \ setsid nohup start-vnc >/tmp/vnc_start.log 2>&1 & \ - sleep 2; \ + sleep 3; \ else \ export DISPLAY={display} && \ rm -f /tmp/.X0-lock && \ setsid nohup Xvfb {display} -screen 0 {res} >/tmp/xvfb.log 2>&1 & \ sleep 1 && \ - setsid nohup fluxbox >/tmp/fluxbox.log 2>&1 & \ + setsid nohup env DISPLAY={display} fluxbox >/tmp/fluxbox.log 2>&1 & \ sleep 1 && \ - setsid nohup x11vnc -display {display} -forever -shared -nopw -rfbport {vnc_port} >/tmp/x11vnc.log 2>&1 & \ + setsid nohup env DISPLAY={display} x11vnc -display {display} -forever -shared -nopw -rfbport {vnc_port} >/tmp/x11vnc.log 2>&1 & \ sleep 1 && \ setsid nohup websockify --web /usr/share/novnc 0.0.0.0:{web_port} localhost:{vnc_port} >/tmp/websockify.log 2>&1 & \ - sleep 1; \ + sleep 2; \ fi'", display = DISPLAY_ID, res = SCREEN_RESOLUTION, diff --git a/crates/flock-tools/src/tools/sandbox/browser/interactive.rs b/crates/flock-tools/src/tools/sandbox/browser/interactive.rs index e0ed66e5..66c2c341 100644 --- a/crates/flock-tools/src/tools/sandbox/browser/interactive.rs +++ b/crates/flock-tools/src/tools/sandbox/browser/interactive.rs @@ -58,11 +58,11 @@ pub async fn handle_interactive( if ! python3 -c 'import socket; s = socket.socket(); s.connect((\"127.0.0.1\", 9222))' >/dev/null 2>&1; then \ echo 'Starting chromium exposing 9222 debugger port on DISPLAY={display}...' && \ if command -v chromium >/dev/null 2>&1; then \ - setsid nohup chromium --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-software-rasterizer >/tmp/interactive_chrome.log 2>&1 & \ + setsid nohup chromium --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-dev-shm-usage --no-first-run --no-default-browser-check --window-size=1280,1024 >/tmp/interactive_chrome.log 2>&1 & \ elif command -v chromium-browser >/dev/null 2>&1; then \ - setsid nohup chromium-browser --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-software-rasterizer >/tmp/interactive_chrome.log 2>&1 & \ + setsid nohup chromium-browser --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-dev-shm-usage --no-first-run --no-default-browser-check --window-size=1280,1024 >/tmp/interactive_chrome.log 2>&1 & \ elif command -v google-chrome >/dev/null 2>&1; then \ - setsid nohup google-chrome --no-sandbox --remote-debugging-port=9222 --disable-gpu >/tmp/interactive_chrome.log 2>&1 & \ + setsid nohup google-chrome --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-dev-shm-usage --no-first-run --no-default-browser-check --window-size=1280,1024 >/tmp/interactive_chrome.log 2>&1 & \ fi && \ sleep 3; \ fi", diff --git a/crates/flock-tools/src/tools/sandbox/browser/mod.rs b/crates/flock-tools/src/tools/sandbox/browser/mod.rs index 1fb588fa..28f0ece1 100644 --- a/crates/flock-tools/src/tools/sandbox/browser/mod.rs +++ b/crates/flock-tools/src/tools/sandbox/browser/mod.rs @@ -149,14 +149,20 @@ async fn execute_regular_action( "export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers && \ export DISPLAY={display} && \ mkdir -p /tmp && echo '{}' | base64 -d > /tmp/run_browser.py && \ + if python3 -c 'import socket; s = socket.socket(); s.connect((\"127.0.0.1\", 9222))' >/dev/null 2>&1; then \ + if ps aux 2>/dev/null | grep -v grep | grep -q 'disable-software-rasterizer'; then \ + echo 'Detected old chromium with --disable-software-rasterizer, restarting with correct flags...' && \ + pkill -f 'remote-debugging-port=9222' 2>/dev/null; sleep 1; \ + fi; \ + fi; \ if ! python3 -c 'import socket; s = socket.socket(); s.connect((\"127.0.0.1\", 9222))' >/dev/null 2>&1; then \ echo 'Starting headful chromium with remote debugging on DISPLAY={display}...' && \ if command -v chromium >/dev/null 2>&1; then \ - setsid nohup chromium --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-software-rasterizer >/tmp/headless_chrome.log 2>&1 & \ + setsid nohup env DISPLAY={display} chromium --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-dev-shm-usage --no-first-run --no-default-browser-check --window-size=1280,1024 >/tmp/headless_chrome.log 2>&1 & \ elif command -v chromium-browser >/dev/null 2>&1; then \ - setsid nohup chromium-browser --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-software-rasterizer >/tmp/headless_chrome.log 2>&1 & \ + setsid nohup env DISPLAY={display} chromium-browser --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-dev-shm-usage --no-first-run --no-default-browser-check --window-size=1280,1024 >/tmp/headless_chrome.log 2>&1 & \ elif command -v google-chrome >/dev/null 2>&1; then \ - setsid nohup google-chrome --no-sandbox --remote-debugging-port=9222 --disable-gpu >/tmp/headless_chrome.log 2>&1 & \ + setsid nohup env DISPLAY={display} google-chrome --no-sandbox --remote-debugging-port=9222 --disable-gpu --disable-dev-shm-usage --no-first-run --no-default-browser-check --window-size=1280,1024 >/tmp/headless_chrome.log 2>&1 & \ fi && \ sleep 3; \ fi; \ diff --git a/crates/flock-tools/tests/diagnose_e2b.rs b/crates/flock-tools/tests/diagnose_e2b.rs index 4ed70287..45a6a164 100644 --- a/crates/flock-tools/tests/diagnose_e2b.rs +++ b/crates/flock-tools/tests/diagnose_e2b.rs @@ -4,34 +4,40 @@ use flock_core::config::settings::SandboxConfig; #[tokio::test] async fn test_diagnose_e2b() { let db = DbManager::init().await.expect("Failed to init DB"); - // Get E2B config let cfg = flock_tools::sandbox_core::config::get_sandbox_config(&db).await .expect("No active sandbox config"); let api_key = cfg.e2b_api_key.as_ref().expect("E2B API key is missing"); - let base_url = cfg.e2b_api_url.as_deref().unwrap_or("https://api.e2b.app").trim_end_matches('/'); + println!("API Key: {}", api_key); - let client = reqwest::Client::new(); - let url = format!("{}/sandboxes", base_url); - let resp = client.get(&url) - .header("X-API-Key", api_key) - .send() - .await - .expect("Failed to get E2B sandboxes"); + // 1. 创建沙盒 + println!("Creating sandbox..."); + let sandbox_id = flock_tools::sandbox_core::e2b::lifecycle::create_sandbox(&cfg).await + .expect("Failed to create sandbox"); + println!("Sandbox ID: {}", sandbox_id); - let text = resp.text().await.unwrap_or_default(); - println!("Running E2B Sandboxes: {}", text); + // Wait a few seconds for VNC processes to fully initialize + println!("Waiting for processes to initialize..."); + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - let val: serde_json::Value = serde_json::from_str(&text).expect("Failed to parse JSON"); - if let Some(arr) = val.as_array() { - for sb in arr { - if let Some(sandbox_id) = sb.get("sandboxID").or_else(|| sb.get("sandbox_id")).and_then(|v| v.as_str()) { - println!("Diagnosing E2B Sandbox: {}", sandbox_id); - let diag_cmd = "echo '--- websockify.log ---'; cat /tmp/websockify.log; echo '--- x11vnc.log ---'; cat /tmp/x11vnc.log"; - if let Ok((stdout, _exit_code)) = flock_tools::sandbox_core::e2b::exec::execute_command(&cfg, sandbox_id, diag_cmd).await { - println!("Diagnostics output:\n{}", stdout); - } - } - } + // Check logs and processes using execute_command helper + println!("--- Process status ---"); + let check_ps = "ps aux | grep -E 'Xvfb|fluxbox|x11vnc|websockify'"; + match flock_tools::sandbox_core::e2b::exec::execute_command(&cfg, &sandbox_id, check_ps).await { + Ok((stdout, code)) => println!("Exit code: {}\nStdout:\n{}", code, stdout), + Err(e) => println!("Error running ps check: {:?}", e), } + + // Check logs + let diag_cmd = "echo '=== xvfb.log ==='; cat /tmp/xvfb.log; echo '=== fluxbox.log ==='; cat /tmp/x11vnc.log; echo '=== websockify.log ==='; cat /tmp/websockify.log"; + println!("--- Logs ---"); + match flock_tools::sandbox_core::e2b::exec::execute_command(&cfg, &sandbox_id, diag_cmd).await { + Ok((stdout, code)) => println!("Exit code: {}\nStdout:\n{}", code, stdout), + Err(e) => println!("Error running logs check: {:?}", e), + } + + // Destroy the sandbox + println!("Destroying sandbox {}...", sandbox_id); + let _ = flock_tools::sandbox_core::e2b::lifecycle::destroy_sandbox(&cfg, &sandbox_id).await; + println!("Done."); } diff --git a/flock-ui/src-tauri/src/commands/common/sandbox.rs b/flock-ui/src-tauri/src/commands/common/sandbox.rs index 50e20862..1b2afebd 100644 --- a/flock-ui/src-tauri/src/commands/common/sandbox.rs +++ b/flock-ui/src-tauri/src/commands/common/sandbox.rs @@ -43,7 +43,7 @@ pub async fn get_active_sandbox_vnc_url( let provider = flock_tools::sandbox_core::manager::get_provider(provider_name); provider.get_vnc_url(&*db, &cfg, &sandbox_id).await.unwrap_or_else(|_| { if provider_name == "e2b" { - format!("https://6080-{}.e2b.app/vnc.html?autoconnect=true&resize=scale&skip-preview-warning=true&skip_preview_warning=true", sandbox_id) + format!("https://6080-{}.e2b.app/vnc.html?autoconnect=true&resize=scale&path=websockify", sandbox_id) } else { format!("https://6080-{}.proxy.app.daytona.io/vnc.html?autoconnect=true&resize=scale", sandbox_id) } diff --git a/flock-ui/src/components/Settings/SandboxSettings/hooks/useSandboxSettings.ts b/flock-ui/src/components/Settings/SandboxSettings/hooks/useSandboxSettings.ts index 63caed7a..9e993147 100644 --- a/flock-ui/src/components/Settings/SandboxSettings/hooks/useSandboxSettings.ts +++ b/flock-ui/src/components/Settings/SandboxSettings/hooks/useSandboxSettings.ts @@ -63,6 +63,17 @@ export function useSandboxSettings() { loadAll(); }, []); + const handleProviderChange = (newProvider: 'e2b' | 'daytona' | 'local') => { + if (newProvider !== provider) { + // 切换 provider 时清空 snapshot,避免将上一个 provider 的 snapshot ID 错误地 + // 传给新 provider(如把 Daytona workspace UUID 当 E2B template ID 使用) + setSnapshot(''); + // 同时在后台销毁旧沙盒,清除内存中的 ACTIVE_SANDBOX_ID + invoke('destroy_sandbox').catch(() => {}); + } + setProvider(newProvider); + }; + useEffect(() => { fetchSnapshotsList(); }, [provider, e2bApiKey]); @@ -275,7 +286,7 @@ export function useSandboxSettings() { }; return { - provider, setProvider, + provider, setProvider: handleProviderChange, apiUrl, setApiUrl, apiKey, setApiKey, e2bApiKey, setE2bApiKey, diff --git a/flock-ui/src/pages/Workspace/components/EnvironmentPanel/components/VncView.tsx b/flock-ui/src/pages/Workspace/components/EnvironmentPanel/components/VncView.tsx index d46c2936..bce2be94 100644 --- a/flock-ui/src/pages/Workspace/components/EnvironmentPanel/components/VncView.tsx +++ b/flock-ui/src/pages/Workspace/components/EnvironmentPanel/components/VncView.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { Box } from '@mantine/core'; import { ImageView } from '@/pages/Workspace/components/EnvironmentPanel/ImageView'; import { useWorkspaceStore } from '@/store/workspaceStore'; @@ -121,21 +122,7 @@ export function VncView({ {!isPlaybackMode && activeTab === 'vnc' && !isOfflineMode && ( -