mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 22:54:24 +01:00
feat/fix: don't stop cli starting if MCPs don't load (#2860)
This commit is contained in:
@@ -632,6 +632,7 @@ pub async fn cli() -> Result<()> {
|
|||||||
additional_system_prompt: None,
|
additional_system_prompt: None,
|
||||||
debug,
|
debug,
|
||||||
max_tool_repetitions,
|
max_tool_repetitions,
|
||||||
|
interactive: true, // Session command is always interactive
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
setup_logging(
|
setup_logging(
|
||||||
@@ -740,6 +741,7 @@ pub async fn cli() -> Result<()> {
|
|||||||
additional_system_prompt: input_config.additional_system_prompt,
|
additional_system_prompt: input_config.additional_system_prompt,
|
||||||
debug,
|
debug,
|
||||||
max_tool_repetitions,
|
max_tool_repetitions,
|
||||||
|
interactive, // Use the interactive flag from the Run command
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -854,6 +856,7 @@ pub async fn cli() -> Result<()> {
|
|||||||
additional_system_prompt: None,
|
additional_system_prompt: None,
|
||||||
debug: false,
|
debug: false,
|
||||||
max_tool_repetitions: None,
|
max_tool_repetitions: None,
|
||||||
|
interactive: true, // Default case is always interactive
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
setup_logging(
|
setup_logging(
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ pub async fn agent_generator(
|
|||||||
additional_system_prompt: None,
|
additional_system_prompt: None,
|
||||||
debug: false,
|
debug: false,
|
||||||
max_tool_repetitions: None,
|
max_tool_repetitions: None,
|
||||||
|
interactive: false, // Benchmarking is non-interactive
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,102 @@ pub struct SessionBuilderConfig {
|
|||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
/// Maximum number of consecutive identical tool calls allowed
|
/// Maximum number of consecutive identical tool calls allowed
|
||||||
pub max_tool_repetitions: Option<u32>,
|
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 {
|
pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
|
||||||
@@ -180,12 +276,35 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
|
|||||||
ExtensionError::Transport(McpClientError::StdioProcessError(inner)) => inner,
|
ExtensionError::Transport(McpClientError::StdioProcessError(inner)) => inner,
|
||||||
_ => e.to_string(),
|
_ => e.to_string(),
|
||||||
};
|
};
|
||||||
eprintln!("Failed to start extension: {}, {:?}", extension.name(), err);
|
|
||||||
eprintln!(
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,25 +313,99 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
|
|||||||
|
|
||||||
// Add extensions if provided
|
// Add extensions if provided
|
||||||
for extension_str in session_config.extensions {
|
for extension_str in session_config.extensions {
|
||||||
if let Err(e) = session.add_extension(extension_str).await {
|
if let Err(e) = session.add_extension(extension_str.clone()).await {
|
||||||
eprintln!("Failed to start extension: {}", e);
|
eprintln!(
|
||||||
process::exit(1);
|
"{}",
|
||||||
|
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
|
// Add remote extensions if provided
|
||||||
for extension_str in session_config.remote_extensions {
|
for extension_str in session_config.remote_extensions {
|
||||||
if let Err(e) = session.add_remote_extension(extension_str).await {
|
if let Err(e) = session.add_remote_extension(extension_str.clone()).await {
|
||||||
eprintln!("Failed to start extension: {}", e);
|
eprintln!(
|
||||||
process::exit(1);
|
"{}",
|
||||||
|
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
|
// Add builtin extensions
|
||||||
for builtin in session_config.builtins {
|
for builtin in session_config.builtins {
|
||||||
if let Err(e) = session.add_builtin(builtin).await {
|
if let Err(e) = session.add_builtin(builtin.clone()).await {
|
||||||
eprintln!("Failed to start builtin extension: {}", e);
|
eprintln!(
|
||||||
process::exit(1);
|
"{}",
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,3 +436,64 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> Session {
|
|||||||
);
|
);
|
||||||
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user