feat(goose-acp): enable parallel sessions with isolated agent state (#6392)
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -16,36 +16,34 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
goose = { path = "../goose" }
|
||||
goose-acp = { path = "../goose-acp" }
|
||||
goose-bench = { path = "../goose-bench" }
|
||||
goose-mcp = { path = "../goose-mcp" }
|
||||
rmcp = { workspace = true }
|
||||
sacp = { workspace = true }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
cliclack = "0.3.5"
|
||||
console = "0.16.1"
|
||||
uuid = { version = "1.11", features = ["v4"] }
|
||||
dotenvy = "0.15.7"
|
||||
bat = "0.25.0"
|
||||
anyhow = "1.0"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.43", features = ["full"] }
|
||||
futures = "0.3"
|
||||
anyhow = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
serde = { version = "1.0", features = ["derive"] } # For serialization
|
||||
serde_yaml = "0.9"
|
||||
tempfile = "3"
|
||||
etcetera = { workspace = true }
|
||||
rand = "0.8.5"
|
||||
rustyline = "15.0.0"
|
||||
tracing = "0.1"
|
||||
tracing = { workspace = true }
|
||||
chrono = "0.4"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json", "time"] }
|
||||
tracing-appender = "0.2"
|
||||
once_cell = "1.20.2"
|
||||
shlex = "1.3.0"
|
||||
async-trait = "0.1.89"
|
||||
base64 = "0.22.1"
|
||||
regex = "1.11.1"
|
||||
nix = { version = "0.30.1", features = ["process", "signal"] }
|
||||
tar = "0.4"
|
||||
# Web server dependencies
|
||||
axum = { version = "0.8.1", features = ["ws", "macros"] }
|
||||
@@ -55,7 +53,6 @@ webbrowser = {workspace = true}
|
||||
indicatif = "0.18.1"
|
||||
tokio-util = { version = "0.7.15", features = ["compat", "rt"] }
|
||||
anstream = "0.6.18"
|
||||
url = "2.5.7"
|
||||
open = "5.3.2"
|
||||
urlencoding = "2.1"
|
||||
clap_complete = "4.5.62"
|
||||
@@ -71,5 +68,5 @@ disable-update = []
|
||||
tempfile = "3"
|
||||
temp-env = { version = "0.3.6", features = ["async_closure"] }
|
||||
test-case = "3.3"
|
||||
tokio = { version = "1.43", features = ["rt", "macros"] }
|
||||
serial_test = "3.2.0"
|
||||
tokio = { workspace = true }
|
||||
serial_test = { workspace = true }
|
||||
|
||||
+38
-26
@@ -8,7 +8,6 @@ use goose_mcp::{
|
||||
AutoVisualiserRouter, ComputerControllerServer, DeveloperServer, MemoryServer, TutorialServer,
|
||||
};
|
||||
|
||||
use crate::commands::acp::run_acp_agent;
|
||||
use crate::commands::bench::agent_generator;
|
||||
use crate::commands::configure::{configure_telemetry_consent_dialog, handle_configure};
|
||||
use crate::commands::info::handle_info;
|
||||
@@ -320,21 +319,24 @@ async fn get_or_create_session_id(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let session_manager = SessionManager::instance();
|
||||
|
||||
let Some(id) = identifier else {
|
||||
return if resume {
|
||||
let sessions = SessionManager::list_sessions().await?;
|
||||
let sessions = session_manager.list_sessions().await?;
|
||||
let session_id = sessions
|
||||
.first()
|
||||
.map(|s| s.id.clone())
|
||||
.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(),
|
||||
SessionType::User,
|
||||
)
|
||||
.await?;
|
||||
let session = session_manager
|
||||
.create_session(
|
||||
std::env::current_dir()?,
|
||||
"CLI Session".to_string(),
|
||||
SessionType::User,
|
||||
)
|
||||
.await?;
|
||||
Ok(Some(session.id))
|
||||
};
|
||||
};
|
||||
@@ -343,7 +345,7 @@ async fn get_or_create_session_id(
|
||||
Ok(Some(session_id))
|
||||
} else if let Some(name) = id.name {
|
||||
if resume {
|
||||
let sessions = SessionManager::list_sessions().await?;
|
||||
let sessions = session_manager.list_sessions().await?;
|
||||
let session_id = sessions
|
||||
.into_iter()
|
||||
.find(|s| s.name == name || s.id == name)
|
||||
@@ -351,14 +353,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(),
|
||||
SessionType::User,
|
||||
)
|
||||
.await?;
|
||||
let session = session_manager
|
||||
.create_session(std::env::current_dir()?, name.clone(), SessionType::User)
|
||||
.await?;
|
||||
|
||||
SessionManager::update_session(&session.id)
|
||||
session_manager
|
||||
.update(&session.id)
|
||||
.user_provided_name(name)
|
||||
.apply()
|
||||
.await?;
|
||||
@@ -373,12 +373,13 @@ 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(),
|
||||
SessionType::User,
|
||||
)
|
||||
.await?;
|
||||
let session = session_manager
|
||||
.create_session(
|
||||
std::env::current_dir()?,
|
||||
"CLI Session".to_string(),
|
||||
SessionType::User,
|
||||
)
|
||||
.await?;
|
||||
Ok(Some(session.id))
|
||||
}
|
||||
}
|
||||
@@ -387,7 +388,8 @@ async fn lookup_session_id(identifier: Identifier) -> Result<String> {
|
||||
if let Some(session_id) = identifier.session_id {
|
||||
Ok(session_id)
|
||||
} else if let Some(name) = identifier.name {
|
||||
let sessions = SessionManager::list_sessions().await?;
|
||||
let session_manager = SessionManager::instance();
|
||||
let sessions = session_manager.list_sessions().await?;
|
||||
sessions
|
||||
.into_iter()
|
||||
.find(|s| s.name == name || s.id == name)
|
||||
@@ -1008,10 +1010,15 @@ async fn handle_session_subcommand(command: SessionCommand) -> Result<()> {
|
||||
output,
|
||||
format,
|
||||
} => {
|
||||
let session_manager = SessionManager::instance();
|
||||
let session_identifier = if let Some(id) = identifier {
|
||||
lookup_session_id(id).await?
|
||||
} else {
|
||||
match crate::commands::session::prompt_interactive_session_selection().await {
|
||||
match crate::commands::session::prompt_interactive_session_selection(
|
||||
&session_manager,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
@@ -1023,10 +1030,15 @@ async fn handle_session_subcommand(command: SessionCommand) -> Result<()> {
|
||||
.await?;
|
||||
}
|
||||
SessionCommand::Diagnostics { identifier, output } => {
|
||||
let session_manager = SessionManager::instance();
|
||||
let session_id = if let Some(id) = identifier {
|
||||
lookup_session_id(id).await?
|
||||
} else {
|
||||
match crate::commands::session::prompt_interactive_session_selection().await {
|
||||
match crate::commands::session::prompt_interactive_session_selection(
|
||||
&session_manager,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
@@ -1469,7 +1481,7 @@ pub async fn cli() -> anyhow::Result<()> {
|
||||
Some(Command::Configure {}) => handle_configure().await,
|
||||
Some(Command::Info { verbose }) => handle_info(verbose),
|
||||
Some(Command::Mcp { server }) => handle_mcp_command(server).await,
|
||||
Some(Command::Acp { builtins }) => run_acp_agent(builtins).await,
|
||||
Some(Command::Acp { builtins }) => goose_acp::server::run(builtins).await,
|
||||
Some(Command::Session {
|
||||
command: Some(cmd), ..
|
||||
}) => handle_session_subcommand(cmd).await,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ use crate::session::SessionBuilderConfig;
|
||||
use crate::{logging, CliSession};
|
||||
use async_trait::async_trait;
|
||||
use goose::conversation::Conversation;
|
||||
use goose::session::session_manager::Session;
|
||||
use goose_bench::bench_session::{BenchAgent, BenchBaseSession};
|
||||
use goose_bench::eval_suites::ExtensionRequirements;
|
||||
use std::sync::Arc;
|
||||
@@ -25,8 +26,8 @@ impl BenchBaseSession for CliSession {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_session_id(&self) -> anyhow::Result<String> {
|
||||
Ok(self.session_id().to_string())
|
||||
async fn get_session(&self) -> anyhow::Result<Session> {
|
||||
self.get_session().await
|
||||
}
|
||||
}
|
||||
pub async fn agent_generator(
|
||||
|
||||
@@ -22,7 +22,7 @@ use goose::model::ModelConfig;
|
||||
use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
|
||||
use goose::providers::provider_test::test_provider_configuration;
|
||||
use goose::providers::{create, providers, retry_operation, RetryConfig};
|
||||
use goose::session::{SessionManager, SessionType};
|
||||
use goose::session::SessionType;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1081,8 +1081,7 @@ pub fn remove_extension_dialog() -> anyhow::Result<()> {
|
||||
|
||||
for name in selected {
|
||||
remove_extension(&name_to_key(name));
|
||||
let mut permission_manager = PermissionManager::default();
|
||||
permission_manager.remove_extension(&name_to_key(name));
|
||||
PermissionManager::instance().remove_extension(&name_to_key(name));
|
||||
cliclack::outro(format!("Removed {} extension", style(name).green()))?;
|
||||
}
|
||||
|
||||
@@ -1396,15 +1395,19 @@ pub async fn configure_tool_permissions_dialog() -> anyhow::Result<()> {
|
||||
.expect("No model configured. Please set model first");
|
||||
let model_config = ModelConfig::new(&model)?;
|
||||
|
||||
let session = SessionManager::create_session(
|
||||
std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")),
|
||||
"Tool Permission Configuration".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let agent = Agent::new();
|
||||
let new_provider = create(&provider_name, model_config).await?;
|
||||
|
||||
let session = agent
|
||||
.config
|
||||
.session_manager
|
||||
.create_session(
|
||||
std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")),
|
||||
"Tool Permission Configuration".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await?;
|
||||
|
||||
agent.update_provider(new_provider, &session.id).await?;
|
||||
if let Some(config) = get_extension_by_name(&selected_extension_name) {
|
||||
agent
|
||||
@@ -1426,9 +1429,9 @@ pub async fn configure_tool_permissions_dialog() -> anyhow::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut permission_manager = PermissionManager::default();
|
||||
let permission_manager = PermissionManager::instance();
|
||||
let selected_tools = agent
|
||||
.list_tools(Some(selected_extension_name.clone()))
|
||||
.list_tools(&session.id, Some(selected_extension_name.clone()))
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|tool| {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod acp;
|
||||
pub mod bench;
|
||||
pub mod configure;
|
||||
pub mod info;
|
||||
|
||||
@@ -3,17 +3,10 @@ use goose::scheduler::{
|
||||
get_default_scheduled_recipes_dir, get_default_scheduler_storage_path, ScheduledJob, Scheduler,
|
||||
SchedulerError,
|
||||
};
|
||||
use goose::session::SessionManager;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
async fn create_scheduler() -> Result<Arc<Scheduler>> {
|
||||
let storage_path =
|
||||
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
|
||||
Scheduler::new(storage_path)
|
||||
.await
|
||||
.context("Failed to initialize scheduler")
|
||||
}
|
||||
|
||||
fn validate_cron_expression(cron: &str) -> Result<()> {
|
||||
// Basic validation and helpful suggestions
|
||||
if cron.trim().is_empty() {
|
||||
@@ -97,7 +90,12 @@ pub async fn handle_schedule_add(
|
||||
process_start_time: None,
|
||||
};
|
||||
|
||||
let scheduler = create_scheduler().await?;
|
||||
let scheduler_storage_path =
|
||||
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
|
||||
let session_manager = Arc::new(SessionManager::instance());
|
||||
let scheduler = Scheduler::new(scheduler_storage_path, session_manager)
|
||||
.await
|
||||
.context("Failed to initialize scheduler")?;
|
||||
|
||||
match scheduler.add_scheduled_job(job, true).await {
|
||||
Ok(_) => {
|
||||
@@ -139,7 +137,12 @@ pub async fn handle_schedule_add(
|
||||
}
|
||||
|
||||
pub async fn handle_schedule_list() -> Result<()> {
|
||||
let scheduler = create_scheduler().await?;
|
||||
let scheduler_storage_path =
|
||||
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
|
||||
let session_manager = Arc::new(SessionManager::instance());
|
||||
let scheduler = Scheduler::new(scheduler_storage_path, session_manager)
|
||||
.await
|
||||
.context("Failed to initialize scheduler")?;
|
||||
|
||||
let jobs = scheduler.list_scheduled_jobs().await;
|
||||
if jobs.is_empty() {
|
||||
@@ -170,7 +173,12 @@ pub async fn handle_schedule_list() -> Result<()> {
|
||||
}
|
||||
|
||||
pub async fn handle_schedule_remove(schedule_id: String) -> Result<()> {
|
||||
let scheduler = create_scheduler().await?;
|
||||
let scheduler_storage_path =
|
||||
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
|
||||
let session_manager = Arc::new(SessionManager::instance());
|
||||
let scheduler = Scheduler::new(scheduler_storage_path, session_manager)
|
||||
.await
|
||||
.context("Failed to initialize scheduler")?;
|
||||
|
||||
match scheduler.remove_scheduled_job(&schedule_id, true).await {
|
||||
Ok(_) => {
|
||||
@@ -193,7 +201,12 @@ pub async fn handle_schedule_remove(schedule_id: String) -> Result<()> {
|
||||
}
|
||||
|
||||
pub async fn handle_schedule_sessions(schedule_id: String, limit: Option<usize>) -> Result<()> {
|
||||
let scheduler = create_scheduler().await?;
|
||||
let scheduler_storage_path =
|
||||
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
|
||||
let session_manager = Arc::new(SessionManager::instance());
|
||||
let scheduler = Scheduler::new(scheduler_storage_path, session_manager)
|
||||
.await
|
||||
.context("Failed to initialize scheduler")?;
|
||||
|
||||
match scheduler.sessions(&schedule_id, limit.unwrap_or(50)).await {
|
||||
Ok(sessions) => {
|
||||
@@ -225,7 +238,12 @@ pub async fn handle_schedule_sessions(schedule_id: String, limit: Option<usize>)
|
||||
}
|
||||
|
||||
pub async fn handle_schedule_run_now(schedule_id: String) -> Result<()> {
|
||||
let scheduler = create_scheduler().await?;
|
||||
let scheduler_storage_path =
|
||||
get_default_scheduler_storage_path().context("Failed to get scheduler storage path")?;
|
||||
let session_manager = Arc::new(SessionManager::instance());
|
||||
let scheduler = Scheduler::new(scheduler_storage_path, session_manager)
|
||||
.await
|
||||
.context("Failed to initialize scheduler")?;
|
||||
|
||||
match scheduler.run_now(&schedule_id).await {
|
||||
Ok(session_id) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::path::PathBuf;
|
||||
|
||||
const TRUNCATED_DESC_LENGTH: usize = 60;
|
||||
|
||||
pub async fn remove_sessions(sessions: Vec<Session>) -> Result<()> {
|
||||
async fn remove_sessions(session_manager: &SessionManager, sessions: Vec<Session>) -> Result<()> {
|
||||
println!("The following sessions will be removed:");
|
||||
for session in &sessions {
|
||||
println!("- {} {}", session.id, session.name);
|
||||
@@ -23,7 +23,7 @@ pub async fn remove_sessions(sessions: Vec<Session>) -> Result<()> {
|
||||
|
||||
if should_delete {
|
||||
for session in sessions {
|
||||
SessionManager::delete_session(&session.id).await?;
|
||||
session_manager.delete_session(&session.id).await?;
|
||||
println!("Session `{}` removed.", session.id);
|
||||
}
|
||||
} else {
|
||||
@@ -76,7 +76,8 @@ pub async fn handle_session_remove(
|
||||
name: Option<String>,
|
||||
regex_string: Option<String>,
|
||||
) -> Result<()> {
|
||||
let all_sessions = match SessionManager::list_sessions().await {
|
||||
let session_manager = SessionManager::instance();
|
||||
let all_sessions = match session_manager.list_sessions().await {
|
||||
Ok(sessions) => sessions,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to retrieve sessions: {:?}", e);
|
||||
@@ -125,7 +126,7 @@ pub async fn handle_session_remove(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
remove_sessions(matched_sessions).await
|
||||
remove_sessions(&session_manager, matched_sessions).await
|
||||
}
|
||||
|
||||
pub async fn handle_session_list(
|
||||
@@ -134,7 +135,8 @@ pub async fn handle_session_list(
|
||||
working_dir: Option<PathBuf>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<()> {
|
||||
let mut sessions = SessionManager::list_sessions().await?;
|
||||
let session_manager = SessionManager::instance();
|
||||
let mut sessions = session_manager.list_sessions().await?;
|
||||
|
||||
if let Some(ref pat) = working_dir {
|
||||
let pat_lower = pat.to_string_lossy().to_lowercase();
|
||||
@@ -181,7 +183,8 @@ pub async fn handle_session_export(
|
||||
output_path: Option<PathBuf>,
|
||||
format: String,
|
||||
) -> Result<()> {
|
||||
let session = match SessionManager::get_session(&session_id, true).await {
|
||||
let session_manager = SessionManager::instance();
|
||||
let session = match session_manager.get_session(&session_id, true).await {
|
||||
Ok(session) => session,
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -222,12 +225,15 @@ pub async fn handle_diagnostics(session_id: &str, output_path: Option<PathBuf>)
|
||||
session_id
|
||||
);
|
||||
|
||||
let diagnostics_data = generate_diagnostics(session_id).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to write to generate diagnostics bundle for session '{}'",
|
||||
session_id
|
||||
)
|
||||
})?;
|
||||
let session_manager = SessionManager::instance();
|
||||
let diagnostics_data = generate_diagnostics(&session_manager, session_id)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to write to generate diagnostics bundle for session '{}'",
|
||||
session_id
|
||||
)
|
||||
})?;
|
||||
|
||||
let output_file = if let Some(path) = output_path {
|
||||
path.clone()
|
||||
@@ -319,8 +325,10 @@ fn export_session_to_markdown(
|
||||
/// Prompt the user to interactively select a session
|
||||
///
|
||||
/// Shows a list of available sessions and lets the user select one
|
||||
pub async fn prompt_interactive_session_selection() -> Result<String> {
|
||||
let sessions = SessionManager::list_sessions().await?;
|
||||
pub async fn prompt_interactive_session_selection(
|
||||
session_manager: &SessionManager,
|
||||
) -> Result<String> {
|
||||
let sessions = session_manager.list_sessions().await?;
|
||||
|
||||
if sessions.is_empty() {
|
||||
return Err(anyhow::anyhow!("No sessions found"));
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono;
|
||||
use goose::conversation::message::{Message, MessageContent, MessageMetadata};
|
||||
use goose::session::SessionManager;
|
||||
use goose::session::SessionType;
|
||||
use goose::session::{SessionManager, SessionType};
|
||||
use rmcp::model::Role;
|
||||
|
||||
use crate::session::{build_session, SessionBuilderConfig};
|
||||
@@ -119,10 +118,13 @@ pub async fn handle_term_init(
|
||||
with_command_not_found: bool,
|
||||
) -> Result<()> {
|
||||
let config = shell.config();
|
||||
let session_manager = SessionManager::instance();
|
||||
|
||||
let working_dir = std::env::current_dir()?;
|
||||
let named_session = if let Some(ref name) = name {
|
||||
let sessions = SessionManager::list_sessions_by_types(&[SessionType::Terminal]).await?;
|
||||
let sessions = session_manager
|
||||
.list_sessions_by_types(&[SessionType::Terminal])
|
||||
.await?;
|
||||
sessions.into_iter().find(|s| s.name == *name)
|
||||
} else {
|
||||
None
|
||||
@@ -131,15 +133,17 @@ pub async fn handle_term_init(
|
||||
let session = match named_session {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
let session = SessionManager::create_session(
|
||||
working_dir,
|
||||
"Goose Term Session".to_string(),
|
||||
SessionType::Terminal,
|
||||
)
|
||||
.await?;
|
||||
let session = session_manager
|
||||
.create_session(
|
||||
working_dir,
|
||||
"Goose Term Session".to_string(),
|
||||
SessionType::Terminal,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(name) = name {
|
||||
SessionManager::update_session(&session.id)
|
||||
session_manager
|
||||
.update(&session.id)
|
||||
.user_provided_name(name)
|
||||
.apply()
|
||||
.await?;
|
||||
@@ -184,7 +188,8 @@ pub async fn handle_term_log(command: String) -> Result<()> {
|
||||
)
|
||||
.with_metadata(MessageMetadata::user_only());
|
||||
|
||||
SessionManager::add_message(&session_id, &message).await?;
|
||||
let session_manager = SessionManager::instance();
|
||||
session_manager.add_message(&session_id, &message).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -201,13 +206,15 @@ pub async fn handle_term_run(prompt: Vec<String>) -> Result<()> {
|
||||
})?;
|
||||
|
||||
let working_dir = std::env::current_dir()?;
|
||||
let session_manager = SessionManager::instance();
|
||||
|
||||
SessionManager::update_session(&session_id)
|
||||
session_manager
|
||||
.update(&session_id)
|
||||
.working_dir(working_dir)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
let session = SessionManager::get_session(&session_id, true).await?;
|
||||
let session = session_manager.get_session(&session_id, true).await?;
|
||||
let user_messages_after_last_assistant: Vec<&Message> =
|
||||
if let Some(conv) = &session.conversation {
|
||||
conv.messages()
|
||||
@@ -220,7 +227,9 @@ pub async fn handle_term_run(prompt: Vec<String>) -> Result<()> {
|
||||
};
|
||||
|
||||
if let Some(oldest_user) = user_messages_after_last_assistant.last() {
|
||||
SessionManager::truncate_conversation(&session_id, oldest_user.created).await?;
|
||||
session_manager
|
||||
.truncate_conversation(&session_id, oldest_user.created)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let prompt_with_context = if user_messages_after_last_assistant.is_empty() {
|
||||
@@ -260,7 +269,8 @@ pub async fn handle_term_info() -> Result<()> {
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
let session = SessionManager::get_session(&session_id, false).await.ok();
|
||||
let session_manager = SessionManager::instance();
|
||||
let session = session_manager.get_session(&session_id, false).await.ok();
|
||||
let total_tokens = session.as_ref().and_then(|s| s.total_tokens).unwrap_or(0) as usize;
|
||||
|
||||
let config = goose::config::Config::global();
|
||||
|
||||
@@ -16,7 +16,6 @@ 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;
|
||||
use std::{net::ToSocketAddrs, sync::Arc};
|
||||
@@ -171,14 +170,17 @@ fn get_provider_and_model() -> (String, String) {
|
||||
async fn create_agent(provider_name: &str, model: &str) -> Result<Agent> {
|
||||
let model_config = goose::model::ModelConfig::new(model)?;
|
||||
|
||||
let init_session = SessionManager::create_session(
|
||||
std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")),
|
||||
"Web Agent Initialization".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let agent = Agent::new();
|
||||
|
||||
let session_manager = agent.config.session_manager.clone();
|
||||
let init_session = session_manager
|
||||
.create_session(
|
||||
std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")),
|
||||
"Web Agent Initialization".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let provider = goose::providers::create(provider_name, model_config).await?;
|
||||
agent.update_provider(provider, &init_session.id).await?;
|
||||
|
||||
@@ -284,14 +286,21 @@ pub async fn handle_web(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serve_index(uri: Uri) -> 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()))?;
|
||||
async fn serve_index(
|
||||
State(state): State<AppState>,
|
||||
uri: Uri,
|
||||
) -> Result<Redirect, (http::StatusCode, String)> {
|
||||
let session = state
|
||||
.agent
|
||||
.config
|
||||
.session_manager
|
||||
.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()))?;
|
||||
|
||||
let redirect_url = if let Some(query) = uri.query() {
|
||||
format!("/session/{}?{}", session.id, query)
|
||||
@@ -351,8 +360,8 @@ async fn health_check() -> Json<serde_json::Value> {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn list_sessions() -> Json<serde_json::Value> {
|
||||
match SessionManager::list_sessions().await {
|
||||
async fn list_sessions(State(state): State<AppState>) -> Json<serde_json::Value> {
|
||||
match state.agent.config.session_manager.list_sessions().await {
|
||||
Ok(sessions) => {
|
||||
let mut session_info = Vec::new();
|
||||
|
||||
@@ -375,9 +384,16 @@ async fn list_sessions() -> Json<serde_json::Value> {
|
||||
}
|
||||
}
|
||||
async fn get_session(
|
||||
State(state): State<AppState>,
|
||||
axum::extract::Path(session_id): axum::extract::Path<String>,
|
||||
) -> Json<serde_json::Value> {
|
||||
match SessionManager::get_session(&session_id, true).await {
|
||||
match state
|
||||
.agent
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(&session_id, true)
|
||||
.await
|
||||
{
|
||||
Ok(session) => Json(serde_json::json!({
|
||||
"metadata": session,
|
||||
"messages": session.conversation.unwrap_or_default().messages()
|
||||
@@ -544,7 +560,11 @@ async fn process_message_streaming(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let session = SessionManager::get_session(&session_id, true).await?;
|
||||
let session = agent
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(&session_id, true)
|
||||
.await?;
|
||||
let mut messages = session.conversation.unwrap_or_default();
|
||||
messages.push(user_message.clone());
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! MockClient is a mock implementation of the McpClientTrait for testing purposes.
|
||||
//! add a tool you want to have around and then add the client to the extension router
|
||||
|
||||
use goose::agents::mcp_client::{Error, McpClientTrait};
|
||||
use goose::agents::mcp_client::{Error, McpClientTrait, McpMeta};
|
||||
use rmcp::{
|
||||
model::{
|
||||
CallToolResult, Content, ErrorData, GetPromptResult, ListPromptsResult,
|
||||
@@ -94,6 +94,7 @@ impl McpClientTrait for MockClient {
|
||||
&self,
|
||||
name: &str,
|
||||
arguments: Option<serde_json::Map<String, Value>>,
|
||||
_meta: McpMeta,
|
||||
_cancel_token: CancellationToken,
|
||||
) -> Result<CallToolResult, Error> {
|
||||
if let Some(handler) = self.handlers.get(name) {
|
||||
|
||||
@@ -6,7 +6,9 @@ use crate::scenario_tests::mock_client::weather_client;
|
||||
use crate::scenario_tests::provider_configs::{get_provider_configs, ProviderConfig};
|
||||
use crate::session::CliSession;
|
||||
use anyhow::Result;
|
||||
use goose::agents::Agent;
|
||||
use goose::agents::{Agent, AgentConfig};
|
||||
use goose::config::permission::PermissionManager;
|
||||
use goose::config::GooseMode;
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::{create, testprovider::TestProvider};
|
||||
use goose::session::session_manager::SessionType;
|
||||
@@ -14,6 +16,7 @@ use goose::session::SessionManager;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use tempfile::TempDir;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub const SCENARIO_TESTS_DIR: &str = "src/scenario_tests";
|
||||
@@ -198,7 +201,11 @@ where
|
||||
|
||||
let mock_client = weather_client();
|
||||
|
||||
let agent = Agent::new();
|
||||
let temp_dir = TempDir::new()?;
|
||||
let session_manager = Arc::new(SessionManager::new(temp_dir.path().to_path_buf()));
|
||||
let permission_manager = Arc::new(PermissionManager::new(temp_dir.path().to_path_buf()));
|
||||
let agent_config = AgentConfig::new(session_manager, permission_manager, None, GooseMode::Auto); // no scheduler needed for scenario tests
|
||||
let agent = Agent::with_config(agent_config);
|
||||
agent
|
||||
.extension_manager
|
||||
.add_client(
|
||||
@@ -217,12 +224,15 @@ where
|
||||
)
|
||||
.await;
|
||||
|
||||
let session = SessionManager::create_session(
|
||||
PathBuf::default(),
|
||||
"scenario-runner".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await?;
|
||||
let session = agent
|
||||
.config
|
||||
.session_manager
|
||||
.create_session(
|
||||
PathBuf::default(),
|
||||
"scenario-runner".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await?;
|
||||
|
||||
agent
|
||||
.update_provider(
|
||||
|
||||
@@ -10,9 +10,7 @@ use goose::config::{
|
||||
use goose::providers::create;
|
||||
use goose::recipe::{Response, SubRecipe};
|
||||
|
||||
use goose::agents::extension::PlatformExtensionContext;
|
||||
use goose::session::session_manager::SessionType;
|
||||
use goose::session::SessionManager;
|
||||
use goose::session::{EnabledExtensionsState, ExtensionState};
|
||||
use rustyline::EditMode;
|
||||
use std::collections::HashSet;
|
||||
@@ -147,12 +145,15 @@ async fn offer_extension_debugging_help(
|
||||
// Create a minimal agent for debugging
|
||||
let debug_agent = Agent::new();
|
||||
|
||||
let session = SessionManager::create_session(
|
||||
std::env::current_dir()?,
|
||||
"CLI Session".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await?;
|
||||
let session = debug_agent
|
||||
.config
|
||||
.session_manager
|
||||
.create_session(
|
||||
std::env::current_dir()?,
|
||||
"CLI Session".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug_agent.update_provider(provider, &session.id).await?;
|
||||
|
||||
@@ -252,10 +253,12 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
goose::posthog::set_session_context("cli", session_config.resume);
|
||||
|
||||
let config = Config::global();
|
||||
let agent: Agent = Agent::new();
|
||||
let session_manager = agent.config.session_manager.clone();
|
||||
|
||||
let (saved_provider, saved_model_config) = if session_config.resume {
|
||||
if let Some(ref session_id) = session_config.session_id {
|
||||
match SessionManager::get_session(session_id, false).await {
|
||||
match session_manager.get_session(session_id, false).await {
|
||||
Ok(session_data) => (session_data.provider_name, session_data.model_config),
|
||||
Err(_) => (None, None),
|
||||
}
|
||||
@@ -310,8 +313,6 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
.with_temperature(temperature)
|
||||
};
|
||||
|
||||
let agent: Agent = Agent::new();
|
||||
|
||||
agent
|
||||
.apply_recipe_components(
|
||||
session_config.sub_recipes,
|
||||
@@ -348,17 +349,14 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
|
||||
let session_id: String = if session_config.no_session {
|
||||
let working_dir = std::env::current_dir().expect("Could not get working directory");
|
||||
let session = SessionManager::create_session(
|
||||
working_dir,
|
||||
"CLI Session".to_string(),
|
||||
SessionType::Hidden,
|
||||
)
|
||||
.await
|
||||
.expect("Could not create session");
|
||||
let session = session_manager
|
||||
.create_session(working_dir, "CLI Session".to_string(), SessionType::Hidden)
|
||||
.await
|
||||
.expect("Could not create session");
|
||||
session.id
|
||||
} else if session_config.resume {
|
||||
if let Some(session_id) = session_config.session_id {
|
||||
match SessionManager::get_session(&session_id, false).await {
|
||||
match session_manager.get_session(&session_id, false).await {
|
||||
Ok(_) => session_id,
|
||||
Err(_) => {
|
||||
output::render_error(&format!(
|
||||
@@ -369,7 +367,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match SessionManager::list_sessions().await {
|
||||
match session_manager.list_sessions().await {
|
||||
Ok(sessions) if !sessions.is_empty() => sessions[0].id.clone(),
|
||||
_ => {
|
||||
output::render_error("Cannot resume - no previous sessions found");
|
||||
@@ -389,16 +387,11 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
agent
|
||||
.extension_manager
|
||||
.set_context(PlatformExtensionContext {
|
||||
session_id: Some(session_id.clone()),
|
||||
extension_manager: Some(Arc::downgrade(&agent.extension_manager)),
|
||||
})
|
||||
.await;
|
||||
|
||||
if session_config.resume {
|
||||
let session = SessionManager::get_session(&session_id, false)
|
||||
let session = agent
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(&session_id, false)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
output::render_error(&format!("Failed to read session metadata: {}", e));
|
||||
@@ -451,7 +444,12 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
let extensions_to_run: Vec<_> = if let Some(extensions) = session_config.extensions_override {
|
||||
extensions.into_iter().collect()
|
||||
} else if session_config.resume {
|
||||
match SessionManager::get_session(&session_id, false).await {
|
||||
match agent
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(&session_id, false)
|
||||
.await
|
||||
{
|
||||
Ok(session_data) => {
|
||||
if let Some(saved_state) =
|
||||
EnabledExtensionsState::from_extension_data(&session_data.extension_data)
|
||||
|
||||
@@ -33,7 +33,6 @@ use goose::agents::extension::{Envs, ExtensionConfig, PLATFORM_EXTENSIONS};
|
||||
use goose::agents::types::RetryConfig;
|
||||
use goose::agents::{Agent, SessionConfig, COMPACT_TRIGGERS};
|
||||
use goose::config::{Config, GooseMode};
|
||||
use goose::session::SessionManager;
|
||||
use input::InputResult;
|
||||
use rmcp::model::PromptMessage;
|
||||
use rmcp::model::ServerNotification;
|
||||
@@ -229,7 +228,10 @@ impl CliSession {
|
||||
retry_config: Option<RetryConfig>,
|
||||
output_format: String,
|
||||
) -> Self {
|
||||
let messages = SessionManager::get_session(&session_id, true)
|
||||
let messages = agent
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(&session_id, true)
|
||||
.await
|
||||
.map(|session| session.conversation.unwrap_or_default())
|
||||
.unwrap();
|
||||
@@ -693,14 +695,22 @@ impl CliSession {
|
||||
}
|
||||
|
||||
async fn handle_clear(&mut self) -> Result<()> {
|
||||
if let Err(e) =
|
||||
SessionManager::replace_conversation(&self.session_id, &Conversation::default()).await
|
||||
if let Err(e) = self
|
||||
.agent
|
||||
.config
|
||||
.session_manager
|
||||
.replace_conversation(&self.session_id, &Conversation::default())
|
||||
.await
|
||||
{
|
||||
output::render_error(&format!("Failed to clear session: {}", e));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Err(e) = SessionManager::update_session(&self.session_id)
|
||||
if let Err(e) = self
|
||||
.agent
|
||||
.config
|
||||
.session_manager
|
||||
.update(&self.session_id)
|
||||
.total_tokens(Some(0))
|
||||
.input_tokens(Some(0))
|
||||
.output_tokens(Some(0))
|
||||
@@ -1021,7 +1031,13 @@ impl CliSession {
|
||||
}
|
||||
|
||||
if is_json_mode {
|
||||
let metadata = match SessionManager::get_session(&self.session_id, false).await {
|
||||
let metadata = match self
|
||||
.agent
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(&self.session_id, false)
|
||||
.await
|
||||
{
|
||||
Ok(session) => JsonMetadata {
|
||||
total_tokens: session.total_tokens,
|
||||
status: "completed".to_string(),
|
||||
@@ -1037,7 +1053,11 @@ impl CliSession {
|
||||
};
|
||||
println!("{}", serde_json::to_string_pretty(&json_output)?);
|
||||
} else if is_stream_json_mode {
|
||||
let total_tokens = SessionManager::get_session(&self.session_id, false)
|
||||
let total_tokens = self
|
||||
.agent
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(&self.session_id, false)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|s| s.total_tokens);
|
||||
@@ -1207,7 +1227,11 @@ impl CliSession {
|
||||
}
|
||||
|
||||
pub async fn get_session(&self) -> Result<goose::session::Session> {
|
||||
SessionManager::get_session(&self.session_id, false).await
|
||||
self.agent
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(&self.session_id, false)
|
||||
.await
|
||||
}
|
||||
|
||||
// Get the session's total token usage
|
||||
|
||||
Reference in New Issue
Block a user