fix: actually set the working dir for extensions from session (#6612)

This commit is contained in:
Zane
2026-01-21 10:03:49 -10:00
committed by GitHub
parent a7699e1607
commit 5bf4cf0463
4 changed files with 44 additions and 14 deletions
+18 -2
View File
@@ -634,6 +634,7 @@ impl Agent {
/// Load extensions from session into the agent /// Load extensions from session into the agent
/// Skips extensions that are already loaded /// Skips extensions that are already loaded
/// Uses the session's working_dir for extension initialization
pub async fn load_extensions_from_session( pub async fn load_extensions_from_session(
self: &Arc<Self>, self: &Arc<Self>,
session: &Session, session: &Session,
@@ -651,11 +652,15 @@ impl Agent {
} }
}; };
// Capture the session's working_dir to pass to extensions
let working_dir = session.working_dir.clone();
let extension_futures = enabled_configs let extension_futures = enabled_configs
.into_iter() .into_iter()
.map(|config| { .map(|config| {
let config_clone = config.clone(); let config_clone = config.clone();
let agent_ref = self.clone(); let agent_ref = self.clone();
let working_dir_clone = working_dir.clone();
async move { async move {
let name = config_clone.name().to_string(); let name = config_clone.name().to_string();
@@ -674,7 +679,10 @@ impl Agent {
}; };
} }
match agent_ref.add_extension(config_clone).await { match agent_ref
.add_extension_with_working_dir(config_clone, Some(working_dir_clone))
.await
{
Ok(_) => ExtensionLoadResult { Ok(_) => ExtensionLoadResult {
name, name,
success: true, success: true,
@@ -698,6 +706,14 @@ impl Agent {
} }
pub async fn add_extension(&self, extension: ExtensionConfig) -> ExtensionResult<()> { pub async fn add_extension(&self, extension: ExtensionConfig) -> ExtensionResult<()> {
self.add_extension_with_working_dir(extension, None).await
}
pub async fn add_extension_with_working_dir(
&self,
extension: ExtensionConfig,
working_dir: Option<std::path::PathBuf>,
) -> ExtensionResult<()> {
match &extension { match &extension {
ExtensionConfig::Frontend { ExtensionConfig::Frontend {
tools, tools,
@@ -726,7 +742,7 @@ impl Agent {
} }
_ => { _ => {
self.extension_manager self.extension_manager
.add_extension(extension.clone()) .add_extension_with_working_dir(extension.clone(), working_dir)
.await?; .await?;
} }
} }
+22 -10
View File
@@ -466,13 +466,6 @@ impl ExtensionManager {
&self.context &self.context
} }
/// Resolve the working directory for an extension.
/// Falls back to current_dir when working_dir is not available.
async fn resolve_working_dir(&self) -> PathBuf {
// Fall back to current_dir - working_dir is passed through the call chain from session
std::env::current_dir().unwrap_or_default()
}
pub async fn supports_resources(&self) -> bool { pub async fn supports_resources(&self) -> bool {
self.extensions self.extensions
.lock() .lock()
@@ -481,7 +474,14 @@ impl ExtensionManager {
.any(|ext| ext.supports_resources()) .any(|ext| ext.supports_resources())
} }
pub async fn add_extension(self: &Arc<Self>, config: ExtensionConfig) -> ExtensionResult<()> { /// Add an extension with an optional working directory.
/// If working_dir is None, falls back to current_dir.
#[allow(clippy::too_many_lines)]
pub async fn add_extension_with_working_dir(
self: &Arc<Self>,
config: ExtensionConfig,
working_dir: Option<PathBuf>,
) -> ExtensionResult<()> {
let config_name = config.key().to_string(); let config_name = config.key().to_string();
let sanitized_name = normalize(&config_name); let sanitized_name = normalize(&config_name);
@@ -489,8 +489,9 @@ impl ExtensionManager {
return Ok(()); return Ok(());
} }
// Resolve working_dir: session > current_dir // Resolve working_dir: explicit > current_dir
let effective_working_dir = self.resolve_working_dir().await; let effective_working_dir =
working_dir.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let mut temp_dir = None; let mut temp_dir = None;
@@ -555,6 +556,17 @@ impl ExtensionManager {
.ok_or_else(|| { .ok_or_else(|| {
ExtensionError::ConfigError(format!("Unknown builtin extension: {}", name)) ExtensionError::ConfigError(format!("Unknown builtin extension: {}", name))
})?; })?;
// Set GOOSE_WORKING_DIR in the current process for builtin extensions
// since they run in-process and read from std::env::var
if effective_working_dir.exists() && effective_working_dir.is_dir() {
std::env::set_var("GOOSE_WORKING_DIR", &effective_working_dir);
tracing::info!(
"Set GOOSE_WORKING_DIR for builtin extension: {:?}",
effective_working_dir
);
}
let (server_read, client_write) = tokio::io::duplex(65536); let (server_read, client_write) = tokio::io::duplex(65536);
let (client_read, server_write) = tokio::io::duplex(65536); let (client_read, server_write) = tokio::io::duplex(65536);
(def.spawn_server)(server_read, server_write); (def.spawn_server)(server_read, server_write);
@@ -211,7 +211,7 @@ impl ExtensionManagerClient {
}; };
extension_manager extension_manager
.add_extension(config) .add_extension_with_working_dir(config, None)
.await .await
.map(|_| { .map(|_| {
vec![Content::text(format!( vec![Content::text(format!(
+3 -1
View File
@@ -264,7 +264,9 @@ async fn test_replayed_session(
#[allow(clippy::redundant_closure_call)] #[allow(clippy::redundant_closure_call)]
let result = (async || -> Result<(), Box<dyn std::error::Error>> { let result = (async || -> Result<(), Box<dyn std::error::Error>> {
extension_manager.add_extension(extension_config).await?; extension_manager
.add_extension_with_working_dir(extension_config, None)
.await?;
let mut results = Vec::new(); let mut results = Vec::new();
for tool_call in tool_calls { for tool_call in tool_calls {
let tool_call = CallToolRequestParam { let tool_call = CallToolRequestParam {