Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
54d3033
feat: add E2B sandbox provider and multi-provider selection in settings
Onelevenvy Jun 26, 2026
b057df3
feat: design daytona and local providers and fix E2B test URL with X-…
Onelevenvy Jun 26, 2026
e58d8d1
fix: resolve E2B sandbox command execution 404 and template creation …
Onelevenvy Jun 26, 2026
6d16104
feat: pre-populate official E2B templates in list_daytona_snapshots c…
Onelevenvy Jun 26, 2026
46fad35
refactor(sandbox): extract sandbox manager and implement unified disp…
Onelevenvy Jun 27, 2026
4ba5982
refactor(sandbox): implement strategy pattern for SandboxProvider and…
Onelevenvy Jun 27, 2026
9617381
feat(sandbox): implement auto-provisioning for E2B enhanced custom te…
Onelevenvy Jun 27, 2026
568c3b1
chore: move local_provider into sandbox directory
Onelevenvy Jun 27, 2026
eed693f
chore: move sandbox config out of daytona module into sandbox_core
Onelevenvy Jun 27, 2026
bb0ea78
feat: add E2B API endpoint URL config and fix sandbox snapshot settin…
Onelevenvy Jun 27, 2026
7b93970
fix: resolve char boundary panic, segmented control ts error and e2b …
Onelevenvy Jun 27, 2026
c712021
style: remove E2B Set as Default button and align action button layou…
Onelevenvy Jun 27, 2026
6b73707
feat: migrate E2B builder to template create API for cloud builds wit…
Onelevenvy Jun 27, 2026
d255665
style: unify snapshot creation button styles and update E2B default t…
Onelevenvy Jun 27, 2026
5488b5d
fix: fix E2B template status rendering and pass active provider durin…
Onelevenvy Jun 27, 2026
c19180b
fix: switch to USER root in E2B Dockerfile to prevent sudo privilege …
Onelevenvy Jun 27, 2026
b2e6e4b
fix: generate and copy start_command.sh into E2B template during buil…
Onelevenvy Jun 27, 2026
f893d76
fix: install missing fluxbox and websockify packages in E2B template …
Onelevenvy Jun 27, 2026
21f95a0
fix: resolve E2B list templates 401 unauthorized caused by masked API…
Onelevenvy Jun 27, 2026
f85f75c
feat: hardcode public E2B template ID jj17rl441iqdc6ea5mkb as default…
Onelevenvy Jun 27, 2026
f948018
fix: add --ignore-origin to websockify to bypass VNC connection 502 B…
Onelevenvy Jun 27, 2026
aedd5cd
refactor: decouple daytona and e2b sandboxes and genericize VNC and s…
Onelevenvy Jul 1, 2026
1e2208b
fix: resolve compilation errors from sandbox decoupling refactor
Onelevenvy Jul 1, 2026
8d901b5
chore:bump langgraph-rust version to 0.2.5
Onelevenvy Jul 1, 2026
6cc9a84
chore:bump langgraph-rust version to 0.2.5
Onelevenvy Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ flock-agent = { path = "crates/flock-agent" }
flock-workflow = { path = "crates/flock-workflow" }

# LangGraph
langgraph = { version = "0.2.4", features = ["prebuilt", "providers", "sqlite"] }
langgraph = { version = "0.2.5", features = ["prebuilt", "providers", "sqlite"] }

tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
Expand Down
2 changes: 1 addition & 1 deletion crates/flock-agent/src/agent_setup_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use super::sinks::OutputSink;
/// when the global sandbox is configured.
pub async fn filter_sandbox_tools(config: &Config, registry: &mut ToolRegistry) {
let is_sandbox_configured = if let Some(db) = &config.db_manager {
flock_tools::daytona::get_sandbox_config(db).await.is_some()
flock_tools::sandbox_core::config::get_sandbox_config(db).await.is_some()
} else {
false
};
Expand Down
11 changes: 11 additions & 0 deletions crates/flock-core/src/config/settings/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,24 @@ impl Default for SessionConfig {
pub struct SandboxConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_sandbox_provider")]
pub provider: Option<String>,
pub api_url: Option<String>,
pub api_key: Option<String>,
pub api_key_encrypted: Option<String>,
pub api_key_nonce: Option<String>,
pub e2b_api_key: Option<String>,
pub e2b_api_key_encrypted: Option<String>,
pub e2b_api_key_nonce: Option<String>,
pub e2b_api_url: Option<String>,
pub snapshot: Option<String>,
}

pub fn default_sandbox_provider() -> Option<String> {
Some("e2b".to_string())
}


// --- Default value functions ---
pub fn default_provider() -> String {
"anthropic".to_string()
Expand Down
3 changes: 2 additions & 1 deletion crates/flock-tools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ pub mod adapter;
pub mod file_cache;
pub mod tool_executor;

pub mod daytona;
pub mod sandbox_core;
pub mod registry;
pub mod tools;


pub use tools::{baidu, builtin, google, math, mcp, openweather, sandbox, serper};

/// Snapshot of all registered tools and their provider metadata.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,47 @@ use flock_core::config::settings::SandboxConfig;
/// 获取当前启用的沙盒配置。若未启用或未配置,则返回 None。
pub async fn get_sandbox_config(db: &DbManager) -> Option<SandboxConfig> {
let mut cfg: SandboxConfig = db.get_config("sandbox").await?;

// Decrypt Daytona key
if let (Some(ct), Some(n)) = (&cfg.api_key_encrypted, &cfg.api_key_nonce) {
if let Ok(salt) = db.get_or_create_salt().await {
if let Ok(decrypted) = flock_core::crypto::decrypt_value(ct, n, &salt) {
cfg.api_key = Some(decrypted);
}
}
}
if cfg.enabled && cfg.api_url.is_some() && cfg.api_key.is_some() {
Some(cfg)
} else {
None

// Decrypt E2B key
if let (Some(ct), Some(n)) = (&cfg.e2b_api_key_encrypted, &cfg.e2b_api_key_nonce) {
if let Ok(salt) = db.get_or_create_salt().await {
if let Ok(decrypted) = flock_core::crypto::decrypt_value(ct, n, &salt) {
cfg.e2b_api_key = Some(decrypted);
}
}
}

if !cfg.enabled {
return None;
}

let provider = cfg.provider.as_deref().unwrap_or("e2b");
match provider {
"e2b" => {
if cfg.e2b_api_key.is_some() {
Some(cfg)
} else {
None
}
}
"daytona" => {
if cfg.api_url.is_some() && cfg.api_key.is_some() {
Some(cfg)
} else {
None
}
}
"local" => Some(cfg),
_ => None,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use flock_core::db::DbManager;
use crate::daytona::config::get_sandbox_config;
use crate::sandbox_core::config::get_sandbox_config;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaytonaSandboxResponse {
Expand All @@ -22,26 +22,24 @@ struct ExecuteResponse {
pub exit_code: Option<i32>,
}

/// 在沙盒中执行指令,带有端点兼容重试机制
/// 在 Daytona 沙盒中执行指令(通过 Toolbox API),带端点兼容重试机制
pub async fn execute_command_in_sandbox(
db: &DbManager,
sandbox_id: &str,
command: &str,
) -> anyhow::Result<(String, i32)> {
let cfg = get_sandbox_config(db).await
.ok_or_else(|| anyhow::anyhow!("云端 Daytona 沙箱未配置或未启用"))?;
.ok_or_else(|| anyhow::anyhow!("Daytona 沙箱未配置或未启用"))?;

let api_url = cfg.api_url.as_ref().unwrap().trim_end_matches('/');
let api_key = cfg.api_key.as_ref().unwrap();

// 生成 Toolbox 执行请求 of URL list

let urls = if api_url.contains("app.daytona.io") {
vec!(
format!("https://proxy.app.daytona.io/toolbox/{}/toolbox/process/execute", sandbox_id),
format!("https://proxy.app.daytona.io/toolbox/{}/process/execute", sandbox_id),
)
} else {
// 自建模式
let base = api_url.trim_end_matches("/api").trim_end_matches("/");
vec!(
format!("{}/toolbox/{}/toolbox/process/execute", base, sandbox_id),
Expand Down Expand Up @@ -80,12 +78,10 @@ pub async fn execute_command_in_sandbox(
return Err(anyhow::anyhow!("解析执行响应失败。原始响应体: {}", resp_text));
}
} else if status == reqwest::StatusCode::NOT_FOUND {
// 如果 404,我们尝试下一个候选 endpoint
last_error = Some(anyhow::anyhow!("Toolbox API 返回 404: {}", url));
continue;
} else if status.is_server_error() || status.as_u16() == 502 || status.as_u16() == 503 {
last_error = Some(anyhow::anyhow!("Toolbox API 响应服务端错误 ({}): {}", url, status));
// 可能是沙盒内的 agent 还没完全起来,继续重试
} else {
let err_body = resp.text().await.unwrap_or_default();
return Err(anyhow::anyhow!("Toolbox API 响应失败 ({}): {}", url, err_body));
Expand All @@ -96,8 +92,7 @@ pub async fn execute_command_in_sandbox(
}
}
}

// 如果不是最后一次尝试,等待一小段时间再重试

if attempt < max_retries {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use flock_core::db::DbManager;
use crate::daytona::{execute_command_in_sandbox, get_or_create_active_sandbox};
use crate::sandbox_core::daytona::execute_command_in_sandbox;
use crate::sandbox_core::manager::get_or_create_active_sandbox;
use serde::{Deserialize, Serialize};
use std::path::{Component, Path};

Expand Down
Loading
Loading