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
31 changes: 21 additions & 10 deletions crates/goose-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::commands::session::{handle_session_list, handle_session_remove};
use crate::recipes::extract_from_cli::extract_recipe_info_from_cli;
use crate::recipes::recipe::{explain_recipe, render_recipe_as_yaml};
use crate::session::{build_session, SessionBuilderConfig, SessionSettings};
use goose::session::session_manager::SessionType;
use goose::session::SessionManager;
use goose_bench::bench_config::BenchRunConfig;
use goose_bench::runners::bench_runner::BenchRunner;
Expand Down Expand Up @@ -86,9 +87,12 @@ async fn get_or_create_session_id(
.ok_or_else(|| anyhow::anyhow!("No session found to resume"))?;
Ok(Some(session_id))
} else {
let session =
SessionManager::create_session(std::env::current_dir()?, "CLI Session".to_string())
.await?;
let session = SessionManager::create_session(
std::env::current_dir()?,
"CLI Session".to_string(),
SessionType::User,
)
.await?;
Ok(Some(session.id))
};
};
Expand All @@ -105,8 +109,12 @@ async fn get_or_create_session_id(
.ok_or_else(|| anyhow::anyhow!("No session found with name '{}'", name))?;
Ok(Some(session_id))
} else {
let session =
SessionManager::create_session(std::env::current_dir()?, name.clone()).await?;
let session = SessionManager::create_session(
std::env::current_dir()?,
name.clone(),
SessionType::User,
)
.await?;

SessionManager::update_session(&session.id)
.user_provided_name(name)
Expand All @@ -123,9 +131,12 @@ async fn get_or_create_session_id(
.ok_or_else(|| anyhow::anyhow!("Could not extract session ID from path: {:?}", path))?;
Ok(Some(session_id))
} else {
let session =
SessionManager::create_session(std::env::current_dir()?, "CLI Session".to_string())
.await?;
let session = SessionManager::create_session(
std::env::current_dir()?,
"CLI Session".to_string(),
SessionType::User,
)
.await?;
Ok(Some(session.id))
}
}
Expand Down Expand Up @@ -977,7 +988,7 @@ pub async fn cli() -> anyhow::Result<()> {
let exit_type = if result.is_ok() { "normal" } else { "error" };

let (total_tokens, message_count) = session
.get_metadata()
.get_session()
.await
.map(|m| (m.total_tokens.unwrap_or(0), m.message_count))
.unwrap_or((0, 0));
Expand Down Expand Up @@ -1198,7 +1209,7 @@ pub async fn cli() -> anyhow::Result<()> {
let exit_type = if result.is_ok() { "normal" } else { "error" };

let (total_tokens, message_count) = session
.get_metadata()
.get_session()
.await
.map(|m| (m.total_tokens.unwrap_or(0), m.message_count))
.unwrap_or((0, 0));
Expand Down
49 changes: 22 additions & 27 deletions crates/goose-cli/src/commands/acp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ use agent_client_protocol::{
ToolCallContent,
};
use anyhow::Result;
use goose::agents::Agent;
use goose::agents::{Agent, SessionConfig};
use goose::config::{get_all_extensions, Config};
use goose::conversation::message::{Message, MessageContent};
use goose::conversation::Conversation;
use goose::providers::create;
use goose::session::session_manager::SessionType;
use goose::session::SessionManager;
use rmcp::model::{RawContent, ResourceContents};
use std::collections::{HashMap, HashSet};
use std::fs;
Expand All @@ -19,17 +21,15 @@ use tokio_util::sync::CancellationToken;
use tracing::{error, info, warn};
use url::Url;

/// Represents a single goose session for ACP
struct GooseSession {
struct GooseAcpSession {
messages: Conversation,
tool_call_ids: HashMap<String, String>, // Maps internal tool IDs to ACP tool call IDs
cancel_token: Option<CancellationToken>, // Active cancellation token for prompt processing
}

/// goose ACP Agent implementation that connects to real goose agents
struct GooseAcpAgent {
session_update_tx: mpsc::UnboundedSender<(acp::SessionNotification, oneshot::Sender<()>)>,
sessions: Arc<Mutex<HashMap<String, GooseSession>>>,
session_update_tx: mpsc::UnboundedSender<(SessionNotification, oneshot::Sender<()>)>,
sessions: Arc<Mutex<HashMap<String, GooseAcpSession>>>,
agent: Agent, // Shared agent instance
}

Expand Down Expand Up @@ -97,7 +97,6 @@ impl GooseAcpAgent {
async fn new(
session_update_tx: mpsc::UnboundedSender<(acp::SessionNotification, oneshot::Sender<()>)>,
) -> Result<Self> {
// Load config and create provider
let config = Config::global();

let provider_name: String = config
Expand Down Expand Up @@ -217,7 +216,7 @@ impl GooseAcpAgent {
&self,
content_item: &MessageContent,
session_id: &acp::SessionId,
session: &mut GooseSession,
session: &mut GooseAcpSession,
) -> Result<(), acp::Error> {
match content_item {
MessageContent::Text(text) => {
Expand Down Expand Up @@ -273,7 +272,7 @@ impl GooseAcpAgent {
&self,
tool_request: &goose::conversation::message::ToolRequest,
session_id: &acp::SessionId,
session: &mut GooseSession,
session: &mut GooseAcpSession,
) -> Result<(), acp::Error> {
// Generate ACP tool call ID and track mapping
let acp_tool_id = format!("tool_{}", uuid::Uuid::new_v4());
Expand Down Expand Up @@ -341,7 +340,7 @@ impl GooseAcpAgent {
&self,
tool_response: &goose::conversation::message::ToolResponse,
session_id: &acp::SessionId,
session: &mut GooseSession,
session: &mut GooseAcpSession,
) -> Result<(), acp::Error> {
// Look up the ACP tool call ID
if let Some(acp_tool_id) = session.tool_call_ids.get(&tool_response.id) {
Expand Down Expand Up @@ -496,7 +495,7 @@ impl acp::Agent for GooseAcpAgent {
// Generate a unique session ID
let session_id = uuid::Uuid::new_v4().to_string();

let session = GooseSession {
let session = GooseAcpSession {
messages: Conversation::new_unvalidated(Vec::new()),
tool_call_ids: HashMap::new(),
cancel_token: None,
Expand Down Expand Up @@ -544,30 +543,26 @@ impl acp::Agent for GooseAcpAgent {
// Create and store cancellation token for this prompt
let cancel_token = CancellationToken::new();

// Convert ACP prompt to Goose message
let user_message = self.convert_acp_prompt_to_message(args.prompt);

// Prepare for agent reply
let messages = {
let mut sessions = self.sessions.lock().await;
let session = sessions
.get_mut(&session_id)
.ok_or_else(acp::Error::invalid_params)?;

// Add message to conversation
session.messages.push(user_message);

// Store cancellation token
session.cancel_token = Some(cancel_token.clone());
let session = SessionManager::create_session(
std::env::current_dir().unwrap_or_default(),
"ACP Session".to_string(),
SessionType::Hidden,
)
.await?;
Comment on lines +548 to +553
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

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

A new session is created for every prompt in the ACP handler, but the session is never cleaned up or reused. This will create a new database entry for each prompt, potentially leading to database bloat. Consider reusing the session for the lifetime of the ACP session or cleaning up Hidden sessions periodically.

Copilot uses AI. Check for mistakes.

// Clone what we need for the reply call
session.messages.clone()
let session_config = SessionConfig {
id: session.id.clone(),
schedule_id: None,
max_turns: None,
retry_config: None,
};

// Get agent's reply through the Goose agent
let mut stream = self
.agent
.reply(messages, None, Some(cancel_token.clone()))
.reply(user_message, session_config, Some(cancel_token.clone()))
.await
.map_err(|e| {
error!("Error getting agent reply: {}", e);
Expand Down
4 changes: 1 addition & 3 deletions crates/goose-cli/src/commands/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ impl BenchBaseSession for CliSession {
}

fn get_session_id(&self) -> anyhow::Result<String> {
self.session_id()
.cloned()
.ok_or_else(|| anyhow::anyhow!("No session ID available"))
Ok(self.session_id().to_string())
}
}
pub async fn agent_generator(
Expand Down
1 change: 0 additions & 1 deletion crates/goose-cli/src/commands/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ pub async fn handle_schedule_add(
paused: false,
current_session_id: None,
process_start_time: None,
execution_mode: Some("background".to_string()), // Default to background for CLI
};

let scheduler_storage_path =
Expand Down
11 changes: 4 additions & 7 deletions crates/goose-cli/src/commands/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use base64::Engine;
use futures::{sink::SinkExt, stream::StreamExt};
use goose::agents::{Agent, AgentEvent};
use goose::conversation::message::Message as GooseMessage;
use goose::session::session_manager::SessionType;
use goose::session::SessionManager;
use serde::{Deserialize, Serialize};
use serde_json::Value;
Expand Down Expand Up @@ -226,6 +227,7 @@ async fn serve_index() -> Result<Redirect, (http::StatusCode, String)> {
let session = SessionManager::create_session(
std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")),
"Web session".to_string(),
SessionType::User,
)
.await
.map_err(|err| (http::StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
Expand Down Expand Up @@ -467,21 +469,16 @@ async fn process_message_streaming(

let session = SessionManager::get_session(&session_id, true).await?;
let mut messages = session.conversation.unwrap_or_default();
messages.push(user_message);
messages.push(user_message.clone());

let session_config = SessionConfig {
id: session.id.clone(),
working_dir: session.working_dir,
schedule_id: None,
execution_mode: None,
max_turns: None,
retry_config: None,
};

match agent
.reply(messages.clone(), Some(session_config), None)
.await
{
match agent.reply(user_message, session_config, None).await {
Ok(mut stream) => {
while let Some(result) = stream.next().await {
match result {
Expand Down
19 changes: 13 additions & 6 deletions crates/goose-cli/src/scenario_tests/scenario_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ use anyhow::Result;
use goose::agents::Agent;
use goose::model::ModelConfig;
use goose::providers::{create, testprovider::TestProvider};
use goose::session::session_manager::SessionType;
use goose::session::SessionManager;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio_util::sync::CancellationToken;

Expand Down Expand Up @@ -190,7 +192,6 @@ where
)
};

// Generate messages using the provider
let messages = vec![message_generator(&*provider_arc)];

let mock_client = weather_client();
Expand Down Expand Up @@ -218,19 +219,25 @@ where
.update_provider(provider_arc as Arc<dyn goose::providers::base::Provider>)
.await?;

let mut session = CliSession::new(agent, None, false, None, None, None, None).await;
let session = SessionManager::create_session(
PathBuf::default(),
"scenario-runner".to_string(),
SessionType::Hidden,
)
.await?;
let mut cli_session = CliSession::new(agent, session.id, false, None, None, None, None).await;

let mut error = None;
for message in &messages {
if let Err(e) = session
if let Err(e) = cli_session
.process_message(message.clone(), CancellationToken::default())
.await
{
error = Some(e.to_string());
break;
}
}
let updated_messages = session.message_history();
let updated_messages = cli_session.message_history();

if let Some(ref err_msg) = error {
if err_msg.contains("No recorded response found") {
Expand All @@ -249,7 +256,7 @@ where

validator(&result)?;

drop(session);
drop(cli_session);

if let Some(provider) = provider_for_saving {
if result.error.is_none() {
Expand Down
Loading
Loading