refactor: remove sandbox system and simplify agent architecture

Remove the entire sandbox security system including:
- All sandbox-related Rust code and dependencies (gaol crate)
- Sandbox command handlers and platform-specific implementations
- Comprehensive test suite for sandbox functionality
- Agent sandbox settings UI components

Simplify agent configuration by removing sandbox and permission fields:
- Remove sandbox_enabled, enable_file_read, enable_file_write, enable_network from agent configs
- Update all CC agents to use simplified configuration format
- Remove sandbox references from documentation and UI
This commit is contained in:
Vivek R
2025-07-02 19:17:38 +05:30
parent 124fe1544f
commit 2dfdf31b83
47 changed files with 115 additions and 7774 deletions

View File

@@ -1,4 +1,3 @@
use crate::sandbox::profile::ProfileBuilder;
use anyhow::Result;
use chrono;
use log::{debug, error, info, warn};
@@ -28,7 +27,6 @@ pub struct Agent {
pub system_prompt: String,
pub default_task: Option<String>,
pub model: String,
pub sandbox_enabled: bool,
pub enable_file_read: bool,
pub enable_file_write: bool,
pub enable_network: bool,
@@ -88,10 +86,6 @@ pub struct AgentData {
pub system_prompt: String,
pub default_task: Option<String>,
pub model: String,
pub sandbox_enabled: bool,
pub enable_file_read: bool,
pub enable_file_write: bool,
pub enable_network: bool,
}
/// Database connection state
@@ -235,7 +229,6 @@ pub fn init_database(app: &AppHandle) -> SqliteResult<Connection> {
system_prompt TEXT NOT NULL,
default_task TEXT,
model TEXT NOT NULL DEFAULT 'sonnet',
sandbox_enabled BOOLEAN NOT NULL DEFAULT 1,
enable_file_read BOOLEAN NOT NULL DEFAULT 1,
enable_file_write BOOLEAN NOT NULL DEFAULT 1,
enable_network BOOLEAN NOT NULL DEFAULT 0,
@@ -251,14 +244,6 @@ pub fn init_database(app: &AppHandle) -> SqliteResult<Connection> {
"ALTER TABLE agents ADD COLUMN model TEXT DEFAULT 'sonnet'",
[],
);
let _ = conn.execute(
"ALTER TABLE agents ADD COLUMN sandbox_profile_id INTEGER REFERENCES sandbox_profiles(id)",
[],
);
let _ = conn.execute(
"ALTER TABLE agents ADD COLUMN sandbox_enabled BOOLEAN DEFAULT 1",
[],
);
let _ = conn.execute(
"ALTER TABLE agents ADD COLUMN enable_file_read BOOLEAN DEFAULT 1",
[],
@@ -329,75 +314,6 @@ pub fn init_database(app: &AppHandle) -> SqliteResult<Connection> {
[],
)?;
// Create sandbox profiles table
conn.execute(
"CREATE TABLE IF NOT EXISTS sandbox_profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT 0,
is_default BOOLEAN NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)",
[],
)?;
// Create sandbox rules table
conn.execute(
"CREATE TABLE IF NOT EXISTS sandbox_rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
profile_id INTEGER NOT NULL,
operation_type TEXT NOT NULL,
pattern_type TEXT NOT NULL,
pattern_value TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
platform_support TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (profile_id) REFERENCES sandbox_profiles(id) ON DELETE CASCADE
)",
[],
)?;
// Create trigger to update sandbox profile timestamp
conn.execute(
"CREATE TRIGGER IF NOT EXISTS update_sandbox_profile_timestamp
AFTER UPDATE ON sandbox_profiles
FOR EACH ROW
BEGIN
UPDATE sandbox_profiles SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END",
[],
)?;
// Create sandbox violations table
conn.execute(
"CREATE TABLE IF NOT EXISTS sandbox_violations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
profile_id INTEGER,
agent_id INTEGER,
agent_run_id INTEGER,
operation_type TEXT NOT NULL,
pattern_value TEXT,
process_name TEXT,
pid INTEGER,
denied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (profile_id) REFERENCES sandbox_profiles(id) ON DELETE CASCADE,
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
FOREIGN KEY (agent_run_id) REFERENCES agent_runs(id) ON DELETE CASCADE
)",
[],
)?;
// Create index for efficient querying
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_sandbox_violations_denied_at
ON sandbox_violations(denied_at DESC)",
[],
)?;
// Create default sandbox profiles if they don't exist
crate::sandbox::defaults::create_default_profiles(&conn)?;
// Create settings table for app-wide settings
conn.execute(
@@ -430,7 +346,7 @@ pub async fn list_agents(db: State<'_, AgentDb>) -> Result<Vec<Agent>, String> {
let conn = db.0.lock().map_err(|e| e.to_string())?;
let mut stmt = conn
.prepare("SELECT id, name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents ORDER BY created_at DESC")
.prepare("SELECT id, name, icon, system_prompt, default_task, model, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents ORDER BY created_at DESC")
.map_err(|e| e.to_string())?;
let agents = stmt
@@ -444,12 +360,11 @@ pub async fn list_agents(db: State<'_, AgentDb>) -> Result<Vec<Agent>, String> {
model: row
.get::<_, String>(5)
.unwrap_or_else(|_| "sonnet".to_string()),
sandbox_enabled: row.get::<_, bool>(6).unwrap_or(true),
enable_file_read: row.get::<_, bool>(7).unwrap_or(true),
enable_file_write: row.get::<_, bool>(8).unwrap_or(true),
enable_network: row.get::<_, bool>(9).unwrap_or(false),
created_at: row.get(10)?,
updated_at: row.get(11)?,
enable_file_read: row.get::<_, bool>(6).unwrap_or(true),
enable_file_write: row.get::<_, bool>(7).unwrap_or(true),
enable_network: row.get::<_, bool>(8).unwrap_or(false),
created_at: row.get(9)?,
updated_at: row.get(10)?,
})
})
.map_err(|e| e.to_string())?
@@ -468,21 +383,19 @@ pub async fn create_agent(
system_prompt: String,
default_task: Option<String>,
model: Option<String>,
sandbox_enabled: Option<bool>,
enable_file_read: Option<bool>,
enable_file_write: Option<bool>,
enable_network: Option<bool>,
) -> Result<Agent, String> {
let conn = db.0.lock().map_err(|e| e.to_string())?;
let model = model.unwrap_or_else(|| "sonnet".to_string());
let sandbox_enabled = sandbox_enabled.unwrap_or(true);
let enable_file_read = enable_file_read.unwrap_or(true);
let enable_file_write = enable_file_write.unwrap_or(true);
let enable_network = enable_network.unwrap_or(false);
conn.execute(
"INSERT INTO agents (name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
params![name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network],
"INSERT INTO agents (name, icon, system_prompt, default_task, model, enable_file_read, enable_file_write, enable_network) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
params![name, icon, system_prompt, default_task, model, enable_file_read, enable_file_write, enable_network],
)
.map_err(|e| e.to_string())?;
@@ -491,7 +404,7 @@ pub async fn create_agent(
// Fetch the created agent
let agent = conn
.query_row(
"SELECT id, name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
"SELECT id, name, icon, system_prompt, default_task, model, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
params![id],
|row| {
Ok(Agent {
@@ -501,12 +414,11 @@ pub async fn create_agent(
system_prompt: row.get(3)?,
default_task: row.get(4)?,
model: row.get(5)?,
sandbox_enabled: row.get(6)?,
enable_file_read: row.get(7)?,
enable_file_write: row.get(8)?,
enable_network: row.get(9)?,
created_at: row.get(10)?,
updated_at: row.get(11)?,
enable_file_read: row.get(6)?,
enable_file_write: row.get(7)?,
enable_network: row.get(8)?,
created_at: row.get(9)?,
updated_at: row.get(10)?,
})
},
)
@@ -525,7 +437,6 @@ pub async fn update_agent(
system_prompt: String,
default_task: Option<String>,
model: Option<String>,
sandbox_enabled: Option<bool>,
enable_file_read: Option<bool>,
enable_file_write: Option<bool>,
enable_network: Option<bool>,
@@ -546,11 +457,6 @@ pub async fn update_agent(
];
let mut param_count = 5;
if let Some(se) = sandbox_enabled {
param_count += 1;
query.push_str(&format!(", sandbox_enabled = ?{}", param_count));
params_vec.push(Box::new(se));
}
if let Some(efr) = enable_file_read {
param_count += 1;
query.push_str(&format!(", enable_file_read = ?{}", param_count));
@@ -580,7 +486,7 @@ pub async fn update_agent(
// Fetch the updated agent
let agent = conn
.query_row(
"SELECT id, name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
"SELECT id, name, icon, system_prompt, default_task, model, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
params![id],
|row| {
Ok(Agent {
@@ -590,12 +496,11 @@ pub async fn update_agent(
system_prompt: row.get(3)?,
default_task: row.get(4)?,
model: row.get(5)?,
sandbox_enabled: row.get(6)?,
enable_file_read: row.get(7)?,
enable_file_write: row.get(8)?,
enable_network: row.get(9)?,
created_at: row.get(10)?,
updated_at: row.get(11)?,
enable_file_read: row.get(6)?,
enable_file_write: row.get(7)?,
enable_network: row.get(8)?,
created_at: row.get(9)?,
updated_at: row.get(10)?,
})
},
)
@@ -622,7 +527,7 @@ pub async fn get_agent(db: State<'_, AgentDb>, id: i64) -> Result<Agent, String>
let agent = conn
.query_row(
"SELECT id, name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
"SELECT id, name, icon, system_prompt, default_task, model, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
params![id],
|row| {
Ok(Agent {
@@ -632,12 +537,11 @@ pub async fn get_agent(db: State<'_, AgentDb>, id: i64) -> Result<Agent, String>
system_prompt: row.get(3)?,
default_task: row.get(4)?,
model: row.get::<_, String>(5).unwrap_or_else(|_| "sonnet".to_string()),
sandbox_enabled: row.get::<_, bool>(6).unwrap_or(true),
enable_file_read: row.get::<_, bool>(7).unwrap_or(true),
enable_file_write: row.get::<_, bool>(8).unwrap_or(true),
enable_network: row.get::<_, bool>(9).unwrap_or(false),
created_at: row.get(10)?,
updated_at: row.get(11)?,
enable_file_read: row.get::<_, bool>(6).unwrap_or(true),
enable_file_write: row.get::<_, bool>(7).unwrap_or(true),
enable_network: row.get::<_, bool>(8).unwrap_or(false),
created_at: row.get(9)?,
updated_at: row.get(10)?,
})
},
)
@@ -788,411 +692,30 @@ pub async fn execute_agent(
conn.last_insert_rowid()
};
// Create sandbox rules based on agent-specific permissions (no database dependency)
let sandbox_profile = if !agent.sandbox_enabled {
info!("🔓 Agent '{}': Sandbox DISABLED", agent.name);
None
} else {
info!(
"🔒 Agent '{}': Sandbox enabled | File Read: {} | File Write: {} | Network: {}",
agent.name, agent.enable_file_read, agent.enable_file_write, agent.enable_network
);
// Create rules dynamically based on agent permissions
let mut rules = Vec::new();
// Add file read rules if enabled
if agent.enable_file_read {
// Project directory access
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(1),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "{{PROJECT_PATH}}".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos", "windows"]"#.to_string()),
created_at: String::new(),
});
// System libraries (for language runtimes, etc.)
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(2),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/usr/lib".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos"]"#.to_string()),
created_at: String::new(),
});
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(3),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/usr/local/lib".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos"]"#.to_string()),
created_at: String::new(),
});
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(4),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/System/Library".to_string(),
enabled: true,
platform_support: Some(r#"["macos"]"#.to_string()),
created_at: String::new(),
});
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(5),
profile_id: 0,
operation_type: "file_read_metadata".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/".to_string(),
enabled: true,
platform_support: Some(r#"["macos"]"#.to_string()),
created_at: String::new(),
});
}
// Add network rules if enabled
if agent.enable_network {
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(6),
profile_id: 0,
operation_type: "network_outbound".to_string(),
pattern_type: "all".to_string(),
pattern_value: "".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos"]"#.to_string()),
created_at: String::new(),
});
}
// Always add essential system paths (needed for executables to run)
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(7),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/usr/bin".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos"]"#.to_string()),
created_at: String::new(),
});
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(8),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/opt/homebrew/bin".to_string(),
enabled: true,
platform_support: Some(r#"["macos"]"#.to_string()),
created_at: String::new(),
});
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(9),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/usr/local/bin".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos"]"#.to_string()),
created_at: String::new(),
});
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(10),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/bin".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos"]"#.to_string()),
created_at: String::new(),
});
// System libraries (needed for executables to link)
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(11),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/usr/lib".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos"]"#.to_string()),
created_at: String::new(),
});
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(12),
profile_id: 0,
operation_type: "file_read_all".to_string(),
pattern_type: "subpath".to_string(),
pattern_value: "/System/Library".to_string(),
enabled: true,
platform_support: Some(r#"["macos"]"#.to_string()),
created_at: String::new(),
});
// Always add system info reading (minimal requirement)
rules.push(crate::sandbox::profile::SandboxRule {
id: Some(13),
profile_id: 0,
operation_type: "system_info_read".to_string(),
pattern_type: "all".to_string(),
pattern_value: "".to_string(),
enabled: true,
platform_support: Some(r#"["linux", "macos"]"#.to_string()),
created_at: String::new(),
});
Some(("Agent-specific".to_string(), rules))
};
// Build the command
let mut cmd = if let Some((_profile_name, rules)) = sandbox_profile {
info!("🧪 DEBUG: Testing Claude command first without sandbox...");
// Quick test to see if Claude is accessible at all
let claude_path = match find_claude_binary(&app) {
Ok(path) => path,
Err(e) => {
error!("❌ Claude binary not found: {}", e);
return Err(e);
}
};
match std::process::Command::new(&claude_path)
.arg("--version")
.output()
{
Ok(output) => {
if output.status.success() {
info!(
"✅ Claude command works: {}",
String::from_utf8_lossy(&output.stdout).trim()
);
} else {
warn!("⚠️ Claude command failed with status: {}", output.status);
warn!(" stdout: {}", String::from_utf8_lossy(&output.stdout));
warn!(" stderr: {}", String::from_utf8_lossy(&output.stderr));
}
}
Err(e) => {
error!("❌ Claude command not found or not executable: {}", e);
error!(" This could be why the agent is failing to start");
}
info!("Running agent '{}'", agent.name);
let claude_path = match find_claude_binary(&app) {
Ok(path) => path,
Err(e) => {
error!("Failed to find claude binary: {}", e);
return Err(e);
}
// Test if Claude can actually start a session (this might reveal auth issues)
info!("🧪 Testing Claude with exact same arguments as agent (without sandbox env vars)...");
let mut test_cmd = std::process::Command::new(&claude_path);
test_cmd
.arg("-p")
.arg(&task)
.arg("--system-prompt")
.arg(&agent.system_prompt)
.arg("--model")
.arg(&execution_model)
.arg("--output-format")
.arg("stream-json")
.arg("--verbose")
.arg("--dangerously-skip-permissions")
.current_dir(&project_path);
info!("🧪 Testing command: claude -p \"{}\" --system-prompt \"{}\" --model {} --output-format stream-json --verbose --dangerously-skip-permissions",
task, agent.system_prompt, execution_model);
// Start the test process and give it 5 seconds to produce output
match test_cmd.spawn() {
Ok(mut child) => {
// Wait for 5 seconds to see if it produces output
let start = std::time::Instant::now();
let mut output_received = false;
while start.elapsed() < std::time::Duration::from_secs(5) {
match child.try_wait() {
Ok(Some(status)) => {
info!("🧪 Test process exited with status: {}", status);
output_received = true;
break;
}
Ok(None) => {
// Still running
std::thread::sleep(std::time::Duration::from_millis(100));
}
Err(e) => {
warn!("🧪 Error checking test process: {}", e);
break;
}
}
}
if !output_received {
warn!("🧪 Test process is still running after 5 seconds - this suggests Claude might be waiting for input");
// Kill the test process
let _ = child.kill();
let _ = child.wait();
} else {
info!("🧪 Test process completed quickly - command seems to work");
}
}
Err(e) => {
error!("❌ Failed to spawn test Claude process: {}", e);
}
}
info!("🧪 End of Claude test, proceeding with sandbox...");
// Build the gaol profile using agent-specific permissions
let project_path_buf = PathBuf::from(&project_path);
match ProfileBuilder::new(project_path_buf.clone()) {
Ok(builder) => {
// Build agent-specific profile with permission filtering
match builder.build_agent_profile(
rules,
agent.sandbox_enabled,
agent.enable_file_read,
agent.enable_file_write,
agent.enable_network,
) {
Ok(build_result) => {
// Create the enhanced sandbox executor
#[cfg(unix)]
let executor =
crate::sandbox::executor::SandboxExecutor::new_with_serialization(
build_result.profile,
project_path_buf.clone(),
build_result.serialized,
);
#[cfg(not(unix))]
let executor =
crate::sandbox::executor::SandboxExecutor::new_with_serialization(
(),
project_path_buf.clone(),
build_result.serialized,
);
// Prepare the sandboxed command
let args = vec![
"-p",
&task,
"--system-prompt",
&agent.system_prompt,
"--model",
&execution_model,
"--output-format",
"stream-json",
"--verbose",
"--dangerously-skip-permissions",
];
let claude_path = match find_claude_binary(&app) {
Ok(path) => path,
Err(e) => {
error!("Failed to find claude binary: {}", e);
return Err(e);
}
};
executor.prepare_sandboxed_command(&claude_path, &args, &project_path_buf)
}
Err(e) => {
error!("Failed to build agent-specific sandbox profile: {}, falling back to non-sandboxed", e);
let claude_path = match find_claude_binary(&app) {
Ok(path) => path,
Err(e) => {
error!("Failed to find claude binary: {}", e);
return Err(e);
}
};
let mut cmd = create_command_with_env(&claude_path);
cmd.arg("-p")
.arg(&task)
.arg("--system-prompt")
.arg(&agent.system_prompt)
.arg("--model")
.arg(&execution_model)
.arg("--output-format")
.arg("stream-json")
.arg("--verbose")
.arg("--dangerously-skip-permissions")
.current_dir(&project_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
cmd
}
}
}
Err(e) => {
error!(
"Failed to create ProfileBuilder: {}, falling back to non-sandboxed",
e
);
// Fall back to non-sandboxed command
let claude_path = match find_claude_binary(&app) {
Ok(path) => path,
Err(e) => {
error!("Failed to find claude binary: {}", e);
return Err(e);
}
};
let mut cmd = create_command_with_env(&claude_path);
cmd.arg("-p")
.arg(&task)
.arg("--system-prompt")
.arg(&agent.system_prompt)
.arg("--model")
.arg(&execution_model)
.arg("--output-format")
.arg("stream-json")
.arg("--verbose")
.arg("--dangerously-skip-permissions")
.current_dir(&project_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
cmd
}
}
} else {
// No sandbox or sandbox disabled, use regular command
warn!(
"🚨 Running agent '{}' WITHOUT SANDBOX - full system access!",
agent.name
);
let claude_path = match find_claude_binary(&app) {
Ok(path) => path,
Err(e) => {
error!("Failed to find claude binary: {}", e);
return Err(e);
}
};
let mut cmd = create_command_with_env(&claude_path);
cmd.arg("-p")
.arg(&task)
.arg("--system-prompt")
.arg(&agent.system_prompt)
.arg("--model")
.arg(&execution_model)
.arg("--output-format")
.arg("stream-json")
.arg("--verbose")
.arg("--dangerously-skip-permissions")
.current_dir(&project_path)
.stdin(Stdio::null()) // Don't pipe stdin - we have no input to send
.stdout(Stdio::piped())
.stderr(Stdio::piped());
cmd
};
let mut cmd = create_command_with_env(&claude_path);
cmd.arg("-p")
.arg(&task)
.arg("--system-prompt")
.arg(&agent.system_prompt)
.arg("--model")
.arg(&execution_model)
.arg("--output-format")
.arg("stream-json")
.arg("--verbose")
.arg("--dangerously-skip-permissions")
.current_dir(&project_path)
.stdin(Stdio::null()) // Don't pipe stdin - we have no input to send
.stdout(Stdio::piped())
.stderr(Stdio::piped());
// Spawn the process
info!("🚀 Spawning Claude process...");
@@ -1385,7 +908,7 @@ pub async fn execute_agent(
warn!("⏰ TIMEOUT: No output from Claude process after 30 seconds");
warn!("💡 This usually means:");
warn!(" 1. Claude process is waiting for user input");
warn!(" 2. Sandbox permissions are too restrictive");
warn!(" 3. Claude failed to initialize but didn't report an error");
warn!(" 4. Network connectivity issues");
warn!(" 5. Authentication issues (API key not found/invalid)");
@@ -1807,7 +1330,7 @@ pub async fn export_agent(db: State<'_, AgentDb>, id: i64) -> Result<String, Str
// Fetch the agent
let agent = conn
.query_row(
"SELECT name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network FROM agents WHERE id = ?1",
"SELECT name, icon, system_prompt, default_task, model FROM agents WHERE id = ?1",
params![id],
|row| {
Ok(serde_json::json!({
@@ -1815,11 +1338,7 @@ pub async fn export_agent(db: State<'_, AgentDb>, id: i64) -> Result<String, Str
"icon": row.get::<_, String>(1)?,
"system_prompt": row.get::<_, String>(2)?,
"default_task": row.get::<_, Option<String>>(3)?,
"model": row.get::<_, String>(4)?,
"sandbox_enabled": row.get::<_, bool>(5)?,
"enable_file_read": row.get::<_, bool>(6)?,
"enable_file_write": row.get::<_, bool>(7)?,
"enable_network": row.get::<_, bool>(8)?
"model": row.get::<_, String>(4)?
}))
},
)
@@ -2010,17 +1529,13 @@ pub async fn import_agent(db: State<'_, AgentDb>, json_data: String) -> Result<A
// Create the agent
conn.execute(
"INSERT INTO agents (name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
"INSERT INTO agents (name, icon, system_prompt, default_task, model, enable_file_read, enable_file_write, enable_network) VALUES (?1, ?2, ?3, ?4, ?5, 1, 1, 0)",
params![
final_name,
agent_data.icon,
agent_data.system_prompt,
agent_data.default_task,
agent_data.model,
agent_data.sandbox_enabled,
agent_data.enable_file_read,
agent_data.enable_file_write,
agent_data.enable_network
agent_data.model
],
)
.map_err(|e| format!("Failed to create agent: {}", e))?;
@@ -2030,7 +1545,7 @@ pub async fn import_agent(db: State<'_, AgentDb>, json_data: String) -> Result<A
// Fetch the created agent
let agent = conn
.query_row(
"SELECT id, name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
"SELECT id, name, icon, system_prompt, default_task, model, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1",
params![id],
|row| {
Ok(Agent {
@@ -2040,12 +1555,11 @@ pub async fn import_agent(db: State<'_, AgentDb>, json_data: String) -> Result<A
system_prompt: row.get(3)?,
default_task: row.get(4)?,
model: row.get(5)?,
sandbox_enabled: row.get(6)?,
enable_file_read: row.get(7)?,
enable_file_write: row.get(8)?,
enable_network: row.get(9)?,
created_at: row.get(10)?,
updated_at: row.get(11)?,
enable_file_read: row.get(6)?,
enable_file_write: row.get(7)?,
enable_network: row.get(8)?,
created_at: row.get(9)?,
updated_at: row.get(10)?,
})
},
)