mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
feat: add configurable agent support via config.toml
- Add AgentConfig struct to define agent settings (name, command, args, read-only, enabled, env) - Update Config and Session to include agent configurations - Modify agent_tool.rs to use configured agents with their settings - Update slash commands to dynamically use enabled agents from config - Create comprehensive config.toml.example with agent examples - Support both configured and unconfigured agents for backward compatibility - Allow per-agent read-only override and environment variables - Pass agent configuration through Session for proper access control This allows users to: - Define which AI agents are available - Set custom commands and arguments for each agent - Control read-only/write permissions per agent - Enable/disable specific agents - Configure environment variables for agents 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
This commit is contained in:
@@ -8,6 +8,7 @@ use tokio::task::JoinHandle;
|
||||
use uuid::Uuid;
|
||||
use chrono::{DateTime, Utc, Duration};
|
||||
|
||||
use crate::config_types::AgentConfig;
|
||||
use crate::openai_tools::{JsonSchema, OpenAiTool, ResponsesApiTool};
|
||||
|
||||
// Agent status enum
|
||||
@@ -41,6 +42,8 @@ pub struct Agent {
|
||||
pub progress: Vec<String>,
|
||||
pub worktree_path: Option<String>,
|
||||
pub branch_name: Option<String>,
|
||||
#[serde(skip)]
|
||||
pub config: Option<AgentConfig>,
|
||||
}
|
||||
|
||||
// Global agent manager
|
||||
@@ -70,6 +73,52 @@ impl AgentManager {
|
||||
files: Vec<String>,
|
||||
read_only: bool,
|
||||
batch_id: Option<String>,
|
||||
) -> String {
|
||||
self.create_agent_internal(
|
||||
model,
|
||||
prompt,
|
||||
context,
|
||||
output_goal,
|
||||
files,
|
||||
read_only,
|
||||
batch_id,
|
||||
None,
|
||||
).await
|
||||
}
|
||||
|
||||
pub async fn create_agent_with_config(
|
||||
&mut self,
|
||||
model: String,
|
||||
prompt: String,
|
||||
context: Option<String>,
|
||||
output_goal: Option<String>,
|
||||
files: Vec<String>,
|
||||
read_only: bool,
|
||||
batch_id: Option<String>,
|
||||
config: AgentConfig,
|
||||
) -> String {
|
||||
self.create_agent_internal(
|
||||
model,
|
||||
prompt,
|
||||
context,
|
||||
output_goal,
|
||||
files,
|
||||
read_only,
|
||||
batch_id,
|
||||
Some(config),
|
||||
).await
|
||||
}
|
||||
|
||||
async fn create_agent_internal(
|
||||
&mut self,
|
||||
model: String,
|
||||
prompt: String,
|
||||
context: Option<String>,
|
||||
output_goal: Option<String>,
|
||||
files: Vec<String>,
|
||||
read_only: bool,
|
||||
batch_id: Option<String>,
|
||||
config: Option<AgentConfig>,
|
||||
) -> String {
|
||||
let agent_id = Uuid::new_v4().to_string();
|
||||
|
||||
@@ -91,6 +140,7 @@ impl AgentManager {
|
||||
progress: Vec::new(),
|
||||
worktree_path: None,
|
||||
branch_name: None,
|
||||
config: config.clone(),
|
||||
};
|
||||
|
||||
self.agents.insert(agent_id.clone(), agent.clone());
|
||||
@@ -98,7 +148,7 @@ impl AgentManager {
|
||||
// Spawn async agent
|
||||
let agent_id_clone = agent_id.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
execute_agent(agent_id_clone).await;
|
||||
execute_agent(agent_id_clone, config).await;
|
||||
});
|
||||
|
||||
self.handles.insert(agent_id.clone(), handle);
|
||||
@@ -290,7 +340,7 @@ async fn setup_worktree(git_root: &Path, branch_id: &str) -> Result<PathBuf, Str
|
||||
Ok(worktree_path)
|
||||
}
|
||||
|
||||
async fn execute_agent(agent_id: String) {
|
||||
async fn execute_agent(agent_id: String, config: Option<AgentConfig>) {
|
||||
let mut manager = AGENT_MANAGER.write().await;
|
||||
|
||||
// Get agent details
|
||||
@@ -343,7 +393,7 @@ async fn execute_agent(agent_id: String) {
|
||||
drop(manager);
|
||||
|
||||
// Execute with full permissions in the worktree
|
||||
execute_model_with_permissions(&model, &full_prompt, false, Some(worktree_path)).await
|
||||
execute_model_with_permissions(&model, &full_prompt, false, Some(worktree_path), config.clone()).await
|
||||
}
|
||||
Err(e) => Err(format!("Failed to setup worktree: {}", e))
|
||||
}
|
||||
@@ -353,7 +403,7 @@ async fn execute_agent(agent_id: String) {
|
||||
} else {
|
||||
// Execute in read-only mode
|
||||
full_prompt = format!("{}\n\n[Running in read-only mode - no modifications allowed]", full_prompt);
|
||||
execute_model_with_permissions(&model, &full_prompt, true, None).await
|
||||
execute_model_with_permissions(&model, &full_prompt, true, None, config).await
|
||||
};
|
||||
|
||||
// Update result
|
||||
@@ -365,17 +415,47 @@ async fn execute_model_with_permissions(
|
||||
model: &str,
|
||||
prompt: &str,
|
||||
read_only: bool,
|
||||
working_dir: Option<PathBuf>
|
||||
working_dir: Option<PathBuf>,
|
||||
config: Option<AgentConfig>
|
||||
) -> Result<String, String> {
|
||||
let mut cmd = Command::new(model.to_lowercase());
|
||||
// Use config command if provided, otherwise use model name
|
||||
let command = if let Some(ref cfg) = config {
|
||||
cfg.command.clone()
|
||||
} else {
|
||||
model.to_lowercase()
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(command.clone());
|
||||
|
||||
// Set working directory if provided
|
||||
if let Some(dir) = working_dir {
|
||||
cmd.current_dir(dir);
|
||||
}
|
||||
|
||||
// Add environment variables from config if provided
|
||||
if let Some(ref cfg) = config {
|
||||
if let Some(ref env) = cfg.env {
|
||||
for (key, value) in env {
|
||||
cmd.env(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any configured args first
|
||||
for arg in &cfg.args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Build command based on model and permissions
|
||||
match model.to_lowercase().as_str() {
|
||||
// Use command instead of model for matching if config provided
|
||||
let model_lower = model.to_lowercase();
|
||||
let model_name = if config.is_some() {
|
||||
command.as_str()
|
||||
} else {
|
||||
model_lower.as_str()
|
||||
};
|
||||
|
||||
match model_name {
|
||||
"claude" => {
|
||||
if read_only {
|
||||
cmd.args(&[
|
||||
|
||||
+42
-10
@@ -230,6 +230,9 @@ pub(crate) struct Session {
|
||||
/// Manager for external MCP servers/tools.
|
||||
mcp_connection_manager: McpConnectionManager,
|
||||
|
||||
/// Configuration for available agent models
|
||||
agents: Vec<crate::config_types::AgentConfig>,
|
||||
|
||||
/// External notifier command (will be passed as args to exec()). When
|
||||
/// `None` this feature is disabled.
|
||||
notify: Option<Vec<String>>,
|
||||
@@ -887,6 +890,7 @@ async fn submission_loop(
|
||||
cwd,
|
||||
writable_roots,
|
||||
mcp_connection_manager,
|
||||
agents: config.agents.clone(),
|
||||
notify,
|
||||
state: Mutex::new(state),
|
||||
rollout: Mutex::new(rollout_recorder),
|
||||
@@ -1834,16 +1838,44 @@ async fn handle_run_agent(
|
||||
|
||||
let mut agent_ids = Vec::new();
|
||||
for model in models {
|
||||
let agent_id = manager.create_agent(
|
||||
model,
|
||||
params.agent.clone(),
|
||||
params.context.clone(),
|
||||
params.output.clone(),
|
||||
params.files.clone().unwrap_or_default(),
|
||||
params.read_only.unwrap_or(false),
|
||||
batch_id.clone(),
|
||||
).await;
|
||||
agent_ids.push(agent_id);
|
||||
// Check if this model is configured and enabled
|
||||
let agent_config = sess.agents.iter().find(|a|
|
||||
a.name.to_lowercase() == model.to_lowercase() ||
|
||||
a.command.to_lowercase() == model.to_lowercase()
|
||||
);
|
||||
|
||||
if let Some(config) = agent_config {
|
||||
if !config.enabled {
|
||||
continue; // Skip disabled agents
|
||||
}
|
||||
|
||||
// Override read_only if agent is configured as read-only
|
||||
let read_only = config.read_only || params.read_only.unwrap_or(false);
|
||||
|
||||
let agent_id = manager.create_agent_with_config(
|
||||
model,
|
||||
params.agent.clone(),
|
||||
params.context.clone(),
|
||||
params.output.clone(),
|
||||
params.files.clone().unwrap_or_default(),
|
||||
read_only,
|
||||
batch_id.clone(),
|
||||
config.clone(),
|
||||
).await;
|
||||
agent_ids.push(agent_id);
|
||||
} else {
|
||||
// Use default configuration for unknown agents
|
||||
let agent_id = manager.create_agent(
|
||||
model,
|
||||
params.agent.clone(),
|
||||
params.context.clone(),
|
||||
params.output.clone(),
|
||||
params.files.clone().unwrap_or_default(),
|
||||
params.read_only.unwrap_or(false),
|
||||
batch_id.clone(),
|
||||
).await;
|
||||
agent_ids.push(agent_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Send status update showing agents are running
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::config_profile::ConfigProfile;
|
||||
use crate::config_types::AgentConfig;
|
||||
use crate::config_types::History;
|
||||
use crate::config_types::McpServerConfig;
|
||||
use crate::config_types::ReasoningEffort;
|
||||
@@ -111,6 +112,9 @@ pub struct Config {
|
||||
/// Definition for MCP servers that Codex can reach out to for tool calls.
|
||||
pub mcp_servers: HashMap<String, McpServerConfig>,
|
||||
|
||||
/// Configuration for available agent models
|
||||
pub agents: Vec<AgentConfig>,
|
||||
|
||||
/// Combined provider map (defaults merged with user-defined overrides).
|
||||
pub model_providers: HashMap<String, ModelProviderInfo>,
|
||||
|
||||
@@ -352,6 +356,10 @@ pub struct ConfigToml {
|
||||
#[serde(default)]
|
||||
pub mcp_servers: HashMap<String, McpServerConfig>,
|
||||
|
||||
/// Configuration for available agent models
|
||||
#[serde(default)]
|
||||
pub agents: Vec<AgentConfig>,
|
||||
|
||||
/// User-defined provider entries that extend/override the built-in list.
|
||||
#[serde(default)]
|
||||
pub model_providers: HashMap<String, ModelProviderInfo>,
|
||||
@@ -630,6 +638,7 @@ impl Config {
|
||||
user_instructions,
|
||||
base_instructions,
|
||||
mcp_servers: cfg.mcp_servers,
|
||||
agents: cfg.agents,
|
||||
model_providers,
|
||||
project_doc_max_bytes: cfg.project_doc_max_bytes.unwrap_or(PROJECT_DOC_MAX_BYTES),
|
||||
codex_home,
|
||||
|
||||
@@ -11,6 +11,41 @@ use wildmatch::WildMatchPattern;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Configuration for external agent models
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct AgentConfig {
|
||||
/// Name of the agent (e.g., "claude", "gemini", "gpt-4")
|
||||
pub name: String,
|
||||
|
||||
/// Command to execute the agent (e.g., "claude", "gemini")
|
||||
pub command: String,
|
||||
|
||||
/// Optional arguments to pass to the agent command
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
|
||||
/// Whether this agent can only run in read-only mode
|
||||
#[serde(default)]
|
||||
pub read_only: bool,
|
||||
|
||||
/// Whether this agent is enabled
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled: bool,
|
||||
|
||||
/// Optional description of the agent
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Optional environment variables for the agent
|
||||
#[serde(default)]
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct McpServerConfig {
|
||||
pub command: String,
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
// Default models to use for slash commands
|
||||
const DEFAULT_MODELS: &[&str] = &["claude", "gemini", "codex"];
|
||||
use crate::config_types::AgentConfig;
|
||||
|
||||
/// Get the list of enabled agent names from the configuration
|
||||
pub fn get_enabled_agents(agents: &[AgentConfig]) -> Vec<String> {
|
||||
agents
|
||||
.iter()
|
||||
.filter(|agent| agent.enabled)
|
||||
.map(|agent| agent.name.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get default models if no agents are configured
|
||||
fn get_default_models() -> Vec<String> {
|
||||
vec!["claude".to_string(), "gemini".to_string(), "codex".to_string()]
|
||||
}
|
||||
|
||||
/// Format the /plan command into a prompt for the LLM
|
||||
pub fn format_plan_command(task: &str, models: Option<Vec<String>>) -> String {
|
||||
let models_list = models.unwrap_or_else(|| DEFAULT_MODELS.iter().map(|s| s.to_string()).collect());
|
||||
pub fn format_plan_command(task: &str, models: Option<Vec<String>>, agents: Option<&[AgentConfig]>) -> String {
|
||||
let models_list = models.unwrap_or_else(|| {
|
||||
if let Some(agents) = agents {
|
||||
let enabled = get_enabled_agents(agents);
|
||||
if !enabled.is_empty() {
|
||||
enabled
|
||||
} else {
|
||||
get_default_models()
|
||||
}
|
||||
} else {
|
||||
get_default_models()
|
||||
}
|
||||
});
|
||||
let models_str = models_list.iter().map(|m| format!("\"{}\"", m)).collect::<Vec<_>>().join(", ");
|
||||
|
||||
format!(
|
||||
@@ -30,8 +54,19 @@ Task to plan:
|
||||
}
|
||||
|
||||
/// Format the /solve command into a prompt for the LLM
|
||||
pub fn format_solve_command(task: &str, models: Option<Vec<String>>) -> String {
|
||||
let models_list = models.unwrap_or_else(|| DEFAULT_MODELS.iter().map(|s| s.to_string()).collect());
|
||||
pub fn format_solve_command(task: &str, models: Option<Vec<String>>, agents: Option<&[AgentConfig]>) -> String {
|
||||
let models_list = models.unwrap_or_else(|| {
|
||||
if let Some(agents) = agents {
|
||||
let enabled = get_enabled_agents(agents);
|
||||
if !enabled.is_empty() {
|
||||
enabled
|
||||
} else {
|
||||
get_default_models()
|
||||
}
|
||||
} else {
|
||||
get_default_models()
|
||||
}
|
||||
});
|
||||
let models_str = models_list.iter().map(|m| format!("\"{}\"", m)).collect::<Vec<_>>().join(", ");
|
||||
|
||||
format!(
|
||||
@@ -77,8 +112,19 @@ Remember: DO NOT cancel any running agents until you have 100% confirmed the pro
|
||||
}
|
||||
|
||||
/// Format the /code command into a prompt for the LLM
|
||||
pub fn format_code_command(task: &str, models: Option<Vec<String>>) -> String {
|
||||
let models_list = models.unwrap_or_else(|| DEFAULT_MODELS.iter().map(|s| s.to_string()).collect());
|
||||
pub fn format_code_command(task: &str, models: Option<Vec<String>>, agents: Option<&[AgentConfig]>) -> String {
|
||||
let models_list = models.unwrap_or_else(|| {
|
||||
if let Some(agents) = agents {
|
||||
let enabled = get_enabled_agents(agents);
|
||||
if !enabled.is_empty() {
|
||||
enabled
|
||||
} else {
|
||||
get_default_models()
|
||||
}
|
||||
} else {
|
||||
get_default_models()
|
||||
}
|
||||
});
|
||||
let models_str = models_list.iter().map(|m| format!("\"{}\"", m)).collect::<Vec<_>>().join(", ");
|
||||
|
||||
format!(
|
||||
@@ -115,7 +161,7 @@ Coding task to perform:
|
||||
}
|
||||
|
||||
/// Parse a slash command and return the formatted prompt
|
||||
pub fn handle_slash_command(input: &str) -> Option<String> {
|
||||
pub fn handle_slash_command(input: &str, agents: Option<&[AgentConfig]>) -> Option<String> {
|
||||
let input = input.trim();
|
||||
|
||||
// Check if it starts with a slash
|
||||
@@ -133,21 +179,21 @@ pub fn handle_slash_command(input: &str) -> Option<String> {
|
||||
if args.is_empty() {
|
||||
Some("Error: /plan requires a task description. Usage: /plan <task>".to_string())
|
||||
} else {
|
||||
Some(format_plan_command(&args, None))
|
||||
Some(format_plan_command(&args, None, agents))
|
||||
}
|
||||
}
|
||||
"/solve" => {
|
||||
if args.is_empty() {
|
||||
Some("Error: /solve requires a problem description. Usage: /solve <problem>".to_string())
|
||||
} else {
|
||||
Some(format_solve_command(&args, None))
|
||||
Some(format_solve_command(&args, None, agents))
|
||||
}
|
||||
}
|
||||
"/code" => {
|
||||
if args.is_empty() {
|
||||
Some("Error: /code requires a task description. Usage: /code <task>".to_string())
|
||||
} else {
|
||||
Some(format_code_command(&args, None))
|
||||
Some(format_code_command(&args, None, agents))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
@@ -161,31 +207,63 @@ mod tests {
|
||||
#[test]
|
||||
fn test_slash_command_parsing() {
|
||||
// Test /plan command
|
||||
let result = handle_slash_command("/plan implement a new feature");
|
||||
let result = handle_slash_command("/plan implement a new feature", None);
|
||||
assert!(result.is_some());
|
||||
assert!(result.unwrap().contains("Create a comprehensive plan"));
|
||||
|
||||
// Test /solve command
|
||||
let result = handle_slash_command("/solve fix the bug in authentication");
|
||||
let result = handle_slash_command("/solve fix the bug in authentication", None);
|
||||
assert!(result.is_some());
|
||||
assert!(result.unwrap().contains("Solve a complicated problem"));
|
||||
|
||||
// Test /code command
|
||||
let result = handle_slash_command("/code refactor the database module");
|
||||
let result = handle_slash_command("/code refactor the database module", None);
|
||||
assert!(result.is_some());
|
||||
assert!(result.unwrap().contains("Perform a coding task"));
|
||||
|
||||
// Test invalid command
|
||||
let result = handle_slash_command("/invalid test");
|
||||
let result = handle_slash_command("/invalid test", None);
|
||||
assert!(result.is_none());
|
||||
|
||||
// Test non-slash command
|
||||
let result = handle_slash_command("regular message");
|
||||
let result = handle_slash_command("regular message", None);
|
||||
assert!(result.is_none());
|
||||
|
||||
// Test empty arguments
|
||||
let result = handle_slash_command("/plan");
|
||||
let result = handle_slash_command("/plan", None);
|
||||
assert!(result.is_some());
|
||||
assert!(result.unwrap().contains("Error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slash_commands_with_agents() {
|
||||
// Create test agent configurations
|
||||
let agents = vec![
|
||||
AgentConfig {
|
||||
name: "test-claude".to_string(),
|
||||
command: "claude".to_string(),
|
||||
args: vec![],
|
||||
read_only: false,
|
||||
enabled: true,
|
||||
description: None,
|
||||
env: None,
|
||||
},
|
||||
AgentConfig {
|
||||
name: "test-gemini".to_string(),
|
||||
command: "gemini".to_string(),
|
||||
args: vec![],
|
||||
read_only: false,
|
||||
enabled: false, // disabled
|
||||
description: None,
|
||||
env: None,
|
||||
},
|
||||
];
|
||||
|
||||
// Test that only enabled agents are included
|
||||
let result = handle_slash_command("/plan test task", Some(&agents));
|
||||
assert!(result.is_some());
|
||||
let prompt = result.unwrap();
|
||||
assert!(prompt.contains("test-claude"));
|
||||
assert!(!prompt.contains("test-gemini")); // Should not include disabled agent
|
||||
}
|
||||
}
|
||||
@@ -74,15 +74,17 @@ impl SlashCommand {
|
||||
}
|
||||
|
||||
// Use the slash_commands module from core to generate the prompts
|
||||
// Note: We pass None for agents here as the TUI doesn't have access to the session config
|
||||
// The actual agents will be determined when the agent tool is invoked
|
||||
match self {
|
||||
SlashCommand::Plan => {
|
||||
Some(codex_core::slash_commands::format_plan_command(args, None))
|
||||
Some(codex_core::slash_commands::format_plan_command(args, None, None))
|
||||
}
|
||||
SlashCommand::Solve => {
|
||||
Some(codex_core::slash_commands::format_solve_command(args, None))
|
||||
Some(codex_core::slash_commands::format_solve_command(args, None, None))
|
||||
}
|
||||
SlashCommand::Code => {
|
||||
Some(codex_core::slash_commands::format_code_command(args, None))
|
||||
Some(codex_core::slash_commands::format_code_command(args, None, None))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# Codex Configuration Example
|
||||
# Copy this file to config.toml and customize as needed
|
||||
|
||||
# Agent Configuration
|
||||
# Configure which external AI models are available and their settings
|
||||
[[agents]]
|
||||
name = "claude"
|
||||
command = "claude"
|
||||
enabled = true
|
||||
read-only = false
|
||||
description = "Claude AI assistant with full capabilities"
|
||||
args = ["--dangerously-skip-permissions"]
|
||||
|
||||
[[agents]]
|
||||
name = "claude-safe"
|
||||
command = "claude"
|
||||
enabled = true
|
||||
read-only = true
|
||||
description = "Claude AI assistant in read-only mode"
|
||||
args = []
|
||||
|
||||
[[agents]]
|
||||
name = "gemini"
|
||||
command = "gemini"
|
||||
enabled = true
|
||||
read-only = false
|
||||
description = "Google Gemini AI assistant"
|
||||
args = ["-y"]
|
||||
|
||||
[[agents]]
|
||||
name = "gemini-safe"
|
||||
command = "gemini"
|
||||
enabled = true
|
||||
read-only = true
|
||||
description = "Google Gemini AI assistant in read-only mode"
|
||||
args = []
|
||||
|
||||
[[agents]]
|
||||
name = "codex"
|
||||
command = "codex"
|
||||
enabled = true
|
||||
read-only = false
|
||||
description = "Codex AI assistant (self-referential)"
|
||||
args = ["-s", "workspace-write", "-a", "never"]
|
||||
|
||||
[[agents]]
|
||||
name = "gpt-4"
|
||||
command = "gpt"
|
||||
enabled = false
|
||||
read-only = false
|
||||
description = "OpenAI GPT-4 assistant"
|
||||
args = ["--model", "gpt-4"]
|
||||
# Optional environment variables for the agent
|
||||
# env = { OPENAI_API_KEY = "your-key-here" }
|
||||
|
||||
# Custom agent example
|
||||
[[agents]]
|
||||
name = "custom-llm"
|
||||
command = "/usr/local/bin/custom-llm"
|
||||
enabled = false
|
||||
read-only = false
|
||||
description = "Custom LLM implementation"
|
||||
args = ["--config", "/path/to/config.json"]
|
||||
env = { MODEL_PATH = "/models/custom.bin", TEMP = "0.7" }
|
||||
|
||||
# Sandbox Configuration
|
||||
# Controls file system access and permissions
|
||||
sandbox-mode = "workspace-write"
|
||||
|
||||
[sandbox-workspace-write]
|
||||
writable-roots = ["."]
|
||||
network-access = true
|
||||
exclude-tmpdir-env-var = false
|
||||
exclude-slash-tmp = false
|
||||
|
||||
# Shell Environment Configuration
|
||||
[shell-environment-policy]
|
||||
inherit = "all"
|
||||
ignore-default-excludes = false
|
||||
# Exclude sensitive environment variables
|
||||
exclude = ["*SECRET*", "*PASSWORD*", "*CREDENTIAL*"]
|
||||
# Set custom environment variables
|
||||
set = { EDITOR = "vim", PAGER = "less" }
|
||||
|
||||
# History Configuration
|
||||
[history]
|
||||
persistence = "save-all"
|
||||
max-bytes = 10485760 # 10MB
|
||||
|
||||
# MCP Server Configuration
|
||||
# Example MCP server configurations
|
||||
[mcp-servers.example-server]
|
||||
command = "npx"
|
||||
args = ["-y", "@example/mcp-server"]
|
||||
env = { API_KEY = "your-api-key" }
|
||||
|
||||
# File Opener Configuration
|
||||
file-opener = "vscode" # Options: vscode, vscode-insiders, windsurf, cursor, none
|
||||
Reference in New Issue
Block a user