Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
34 changes: 22 additions & 12 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,12 @@ async fn command_gateway(port: u16, verbose: bool) -> Result<()> {
}

// Create ToolRegistryExecutor for LLMAgentBuilder
let tool_executor = Arc::new(ToolRegistryExecutor::new(tools.clone()));
let tool_executor = Arc::new(ToolRegistryExecutor::new(
tools.clone(),
"system".to_string(),
None,
None,
));

// Build system prompt
let system_prompt = context.build_system_prompt(None).await.unwrap_or_else(|_| {
Expand Down Expand Up @@ -350,18 +355,18 @@ async fn command_gateway(port: u16, verbose: bool) -> Result<()> {
let channel_manager = ChannelManager::new(&config, bus.clone());

// Initialize RBAC manager if configured (must be before channel registrations)
let rbac_manager: Option<Arc<RbacManager>> = if let Ok(Some(rbac_config)) = config.get_rbac_config() {
if rbac_config.enabled {
let workspace = config.workspace_path();
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
Some(Arc::new(RbacManager::new(rbac_config, workspace, home)))
let rbac_manager: Option<Arc<RbacManager>> =
if let Ok(Some(rbac_config)) = config.get_rbac_config() {
if rbac_config.enabled {
let workspace = config.workspace_path();
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
Some(Arc::new(RbacManager::new(rbac_config, workspace, home)))
} else {
None
}
} else {
None
}
} else {
None
};

};
// register dingtalk channel if enabled
if config.channels.dingtalk.enabled {
let dingtalk = DingTalkChannel::new(config.channels.dingtalk.clone(), bus.clone());
Expand Down Expand Up @@ -513,7 +518,12 @@ async fn command_agent(message: Option<String>, session: String) -> Result<()> {
}

// Create ToolRegistryExecutor for LLMAgentBuilder
let tool_executor = Arc::new(ToolRegistryExecutor::new(tools.clone()));
let tool_executor = Arc::new(ToolRegistryExecutor::new(
tools.clone(),
"system".to_string(),
None,
None,
));

// Build system prompt
let system_prompt = context.build_system_prompt(None).await.unwrap_or_else(|_| {
Expand Down
68 changes: 65 additions & 3 deletions core/src/agent/loop_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ pub struct AgentLoop {
temperature: Option<f32>,
/// Max tokens
max_tokens: Option<u32>,
/// Resource Limiter
resource_limiter: Option<Arc<crate::sandbox::resource::ResourceLimiter>>,
/// RBAC Manager
rbac_manager: Option<Arc<crate::rbac::manager::RbacManager>>,
}

impl AgentLoop {
Expand Down Expand Up @@ -99,6 +103,20 @@ impl AgentLoop {
// Create mofa TaskOrchestrator for subagent spawning
let task_orchestrator = Arc::new(TaskOrchestrator::with_defaults(provider.clone()));

let resource_limiter = config
.sandbox
.as_ref()
.and_then(|s| s.build_limiter())
.map(Arc::new);

let rbac_manager = config.get_rbac_config().ok().flatten().map(|rc| {
Arc::new(crate::rbac::manager::RbacManager::new(
rc,
config.workspace_path(),
dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/")),
))
});

Ok(Self {
_agent: agent,
provider,
Expand All @@ -112,6 +130,8 @@ impl AgentLoop {
default_model,
temperature,
max_tokens,
resource_limiter,
rbac_manager,
})
}

Expand All @@ -136,6 +156,20 @@ impl AgentLoop {
// Create mofa TaskOrchestrator for subagent spawning
let task_orchestrator = Arc::new(TaskOrchestrator::with_defaults(provider.clone()));

let resource_limiter = config
.sandbox
.as_ref()
.and_then(|s| s.build_limiter())
.map(Arc::new);

let rbac_manager = config.get_rbac_config().ok().flatten().map(|rc| {
Arc::new(crate::rbac::manager::RbacManager::new(
rc,
config.workspace_path(),
dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/")),
))
});

Ok(Self {
_agent: agent,
provider,
Expand All @@ -149,6 +183,8 @@ impl AgentLoop {
default_model,
temperature,
max_tokens,
resource_limiter,
rbac_manager,
})
}

Expand Down Expand Up @@ -269,8 +305,28 @@ impl AgentLoop {
} else {
Some(msg.media.clone())
};
// Determine user
let role = self.rbac_manager.as_ref().map(|manager| {
// Simplified role determination for agent loop: default to guest if not Discord/DingTalk/Feishu
// Real channels will pass correct roles later, but we need *some* role here matching the channel.
// Since AgentLoop only has channel/user_id, it can't check Discord roles directly.
// So we rely on mapping default rules or overrides.
match response_channel.as_str() {
"discord" => manager.get_role_from_discord(&msg.sender_id, &[]),
"dingtalk" => manager.get_role_from_dingtalk(&msg.sender_id, &[]),
"feishu" => manager.get_role_from_feishu(&msg.sender_id, &[]),
_ => crate::rbac::Role::Guest,
}
});

let final_content = self
.run_agent_loop(context_messages, &msg.content, media)
.run_agent_loop(
context_messages,
&msg.content,
media,
msg.sender_id.clone(),
role,
)
.await?;

// Save to session
Expand Down Expand Up @@ -306,9 +362,15 @@ impl AgentLoop {
context: Vec<ChatMessage>,
content: &str,
media: Option<Vec<String>>,
user_id: String,
role: Option<crate::rbac::Role>,
) -> Result<Option<String>> {
let tool_executor = Arc::new(ToolRegistryExecutor::new(self.tools.clone()))
as Arc<dyn mofa_sdk::llm::ToolExecutor>;
let tool_executor = Arc::new(ToolRegistryExecutor::new(
self.tools.clone(),
user_id,
self.resource_limiter.clone(),
role,
)) as Arc<dyn mofa_sdk::llm::ToolExecutor>;

let config = MofaAgentLoopConfig {
max_tool_iterations: self.max_iterations,
Expand Down
4 changes: 4 additions & 0 deletions core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use crate::error::{ConfigError, Result};
use crate::rbac::config::RbacConfig;
use crate::sandbox::config::SandboxConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tokio::fs;
Expand Down Expand Up @@ -376,6 +377,9 @@ pub struct Config {
/// RBAC configuration
#[serde(default)]
pub rbac: Option<RbacConfig>,
/// Sandbox configuration
#[serde(default)]
pub sandbox: Option<SandboxConfig>,
}

impl Config {
Expand Down
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod permissions;
pub mod provider;
pub mod python_env;
pub mod rbac;
pub mod sandbox;
pub mod session;
pub mod tools;
pub mod types;
Expand Down
133 changes: 133 additions & 0 deletions core/src/sandbox/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//! Sandbox configurations

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Global sandbox configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SandboxConfig {
#[serde(default)]
pub resource_limits: Option<ResourceLimitsConfig>,
#[serde(default)]
pub role_limits: Option<HashMap<String, RoleLimitConfig>>,
}

impl SandboxConfig {
pub fn build_limiter(&self) -> Option<super::resource::ResourceLimiter> {
self.resource_limits
.as_ref()
.map(|rc| super::resource::ResourceLimiter {
limits: super::resource::ResourceLimits {
command_timeout_seconds: rc.timeouts.command_seconds,
file_operation_timeout_seconds: rc.timeouts.file_operation_seconds,
web_request_timeout_seconds: rc.timeouts.web_request_seconds,

max_command_output_bytes: rc.sizes.max_command_output_mb * 1024 * 1024,
max_file_read_bytes: rc.sizes.max_file_read_mb * 1024 * 1024,
max_web_response_bytes: rc.sizes.max_web_response_mb * 1024 * 1024,

max_commands_per_minute: rc.rates.commands_per_minute,
max_file_ops_per_minute: rc.rates.file_ops_per_minute,
max_web_requests_per_minute: rc.rates.web_requests_per_minute,

max_concurrent_commands: rc.concurrency.max_concurrent_commands,
max_concurrent_subagents: rc.concurrency.max_concurrent_subagents,
role_limits: self.role_limits.clone().unwrap_or_default(),
},
usage: super::resource::ResourceUsageTracker::new(),
})
}
Comment on lines +15 to +45
}

/// Resource Limits configuration wrapper
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceLimitsConfig {
#[serde(default)]
pub timeouts: TimeoutsConfig,
#[serde(default)]
pub sizes: SizesConfig,
#[serde(default)]
pub rates: RatesConfig,
#[serde(default)]
pub concurrency: ConcurrencyConfig,
}

/// Time limits
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeoutsConfig {
pub command_seconds: u64,
pub file_operation_seconds: u64,
pub web_request_seconds: u64,
}

impl Default for TimeoutsConfig {
fn default() -> Self {
Self {
command_seconds: 60,
file_operation_seconds: 30,
web_request_seconds: 30,
}
}
}

/// Size limits
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SizesConfig {
pub max_command_output_mb: usize,
pub max_file_read_mb: usize,
pub max_web_response_mb: usize,
}

impl Default for SizesConfig {
fn default() -> Self {
Self {
max_command_output_mb: 1,
max_file_read_mb: 10,
max_web_response_mb: 1,
}
}
}

/// Rate limits
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RatesConfig {
pub commands_per_minute: u32,
pub file_ops_per_minute: u32,
pub web_requests_per_minute: u32,
}

impl Default for RatesConfig {
fn default() -> Self {
Self {
commands_per_minute: 30,
file_ops_per_minute: 60,
web_requests_per_minute: 30,
}
}
}

/// Concurrency limits
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConcurrencyConfig {
pub max_concurrent_commands: u32,
pub max_concurrent_subagents: u32,
}

impl Default for ConcurrencyConfig {
fn default() -> Self {
Self {
max_concurrent_commands: 3,
max_concurrent_subagents: 5,
}
}
}

/// Per-Role limits configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RoleLimitConfig {
pub commands_per_minute: Option<u32>,
pub max_concurrent_commands: Option<u32>,
pub file_ops_per_minute: Option<u32>,
pub web_requests_per_minute: Option<u32>,
pub max_concurrent_subagents: Option<u32>,
}
5 changes: 5 additions & 0 deletions core/src/sandbox/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod config;
pub mod resource;

pub use config::*;
pub use resource::*;
Loading
Loading