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
2,928 changes: 1,390 additions & 1,538 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 2 additions & 20 deletions clippy-baselines/too_many_lines.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,24 @@ crates/goose-cli/src/commands/configure.rs::configure_tool_permissions_dialog
crates/goose-cli/src/commands/configure.rs::handle_configure
crates/goose-cli/src/commands/project.rs::handle_project_default
crates/goose-cli/src/commands/project.rs::handle_projects_interactive
crates/goose-cli/src/commands/web.rs::handle_socket
crates/goose-cli/src/commands/web.rs::process_message_streaming
crates/goose-cli/src/session/builder.rs::build_session
crates/goose-cli/src/session/export.rs::tool_response_to_markdown
crates/goose-cli/src/session/mod.rs::handle_interrupted_messages
crates/goose-cli/src/session/mod.rs::interactive
crates/goose-cli/src/session/mod.rs::process_agent_response
crates/goose-mcp/src/computercontroller/docx_tool.rs::docx_tool
crates/goose-mcp/src/computercontroller/mod.rs::new
crates/goose-mcp/src/computercontroller/mod.rs::quick_script
crates/goose-mcp/src/computercontroller/mod.rs::xlsx_tool
crates/goose-mcp/src/computercontroller/pdf_tool.rs::pdf_tool
crates/goose-mcp/src/developer/mod.rs::bash
crates/goose-mcp/src/developer/mod.rs::new
crates/goose-mcp/src/memory/mod.rs::new
crates/goose-server/src/openapi.rs::convert_typed_schema
crates/goose-server/src/openapi.rs::convert_typed_schema
crates/goose-server/src/routes/audio.rs::transcribe_elevenlabs_handler
crates/goose-server/src/routes/audio.rs::transcribe_elevenlabs_handler
crates/goose-server/src/routes/extension.rs::add_extension
crates/goose-server/src/routes/extension.rs::add_extension
crates/goose-server/src/routes/extension.rs::is_command_allowed_with_allowlist
crates/goose-server/src/routes/extension.rs::is_command_allowed_with_allowlist
crates/goose-server/src/routes/reply.rs::reply_handler
crates/goose-server/src/routes/reply.rs::reply_handler
crates/goose/src/agents/agent.rs::create_recipe
crates/goose/src/agents/agent.rs::dispatch_tool_call
crates/goose/src/agents/agent.rs::reply
crates/goose/src/agents/agent.rs::reply_internal
crates/goose/src/agents/extension_manager.rs::add_extension
crates/goose/src/providers/azure.rs::post
crates/goose/src/providers/databricks.rs::post_with_retry
crates/goose/src/providers/formats/anthropic.rs::format_messages
crates/goose/src/providers/formats/anthropic.rs::response_to_streaming_message<S>
crates/goose/src/providers/formats/databricks.rs::format_messages
Expand All @@ -44,9 +32,3 @@ crates/goose/src/providers/formats/openai.rs::format_messages
crates/goose/src/providers/formats/openai.rs::response_to_streaming_message<S>
crates/goose/src/providers/gcpvertexai.rs::post_with_location
crates/goose/src/providers/snowflake.rs::post
crates/goose/src/scheduler.rs::add_scheduled_job
crates/goose/src/scheduler.rs::load_jobs_from_storage
crates/goose/src/scheduler.rs::run_scheduled_job_internal
crates/goose/src/scheduler.rs::update_schedule
crates/goose/src/session/storage.rs::read_messages_with_truncation
crates/mcp-server/src/lib.rs::run<R, W>
3 changes: 2 additions & 1 deletion crates/goose-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ goose = { path = "../goose" }
goose-bench = { path = "../goose-bench" }
goose-mcp = { path = "../goose-mcp" }
rmcp = { workspace = true }
sacp = "9.0.0"
sacp = "10.0.0-alpha.3"
agent-client-protocol-schema = "0.10.5"
clap = { version = "4.4", features = ["derive"] }
cliclack = "0.3.5"
console = "0.16.1"
Expand Down
18 changes: 14 additions & 4 deletions crates/goose-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,17 @@ enum Command {

/// Run goose as an ACP (Agent Client Protocol) agent
#[command(about = "Run goose as an ACP agent server on stdio")]
Acp {},
Acp {
/// Add builtin extensions by name
#[arg(
long = "with-builtin",
value_name = "NAME",
help = "Add builtin extensions by name (e.g., 'developer' or multiple: 'developer,github')",
long_help = "Add one or more builtin extensions that are bundled with goose by specifying their names, comma-separated",
value_delimiter = ','
)]
builtins: Vec<String>,
},

