From bfe2c976c688742b2499d165317dc87f000223b4 Mon Sep 17 00:00:00 2001 From: yayami <116920988+yayami3@users.noreply.github.com> Date: Sun, 21 Sep 2025 20:05:11 +0900 Subject: [PATCH] Add local default agent configuration support - Add LocalSettings struct for managing .amazonq/settings.json - Update agent selection logic to prioritize local settings - New priority order: CLI args -> local settings -> global settings -> default - Enable project-specific default agent configuration --- crates/chat-cli/src/cli/agent/mod.rs | 30 +++++++- .../chat-cli/src/database/local_settings.rs | 68 +++++++++++++++++++ crates/chat-cli/src/database/mod.rs | 1 + 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 crates/chat-cli/src/database/local_settings.rs diff --git a/crates/chat-cli/src/cli/agent/mod.rs b/crates/chat-cli/src/cli/agent/mod.rs index f81133d001..e1a93ade76 100644 --- a/crates/chat-cli/src/cli/agent/mod.rs +++ b/crates/chat-cli/src/cli/agent/mod.rs @@ -649,6 +649,7 @@ impl Agents { // 2. If the above is missing or invalid, assume one that is specified by chat.defaultAgent // 3. If the above is missing or invalid, assume the in-memory default let active_idx = 'active_idx: { + // 1. Command line argument --agent if let Some(name) = agent_name { if all_agents.iter().any(|a| a.name.as_str() == name) { break 'active_idx name.to_string(); @@ -659,7 +660,7 @@ impl Agents { style::Print("Error"), style::SetForegroundColor(Color::Yellow), style::Print(format!( - ": no agent with name {} found. Falling back to user specified default", + ": no agent with name {} found. Falling back to local default", name )), style::Print("\n"), @@ -667,6 +668,30 @@ impl Agents { ); } + // 2. Local settings .amazonq/settings.json + if let Ok(current_dir) = std::env::current_dir() { + if let Ok(local_settings) = crate::database::local_settings::LocalSettings::new(¤t_dir).await { + if let Some(local_default) = local_settings.get_string(Setting::ChatDefaultAgent) { + if all_agents.iter().any(|a| a.name == local_default) { + break 'active_idx local_default; + } + let _ = queue!( + output, + style::SetForegroundColor(Color::Red), + style::Print("Error"), + style::SetForegroundColor(Color::Yellow), + style::Print(format!( + ": local default agent {} not found. Falling back to global default", + local_default + )), + style::Print("\n"), + style::SetForegroundColor(Color::Reset) + ); + } + } + } + + // 3. Global settings if let Some(user_set_default) = os.database.settings.get_string(Setting::ChatDefaultAgent) { if all_agents.iter().any(|a| a.name == user_set_default) { break 'active_idx user_set_default; @@ -677,7 +702,7 @@ impl Agents { style::Print("Error"), style::SetForegroundColor(Color::Yellow), style::Print(format!( - ": user defined default {} not found. Falling back to in-memory default", + ": global default agent {} not found. Falling back to in-memory default", user_set_default )), style::Print("\n"), @@ -685,6 +710,7 @@ impl Agents { ); } + // 4. In-memory default all_agents.push({ let mut agent = Agent::default(); if mcp_enabled { diff --git a/crates/chat-cli/src/database/local_settings.rs b/crates/chat-cli/src/database/local_settings.rs new file mode 100644 index 0000000000..da6b1fd72f --- /dev/null +++ b/crates/chat-cli/src/database/local_settings.rs @@ -0,0 +1,68 @@ +use std::path::Path; + +use serde_json::{Map, Value}; +use tokio::fs::File; +use tokio::io::AsyncReadExt; + +use super::{DatabaseError, settings::Setting}; + +/// Local settings for a specific workspace/project directory +#[derive(Debug, Clone, Default)] +pub struct LocalSettings(Map); + +impl LocalSettings { + /// Create a new LocalSettings instance by reading from the workspace directory + pub async fn new(workspace_dir: &Path) -> Result { + let settings_path = workspace_dir.join(".amazonq").join("settings.json"); + + Ok(Self(match settings_path.exists() { + true => { + let mut file = File::open(&settings_path).await?; + let mut buf = Vec::new(); + file.read_to_end(&mut buf).await?; + serde_json::from_slice(&buf)? + }, + false => Map::new(), + })) + } + + /// Get a setting value + pub fn get(&self, key: Setting) -> Option<&Value> { + self.0.get(key.as_ref()) + } + + /// Get a string setting value + pub fn get_string(&self, key: Setting) -> Option { + self.get(key).and_then(|value| value.as_str().map(|s| s.into())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[tokio::test] + async fn test_local_settings_new_empty() { + let temp_dir = TempDir::new().unwrap(); + let settings = LocalSettings::new(temp_dir.path()).await.unwrap(); + assert!(settings.0.is_empty()); + } + + #[tokio::test] + async fn test_local_settings_get_string() { + let temp_dir = TempDir::new().unwrap(); + + // Create a settings file manually + let amazonq_dir = temp_dir.path().join(".amazonq"); + tokio::fs::create_dir_all(&amazonq_dir).await.unwrap(); + let settings_path = amazonq_dir.join("settings.json"); + tokio::fs::write(&settings_path, r#"{"chat.defaultAgent": "test_agent"}"#).await.unwrap(); + + let settings = LocalSettings::new(temp_dir.path()).await.unwrap(); + assert_eq!( + settings.get_string(Setting::ChatDefaultAgent), + Some("test_agent".to_string()) + ); + } +} diff --git a/crates/chat-cli/src/database/mod.rs b/crates/chat-cli/src/database/mod.rs index 80c667b8a8..e9e7883c7e 100644 --- a/crates/chat-cli/src/database/mod.rs +++ b/crates/chat-cli/src/database/mod.rs @@ -1,4 +1,5 @@ pub mod settings; +pub mod local_settings; use std::ops::Deref; use std::path::Path;