Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions crates/goose-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ pub async fn cli() -> Result<()> {
additional_system_prompt: None,
debug,
max_tool_repetitions,
interactive: true, // Session command is always interactive
})
.await;
setup_logging(
Expand Down Expand Up @@ -740,6 +741,7 @@ pub async fn cli() -> Result<()> {
additional_system_prompt: input_config.additional_system_prompt,
debug,
max_tool_repetitions,
interactive, // Use the interactive flag from the Run command
})
.await;

Expand Down Expand Up @@ -854,6 +856,7 @@ pub async fn cli() -> Result<()> {
additional_system_prompt: None,
debug: false,
max_tool_repetitions: None,
interactive: true, // Default case is always interactive
})
.await;
setup_logging(
Expand Down
1 change: 1 addition & 0 deletions crates/goose-cli/src/commands/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub async fn agent_generator(
additional_system_prompt: None,
debug: false,
max_tool_repetitions: None,
interactive: false, // Benchmarking is non-interactive
})
.await;

Expand Down
280 changes: 267 additions & 13 deletions crates/goose-cli/src/session/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,102 @@ pub struct SessionBuilderConfig {
pub debug: bool,
/// Maximum number of consecutive identical tool calls allowed
pub max_tool_repetitions: Option<u32>,
/// Whether this session will be used interactively (affects debugging prompts)
pub interactive: bool,
}

/// Offers to help debug an extension failure by creating a minimal debugging session
async fn offer_extension_debugging_help(
extension_name: &str,
error_message: &str,
provider: Arc<dyn goose::providers::base::Provider>,
interactive: bool,
) -> Result<(), anyhow::Error> {
// Only offer debugging help in interactive mode
if !interactive {
return Ok(());
}

let help_prompt = format!(
"Would you like me to help debug the '{}' extension failure?",
extension_name
);

let should_help = match cliclack::confirm(help_prompt)
.initial_value(false)
.interact()
{
Ok(choice) => choice,
Err(e) => {
if e.kind() == std::io::ErrorKind::Interrupted {
return Ok(());
} else {
return Err(e.into());
}
}
};

if !should_help {
return Ok(());
}

println!("{}", style("🔧 Starting debugging session...").cyan());

// Create a debugging prompt with context about the extension failure
let debug_prompt = format!(
"I'm having trouble starting an extension called '{}'. Here's the error I encountered:\n\n{}\n\nCan you help me diagnose what might be wrong and suggest how to fix it? Please consider common issues like:\n- Missing dependencies or tools\n- Configuration problems\n- Network connectivity (for remote extensions)\n- Permission issues\n- Path or environment variable problems",
extension_name,
error_message
);

// Create a minimal agent for debugging
let debug_agent = Agent::new();
debug_agent.update_provider(provider).await?;

// Add the developer extension if available to help with debugging
if let Ok(extensions) = ExtensionConfigManager::get_all() {
for ext_wrapper in extensions {
if ext_wrapper.enabled && ext_wrapper.config.name() == "developer" {
if let Err(e) = debug_agent.add_extension(ext_wrapper.config).await {
// If we can't add developer extension, continue without it
eprintln!(
"Note: Could not load developer extension for debugging: {}",
e
);
}
break;
}
}
}

// Create a temporary session file for this debugging session
let temp_session_file =
std::env::temp_dir().join(format!("goose_debug_extension_{}.jsonl", extension_name));

// Create the debugging session
let mut debug_session = Session::new(debug_agent, temp_session_file.clone(), false);

// Process the debugging request
println!("{}", style("Analyzing the extension failure...").yellow());
match debug_session.headless(debug_prompt).await {
Ok(_) => {
println!(
"{}",
style("✅ Debugging session completed. Check the suggestions above.").green()
);
}
Err(e) => {
eprintln!(
"{}",
style(format!("❌ Debugging session failed: {}", e)).red()
);
}
}

// Clean up the temporary session file
let _ = std::fs::remove_file(temp_session_file);

Ok(())
}

pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
Expand Down Expand Up @@ -180,12 +276,35 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
ExtensionError::Transport(McpClientError::StdioProcessError(inner)) => inner,
_ => e.to_string(),
};
eprintln!("Failed to start extension: {}, {:?}", extension.name(), err);
eprintln!(
"Please check extension configuration for {}.",
extension.name()
"{}",
style(format!(
"Warning: Failed to start extension '{}': {}",
extension.name(),
err
))
.yellow()
);
process::exit(1);
eprintln!(
"{}",
style(format!(
"Continuing without extension '{}'",
extension.name()
))
.yellow()
);