/// Start or resume interactive chat sessions
#[command(
Expand Down Expand Up @@ -961,7 +971,7 @@ pub async fn cli() -> anyhow::Result<()> {
Some(Command::Configure {}) => "configure",
Some(Command::Info { .. }) => "info",
Some(Command::Mcp { .. }) => "mcp",
Some(Command::Acp {}) => "acp",
Some(Command::Acp { .. }) => "acp",
Some(Command::Session { .. }) => "session",
Some(Command::Project {}) => "project",
Some(Command::Projects) => "projects",
Expand Down Expand Up @@ -995,8 +1005,8 @@ pub async fn cli() -> anyhow::Result<()> {
McpCommand::Developer => serve(DeveloperServer::new()).await?,
}
}
Some(Command::Acp {}) => {
run_acp_agent().await?;
Some(Command::Acp { builtins }) => {
run_acp_agent(builtins).await?;
}
Some(Command::Session {
command,
Expand Down
50 changes: 44 additions & 6 deletions crates/goose-cli/src/commands/acp.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Result;
use goose::agents::extension::Envs;
use goose::agents::extension::{Envs, PlatformExtensionContext, PLATFORM_EXTENSIONS};
use goose::agents::{Agent, ExtensionConfig, SessionConfig};
use goose::config::{get_all_extensions, Config};
use goose::conversation::message::{ActionRequiredData, Message, MessageContent};
Expand Down Expand Up @@ -246,8 +246,34 @@ fn format_tool_name(tool_name: &str) -> String {
}
}

async fn add_builtins(agent: &Agent, builtins: Vec<String>) {
for builtin in builtins {
let config = if PLATFORM_EXTENSIONS.contains_key(builtin.as_str()) {
ExtensionConfig::Platform {
name: builtin.clone(),
bundled: None,
description: builtin.clone(),
available_tools: Vec::new(),
}
} else {
ExtensionConfig::Builtin {
name: builtin.clone(),
display_name: None,
timeout: None,
bundled: None,
description: builtin.clone(),
available_tools: Vec::new(),
}
};
match agent.add_extension(config).await {
Ok(_) => info!(extension = %builtin, "builtin extension loaded"),
Err(e) => warn!(extension = %builtin, error = %e, "builtin extension load failed"),
}
}
}

impl GooseAcpAgent {
async fn new() -> Result<Self> {
async fn new(builtins: Vec<String>) -> Result<Self> {
let config = Config::global();

let provider_name: String = config
Expand Down Expand Up @@ -286,6 +312,16 @@ impl GooseAcpAgent {
.collect();

let agent_ptr = Arc::new(agent);

// ACP loads the same default extensions as CLI
agent_ptr
.extension_manager
.set_context(PlatformExtensionContext {
session_id: Some(session.id.clone()),
extension_manager: Some(Arc::downgrade(&agent_ptr.extension_manager)),
})
.await;

let mut set = JoinSet::new();
let mut waiting_on = HashSet::new();

Expand Down Expand Up @@ -316,6 +352,8 @@ impl GooseAcpAgent {
}
}

add_builtins(&agent_ptr, builtins).await;

Ok(Self {
sessions: Arc::new(Mutex::new(HashMap::new())),
agent: agent_ptr,
Expand Down Expand Up @@ -584,7 +622,7 @@ impl GooseAcpAgent {
};

cx.send_request(permission_request)
.await_when_result_received(move |result| async move {
.on_receiving_result(move |result| async move {
match result {
Ok(response) => {
agent
Expand Down Expand Up @@ -1029,7 +1067,7 @@ struct GooseAcpHandler {
}

impl JrMessageHandler for GooseAcpHandler {
type Role = AgentToClient;
type Link = AgentToClient;

fn describe_chain(&self) -> impl std::fmt::Debug {
"goose-acp"
Expand Down Expand Up @@ -1097,13 +1135,13 @@ impl JrMessageHandler for GooseAcpHandler {
}
}

pub async fn run_acp_agent() -> Result<()> {
pub async fn run_acp_agent(builtins: Vec<String>) -> Result<()> {
info!("listening on stdio");

let outgoing = tokio::io::stdout().compat_write();
let incoming = tokio::io::stdin().compat();

let agent = Arc::new(GooseAcpAgent::new().await?);
let agent = Arc::new(GooseAcpAgent::new(builtins).await?);
let handler = GooseAcpHandler { agent };

AgentToClient::builder()
Expand Down
38 changes: 3 additions & 35 deletions crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ use rmcp::model::{ErrorCode, ErrorData};

use goose::config::paths::Paths;
use goose::conversation::message::{ActionRequiredData, Message, MessageContent};
use rand::{distributions::Alphanumeric, Rng};
use rustyline::EditMode;
use serde::{Deserialize, Serialize};
use serde_json::Value;
Expand Down Expand Up @@ -171,32 +170,6 @@ pub async fn classify_planner_response(
}
}

fn generate_extension_name(extension_command: &str) -> String {
let cmd_name: String = extension_command
.split([' ', '/'])
.next_back()
.unwrap_or("")
.chars()
.filter(|c| c.is_alphanumeric())
.collect();

let prefix: String = cmd_name.chars().take(16).collect();

let random_suffix: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(8)
.map(char::from)
.collect();

let name = format!("{}_{}", prefix, random_suffix);

if name.chars().next().is_none_or(|c| !c.is_alphabetic()) {
format!("g{}", name)
} else {
name
}
}

impl CliSession {
#[allow(clippy::too_many_arguments)]
pub async fn new(
Expand Down Expand Up @@ -256,10 +229,9 @@ impl CliSession {
}

let cmd = parts.remove(0).to_string();
let name = generate_extension_name(&extension_command);

let config = ExtensionConfig::Stdio {
name,
name: String::new(),
cmd,
args: parts.iter().map(|s| s.to_string()).collect(),
envs: Envs::new(envs),
Expand Down Expand Up @@ -287,10 +259,8 @@ impl CliSession {
/// # Arguments
/// * `extension_url` - URL of the server
pub async fn add_remote_extension(&mut self, extension_url: String) -> Result<()> {
let name = generate_extension_name(&extension_url);

let config = ExtensionConfig::Sse {
name,
name: String::new(),
uri: extension_url,
envs: Envs::new(HashMap::new()),
env_keys: Vec::new(),
Expand All @@ -317,10 +287,8 @@ impl CliSession {
/// # Arguments
/// * `extension_url` - URL of the server
pub async fn add_streamable_http_extension(&mut self, extension_url: String) -> Result<()> {
let name = generate_extension_name(&extension_url);

let config = ExtensionConfig::StreamableHttp {
name,
name: String::new(),
uri: extension_url,
envs: Envs::new(HashMap::new()),
env_keys: Vec::new(),
Expand Down
2 changes: 1 addition & 1 deletion crates/goose-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ schemars = "1.0"
lazy_static = "1.5"
shellexpand = "3.1.0"
indoc = "2.0.5"
xcap = "0.0.14"
xcap = "=0.4.0"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the revlock version that allows everything else to pass. Movement past this in #6301

reqwest = { version = "0.11", features = [
"json",
"rustls-tls-native-roots",
Expand Down
8 changes: 4 additions & 4 deletions crates/goose-mcp/src/computercontroller/pdf_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ mod tests {
async fn test_pdf_text_extraction() {
let test_pdf_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("src/computercontroller/tests/data/test.pdf");
let cache_dir = tempfile::tempdir().unwrap().into_path();
let cache_dir = tempfile::tempdir().unwrap().keep();

println!("Testing text extraction from: {}", test_pdf_path.display());

Expand All @@ -390,7 +390,7 @@ mod tests {
async fn test_pdf_image_extraction() {
let test_pdf_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("src/computercontroller/tests/data/test_image.pdf");
let cache_dir = tempfile::tempdir().unwrap().into_path();
let cache_dir = tempfile::tempdir().unwrap().keep();

println!("Testing image extraction from: {}", test_pdf_path.display());

Expand Down Expand Up @@ -436,7 +436,7 @@ mod tests {

#[tokio::test]
async fn test_pdf_invalid_path() {
let cache_dir = tempfile::tempdir().unwrap().into_path();
let cache_dir = tempfile::tempdir().unwrap().keep();
let result = pdf_tool("nonexistent.pdf", "extract_text", &cache_dir).await;

assert!(result.is_err(), "Should fail with invalid path");
Expand All @@ -446,7 +446,7 @@ mod tests {
async fn test_pdf_invalid_operation() {
let test_pdf_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("src/computercontroller/tests/data/test.pdf");
let cache_dir = tempfile::tempdir().unwrap().into_path();
let cache_dir = tempfile::tempdir().unwrap().keep();

let result = pdf_tool(
test_pdf_path.to_str().unwrap(),
Expand Down
4 changes: 2 additions & 2 deletions crates/goose-mcp/src/developer/rmcp_developer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ impl DeveloperServer {
})?;

let window_titles: Vec<String> =
windows.into_iter().map(|w| w.title().to_string()).collect();
windows.into_iter().filter_map(|w| w.title().ok()).collect();

let content_text = format!("Available windows:\n{}", window_titles.join("\n"));

Expand Down Expand Up @@ -628,7 +628,7 @@ impl DeveloperServer {

let window = windows
.into_iter()
.find(|w| w.title() == window_title)
.find(|w| w.title().is_ok_and(|t| &t == window_title))
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
Expand Down
3 changes: 2 additions & 1 deletion crates/goose/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ unbinder = "0.1.7"
winapi = { version = "0.3", features = ["wincred"] }

[dev-dependencies]
sacp = "9.0.0"
sacp = "10.0.0-alpha.3"
agent-client-protocol-schema = "0.10.5"
criterion = "0.5"
serial_test = "3.2.0"
mockall = "0.13.1"
Expand Down
Loading