// Offer debugging help
if let Err(debug_err) = offer_extension_debugging_help(
&extension.name(),
&err,
Arc::clone(&provider_for_display),
session_config.interactive,
)
.await
{
eprintln!("Note: Could not start debugging session: {}", debug_err);
}
}
}

Expand All @@ -194,25 +313,99 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {

// Add extensions if provided
for extension_str in session_config.extensions {
if let Err(e) = session.add_extension(extension_str).await {
eprintln!("Failed to start extension: {}", e);
process::exit(1);
if let Err(e) = session.add_extension(extension_str.clone()).await {
eprintln!(
"{}",
style(format!(
"Warning: Failed to start extension '{}': {}",
extension_str, e
))
.yellow()
);
eprintln!(
"{}",
style(format!("Continuing without extension '{}'", extension_str)).yellow()
);

// Offer debugging help
if let Err(debug_err) = offer_extension_debugging_help(
&extension_str,
&e.to_string(),
Arc::clone(&provider_for_display),
session_config.interactive,
)
.await
{
eprintln!("Note: Could not start debugging session: {}", debug_err);
}
}
}

// Add remote extensions if provided
for extension_str in session_config.remote_extensions {
if let Err(e) = session.add_remote_extension(extension_str).await {
eprintln!("Failed to start extension: {}", e);
process::exit(1);
if let Err(e) = session.add_remote_extension(extension_str.clone()).await {
eprintln!(
"{}",
style(format!(
"Warning: Failed to start remote extension '{}': {}",
extension_str, e
))
.yellow()
);
eprintln!(
"{}",
style(format!(
"Continuing without remote extension '{}'",
extension_str
))
.yellow()
);

// Offer debugging help
if let Err(debug_err) = offer_extension_debugging_help(
&extension_str,
&e.to_string(),
Arc::clone(&provider_for_display),
session_config.interactive,
)
.await
{
eprintln!("Note: Could not start debugging session: {}", debug_err);
}
}
}

// Add builtin extensions
for builtin in session_config.builtins {
if let Err(e) = session.add_builtin(builtin).await {
eprintln!("Failed to start builtin extension: {}", e);
process::exit(1);
if let Err(e) = session.add_builtin(builtin.clone()).await {
eprintln!(
"{}",
style(format!(
"Warning: Failed to start builtin extension '{}': {}",
builtin, e
))
.yellow()
);
eprintln!(
"{}",
style(format!(
"Continuing without builtin extension '{}'",
builtin
))
.yellow()
);

// Offer debugging help
if let Err(debug_err) = offer_extension_debugging_help(
&builtin,
&e.to_string(),
Arc::clone(&provider_for_display),
session_config.interactive,
)
.await
{
eprintln!("Note: Could not start debugging session: {}", debug_err);
}
}
}

Expand Down Expand Up @@ -243,3 +436,64 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
);
session
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_session_builder_config_creation() {
let config = SessionBuilderConfig {
identifier: Some(Identifier::Name("test".to_string())),
resume: false,
no_session: false,
extensions: vec!["echo test".to_string()],
remote_extensions: vec!["http://example.com".to_string()],
builtins: vec!["developer".to_string()],
extensions_override: None,
additional_system_prompt: Some("Test prompt".to_string()),
debug: true,
max_tool_repetitions: Some(5),
interactive: true,
};

assert_eq!(config.extensions.len(), 1);
assert_eq!(config.remote_extensions.len(), 1);
assert_eq!(config.builtins.len(), 1);
assert!(config.debug);
assert_eq!(config.max_tool_repetitions, Some(5));
assert!(config.interactive);
}

#[test]
fn test_session_builder_config_default() {
let config = SessionBuilderConfig::default();

assert!(config.identifier.is_none());
assert!(!config.resume);
assert!(!config.no_session);
assert!(config.extensions.is_empty());
assert!(config.remote_extensions.is_empty());
assert!(config.builtins.is_empty());
assert!(config.extensions_override.is_none());
assert!(config.additional_system_prompt.is_none());
assert!(!config.debug);
assert!(config.max_tool_repetitions.is_none());
assert!(!config.interactive);
}

#[tokio::test]
async fn test_offer_extension_debugging_help_function_exists() {
// This test just verifies the function compiles and can be called
// We can't easily test the interactive parts without mocking

// We can't actually test the full function without a real provider and user interaction
// But we can at least verify it compiles and the function signature is correct
let extension_name = "test-extension";
let error_message = "test error";

// This test mainly serves as a compilation check
assert_eq!(extension_name, "test-extension");
assert_eq!(error_message, "test error");
}
}
Loading