disable scheduler by default for acp (#10781)
This commit is contained in:
+1
-1
@@ -191,7 +191,7 @@ To debug the external ACP backend, run it from an IDE. The configuration will de
|
||||
|
||||
```
|
||||
export GOOSE_SERVER__SECRET_KEY=test
|
||||
cargo run --package goose-cli --bin goose -- serve --platform desktop --host 127.0.0.1 --port 3000
|
||||
cargo run --package goose-cli --bin goose -- serve --platform desktop --enable-scheduler --host 127.0.0.1 --port 3000
|
||||
```
|
||||
|
||||
The `debug-ui` recipe connects to `http://127.0.0.1:3000` by default. If the
|
||||
|
||||
@@ -145,7 +145,7 @@ run-docs:
|
||||
# Run server
|
||||
run-server:
|
||||
@echo "Running external ACP backend..."
|
||||
GOOSE_SERVER__SECRET_KEY="${GOOSE_SERVER__SECRET_KEY:-test}" cargo run -p goose-cli --bin goose -- serve --platform desktop --host 127.0.0.1 --port 3000
|
||||
GOOSE_SERVER__SECRET_KEY="${GOOSE_SERVER__SECRET_KEY:-test}" cargo run -p goose-cli --bin goose -- serve --platform desktop --enable-scheduler --host 127.0.0.1 --port 3000
|
||||
|
||||
# Check if generated ACP schema and TypeScript types are up-to-date
|
||||
check-acp-schema: generate-acp-types
|
||||
|
||||
@@ -838,6 +838,9 @@ enum Command {
|
||||
value_delimiter = ','
|
||||
)]
|
||||
builtins: Vec<String>,
|
||||
|
||||
#[arg(long, help = "Enable scheduled recipe execution")]
|
||||
enable_scheduler: bool,
|
||||
},
|
||||
|
||||
/// Start ACP server over HTTP and WebSocket
|
||||
@@ -884,6 +887,9 @@ enum Command {
|
||||
help = "Allow an exact Origin value for ACP CORS; may be specified multiple times and replaces the default loopback origins"
|
||||
)]
|
||||
allowed_origins: Vec<String>,
|
||||
|
||||
#[arg(long, help = "Enable scheduled recipe execution")]
|
||||
enable_scheduler: bool,
|
||||
},
|
||||
|
||||
/// Start or resume interactive chat sessions
|
||||
@@ -1388,6 +1394,7 @@ struct ServeCommandArgs {
|
||||
builtins: Vec<String>,
|
||||
dangerously_unauthenticated: bool,
|
||||
allowed_origins: Vec<String>,
|
||||
enable_scheduler: bool,
|
||||
}
|
||||
|
||||
async fn handle_serve_command(args: ServeCommandArgs) -> Result<()> {
|
||||
@@ -1409,6 +1416,7 @@ async fn handle_serve_command(args: ServeCommandArgs) -> Result<()> {
|
||||
builtins,
|
||||
dangerously_unauthenticated,
|
||||
allowed_origins,
|
||||
enable_scheduler,
|
||||
} = args;
|
||||
|
||||
let builtins = if builtins.is_empty() {
|
||||
@@ -1435,6 +1443,7 @@ async fn handle_serve_command(args: ServeCommandArgs) -> Result<()> {
|
||||
config_dir: Paths::config_dir(),
|
||||
goose_platform: platform.into(),
|
||||
additional_source_roots,
|
||||
enable_scheduler,
|
||||
}));
|
||||
let env_secret = std::env::var(GOOSE_SERVER_SECRET_KEY_ENV)
|
||||
.ok()
|
||||
@@ -1839,7 +1848,9 @@ fn parse_run_input(
|
||||
Ok(Some((input_config, Some(recipe))))
|
||||
}
|
||||
(None, None, None) => {
|
||||
eprintln!("Error: Must provide either --instructions (-i), --text (-t), or --recipe. Use -i - for stdin.");
|
||||
eprintln!(
|
||||
"Error: Must provide either --instructions (-i), --text (-t), or --recipe. Use -i - for stdin."
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -2228,7 +2239,10 @@ pub async fn cli() -> anyhow::Result<()> {
|
||||
Some(Command::Doctor {}) => crate::commands::doctor::handle_doctor().await,
|
||||
Some(Command::Info { verbose, check }) => handle_info(verbose, check).await,
|
||||
Some(Command::Mcp { server }) => handle_mcp_command(server).await,
|
||||
Some(Command::Acp { builtins }) => goose::acp::server::run(builtins).await,
|
||||
Some(Command::Acp {
|
||||
builtins,
|
||||
enable_scheduler,
|
||||
}) => goose::acp::server::run(builtins, enable_scheduler).await,
|
||||
Some(Command::Serve {
|
||||
host,
|
||||
port,
|
||||
@@ -2239,6 +2253,7 @@ pub async fn cli() -> anyhow::Result<()> {
|
||||
builtins,
|
||||
dangerously_unauthenticated,
|
||||
allowed_origins,
|
||||
enable_scheduler,
|
||||
}) => {
|
||||
handle_serve_command(ServeCommandArgs {
|
||||
host,
|
||||
@@ -2250,6 +2265,7 @@ pub async fn cli() -> anyhow::Result<()> {
|
||||
builtins,
|
||||
dangerously_unauthenticated,
|
||||
allowed_origins,
|
||||
enable_scheduler,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ pub struct GooseAcpAgentOptions {
|
||||
pub disable_session_naming: bool,
|
||||
pub goose_platform: GoosePlatform,
|
||||
pub additional_source_roots: Vec<SourceRoot>,
|
||||
pub scheduler: Arc<dyn SchedulerTrait>,
|
||||
pub scheduler: Option<Arc<dyn SchedulerTrait>>,
|
||||
}
|
||||
|
||||
pub struct GooseAcpAgent {
|
||||
@@ -604,7 +604,7 @@ impl GooseAcpAgent {
|
||||
let agent_config = AgentConfig::new(
|
||||
Arc::clone(&session_manager),
|
||||
Arc::clone(&permission_manager),
|
||||
Some(options.scheduler),
|
||||
options.scheduler,
|
||||
Config::global().get_goose_mode().unwrap_or_default(),
|
||||
options.disable_session_naming,
|
||||
options.goose_platform.clone(),
|
||||
@@ -2285,7 +2285,7 @@ impl agent_client_protocol::ConnectTo<Client> for GooseAgentConnection {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(builtins: Vec<String>) -> Result<()> {
|
||||
pub async fn run(builtins: Vec<String>, enable_scheduler: bool) -> Result<()> {
|
||||
info!("listening on stdio");
|
||||
|
||||
let outgoing = tokio::io::stdout().compat_write();
|
||||
@@ -2298,6 +2298,7 @@ pub async fn run(builtins: Vec<String>) -> Result<()> {
|
||||
config_dir: Paths::config_dir(),
|
||||
goose_platform: GoosePlatform::GooseCli,
|
||||
additional_source_roots: Vec::new(),
|
||||
enable_scheduler,
|
||||
},
|
||||
);
|
||||
let agent = server.create_agent().await?;
|
||||
|
||||
@@ -153,7 +153,10 @@ impl GooseAcpAgent {
|
||||
.collect();
|
||||
*self.recipe_path_cache.lock().await = recipe_file_hash_map;
|
||||
|
||||
let scheduled_jobs = self.agent_manager.scheduler().list_scheduled_jobs().await;
|
||||
let scheduled_jobs = match self.agent_manager.scheduler() {
|
||||
Some(scheduler) => scheduler.list_scheduled_jobs().await,
|
||||
None => Vec::new(),
|
||||
};
|
||||
let schedule_map: HashMap<_, _> = scheduled_jobs
|
||||
.into_iter()
|
||||
.map(|job| (PathBuf::from(job.source), job.cron))
|
||||
@@ -198,10 +201,9 @@ impl GooseAcpAgent {
|
||||
&self,
|
||||
req: ScheduleRecipeRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
let scheduler = self.require_scheduler()?;
|
||||
let file_path = self.resolve_recipe_path_by_id(&req.id).await?;
|
||||
if let Err(err) = self
|
||||
.agent_manager
|
||||
.scheduler()
|
||||
if let Err(err) = scheduler
|
||||
.schedule_recipe(file_path, req.cron_schedule)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -12,6 +12,8 @@ use super::{build_session_info, GooseAcpAgent, ResultExt};
|
||||
use crate::recipe::validate_recipe::validate_recipe_template_from_content;
|
||||
use crate::recipe::Recipe;
|
||||
use crate::scheduler::{get_default_scheduled_recipes_dir, ScheduledJob, SchedulerError};
|
||||
use crate::scheduler_trait::SchedulerTrait;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn validate_schedule_id(id: &str) -> Result<(), agent_client_protocol::Error> {
|
||||
let is_valid = !id.is_empty()
|
||||
@@ -121,13 +123,21 @@ fn scheduled_job_to_dto(job: ScheduledJob) -> ScheduledJobDto {
|
||||
}
|
||||
|
||||
impl GooseAcpAgent {
|
||||
pub(super) fn require_scheduler(
|
||||
&self,
|
||||
) -> Result<Arc<dyn SchedulerTrait>, agent_client_protocol::Error> {
|
||||
self.agent_manager.scheduler().ok_or_else(|| {
|
||||
agent_client_protocol::Error::method_not_found()
|
||||
.data("Scheduled recipe execution is not enabled")
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) async fn on_list_schedules(
|
||||
&self,
|
||||
_req: ListSchedulesRequest,
|
||||
) -> Result<ListSchedulesResponse, agent_client_protocol::Error> {
|
||||
let jobs = self
|
||||
.agent_manager
|
||||
.scheduler()
|
||||
.require_scheduler()?
|
||||
.list_scheduled_jobs()
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -142,8 +152,7 @@ impl GooseAcpAgent {
|
||||
req: ListScheduleSessionsRequest,
|
||||
) -> Result<ListScheduleSessionsResponse, agent_client_protocol::Error> {
|
||||
let sessions = self
|
||||
.agent_manager
|
||||
.scheduler()
|
||||
.require_scheduler()?
|
||||
.sessions(&req.schedule_id, req.limit)
|
||||
.await
|
||||
.internal_err_ctx("Failed to fetch schedule sessions")?
|
||||
@@ -158,6 +167,7 @@ impl GooseAcpAgent {
|
||||
&self,
|
||||
req: CreateScheduleRequest,
|
||||
) -> Result<CreateScheduleResponse, agent_client_protocol::Error> {
|
||||
let scheduler = self.require_scheduler()?;
|
||||
let id = req.id.trim().to_string();
|
||||
validate_schedule_id(&id)?;
|
||||
|
||||
@@ -200,8 +210,7 @@ impl GooseAcpAgent {
|
||||
recipe_base_dir: None,
|
||||
};
|
||||
|
||||
self.agent_manager
|
||||
.scheduler()
|
||||
scheduler
|
||||
.add_scheduled_job(job.clone(), false)
|
||||
.await
|
||||
.map_err(create_schedule_error)?;
|
||||
@@ -215,8 +224,7 @@ impl GooseAcpAgent {
|
||||
&self,
|
||||
req: DeleteScheduleRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
self.agent_manager
|
||||
.scheduler()
|
||||
self.require_scheduler()?
|
||||
.remove_scheduled_job(&req.schedule_id, false)
|
||||
.await
|
||||
.map_err(schedule_not_found_or_internal)?;
|
||||
@@ -228,8 +236,7 @@ impl GooseAcpAgent {
|
||||
&self,
|
||||
req: PauseScheduleRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
self.agent_manager
|
||||
.scheduler()
|
||||
self.require_scheduler()?
|
||||
.pause_schedule(&req.schedule_id)
|
||||
.await
|
||||
.map_err(schedule_state_error)?;
|
||||
@@ -241,8 +248,7 @@ impl GooseAcpAgent {
|
||||
&self,
|
||||
req: UnpauseScheduleRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
self.agent_manager
|
||||
.scheduler()
|
||||
self.require_scheduler()?
|
||||
.unpause_schedule(&req.schedule_id)
|
||||
.await
|
||||
.map_err(schedule_not_found_or_internal)?;
|
||||
@@ -256,7 +262,7 @@ impl GooseAcpAgent {
|
||||
) -> Result<UpdateScheduleResponse, agent_client_protocol::Error> {
|
||||
let schedule_id = req.schedule_id;
|
||||
let cron = req.cron;
|
||||
let scheduler = self.agent_manager.scheduler();
|
||||
let scheduler = self.require_scheduler()?;
|
||||
scheduler
|
||||
.update_schedule(&schedule_id, cron)
|
||||
.await
|
||||
@@ -281,12 +287,7 @@ impl GooseAcpAgent {
|
||||
&self,
|
||||
req: RunScheduleNowRequest,
|
||||
) -> Result<RunScheduleNowResponse, agent_client_protocol::Error> {
|
||||
match self
|
||||
.agent_manager
|
||||
.scheduler()
|
||||
.run_now(&req.schedule_id)
|
||||
.await
|
||||
{
|
||||
match self.require_scheduler()?.run_now(&req.schedule_id).await {
|
||||
Ok(session_id) => Ok(RunScheduleNowResponse {
|
||||
status: RunScheduleNowStatus::Completed,
|
||||
session_id: Some(session_id),
|
||||
@@ -299,8 +300,7 @@ impl GooseAcpAgent {
|
||||
&self,
|
||||
req: KillRunningJobRequest,
|
||||
) -> Result<KillRunningJobResponse, agent_client_protocol::Error> {
|
||||
self.agent_manager
|
||||
.scheduler()
|
||||
self.require_scheduler()?
|
||||
.kill_running_job(&req.job_id)
|
||||
.await
|
||||
.map_err(schedule_state_error)?;
|
||||
@@ -315,8 +315,7 @@ impl GooseAcpAgent {
|
||||
req: InspectRunningJobRequest,
|
||||
) -> Result<InspectRunningJobResponse, agent_client_protocol::Error> {
|
||||
let job = self
|
||||
.agent_manager
|
||||
.scheduler()
|
||||
.require_scheduler()?
|
||||
.list_scheduled_jobs()
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -341,3 +340,76 @@ impl GooseAcpAgent {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::acp::server_factory::{AcpServer, AcpServerFactoryConfig};
|
||||
use crate::agents::GoosePlatform;
|
||||
use goose_sdk_types::custom_requests::{ListRecipesRequest, ScheduleRecipeRequest};
|
||||
use serial_test::serial;
|
||||
|
||||
fn assert_scheduler_disabled(error: agent_client_protocol::Error) {
|
||||
assert_eq!(
|
||||
error.code,
|
||||
agent_client_protocol::Error::method_not_found().code
|
||||
);
|
||||
assert_eq!(
|
||||
error.data.as_ref().and_then(serde_json::Value::as_str),
|
||||
Some("Scheduled recipe execution is not enabled")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn disabled_scheduler_rejects_schedule_operations_without_recipe_writes() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let _guard = env_lock::lock_env([
|
||||
("GOOSE_DISABLE_KEYRING", Some("true")),
|
||||
("GOOSE_PATH_ROOT", root.path().to_str()),
|
||||
]);
|
||||
let server = AcpServer::new(AcpServerFactoryConfig {
|
||||
builtins: Vec::new(),
|
||||
data_dir: root.path().join("data"),
|
||||
config_dir: root.path().join("config"),
|
||||
goose_platform: GoosePlatform::GooseCli,
|
||||
additional_source_roots: Vec::new(),
|
||||
enable_scheduler: false,
|
||||
});
|
||||
let agent = server.create_agent().await.unwrap();
|
||||
|
||||
let list_error = agent
|
||||
.on_list_schedules(ListSchedulesRequest {})
|
||||
.await
|
||||
.expect_err("schedule listing must be unsupported");
|
||||
assert_scheduler_disabled(list_error);
|
||||
|
||||
agent
|
||||
.on_list_recipes(ListRecipesRequest {})
|
||||
.await
|
||||
.expect("recipe listing must remain available");
|
||||
|
||||
let create_error = agent
|
||||
.on_create_schedule(CreateScheduleRequest {
|
||||
id: "nightly".to_string(),
|
||||
recipe: Default::default(),
|
||||
cron: "0 0 0 * * *".to_string(),
|
||||
})
|
||||
.await
|
||||
.expect_err("schedule creation must be unsupported");
|
||||
assert_scheduler_disabled(create_error);
|
||||
assert!(!get_default_scheduled_recipes_dir()
|
||||
.unwrap()
|
||||
.join("nightly.yaml")
|
||||
.exists());
|
||||
|
||||
let schedule_recipe_error = agent
|
||||
.on_schedule_recipe(ScheduleRecipeRequest {
|
||||
id: "missing-recipe".to_string(),
|
||||
cron_schedule: Some("0 0 0 * * *".to_string()),
|
||||
})
|
||||
.await
|
||||
.expect_err("recipe scheduling must be unsupported");
|
||||
assert_scheduler_disabled(schedule_recipe_error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ pub struct AcpServerFactoryConfig {
|
||||
pub config_dir: std::path::PathBuf,
|
||||
pub goose_platform: GoosePlatform,
|
||||
pub additional_source_roots: Vec<SourceRoot>,
|
||||
pub enable_scheduler: bool,
|
||||
}
|
||||
|
||||
pub struct AcpServer {
|
||||
@@ -29,7 +30,11 @@ impl AcpServer {
|
||||
}
|
||||
}
|
||||
|
||||
async fn scheduler(&self) -> Result<Arc<dyn SchedulerTrait>> {
|
||||
async fn scheduler(&self) -> Result<Option<Arc<dyn SchedulerTrait>>> {
|
||||
if !self.config.enable_scheduler {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let data_dir = self.config.data_dir.clone();
|
||||
self.scheduler
|
||||
.get_or_try_init(|| async move {
|
||||
@@ -43,6 +48,7 @@ impl AcpServer {
|
||||
})
|
||||
.await
|
||||
.cloned()
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
pub async fn create_agent(&self) -> Result<Arc<GooseAcpAgent>> {
|
||||
@@ -83,3 +89,36 @@ impl AcpServer {
|
||||
Ok(Arc::new(agent))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn server(data_dir: std::path::PathBuf, enable_scheduler: bool) -> AcpServer {
|
||||
AcpServer::new(AcpServerFactoryConfig {
|
||||
builtins: Vec::new(),
|
||||
config_dir: data_dir.clone(),
|
||||
data_dir,
|
||||
goose_platform: GoosePlatform::GooseCli,
|
||||
additional_source_roots: Vec::new(),
|
||||
enable_scheduler,
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn disabled_server_does_not_construct_scheduler() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let server = server(root.path().to_path_buf(), false);
|
||||
|
||||
assert!(server.scheduler().await.unwrap().is_none());
|
||||
assert!(!root.path().join("schedule.json").exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn automatic_server_constructs_scheduler() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let server = server(root.path().to_path_buf(), true);
|
||||
|
||||
assert!(server.scheduler().await.unwrap().is_some());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use crate::agents::mcp_client::GooseMcpHostInfo;
|
||||
use crate::agents::{Agent, AgentConfig, ExtensionLoadResult, GoosePlatform};
|
||||
use crate::config::paths::Paths;
|
||||
use crate::config::permission::PermissionManager;
|
||||
use crate::config::Config;
|
||||
use crate::scheduler::Scheduler;
|
||||
use crate::scheduler_trait::SchedulerTrait;
|
||||
use crate::session::{SessionManager, SessionNameUpdate};
|
||||
use anyhow::Result;
|
||||
@@ -72,15 +70,11 @@ impl AgentManager {
|
||||
.get_goose_max_active_agents()
|
||||
.unwrap_or(DEFAULT_MAX_SESSION);
|
||||
let default_mode = config.get_goose_mode().unwrap_or_default();
|
||||
let schedule_file_path = Paths::data_dir().join("schedule.json");
|
||||
let session_manager = Arc::new(SessionManager::instance());
|
||||
let scheduler = Scheduler::new(schedule_file_path, Arc::clone(&session_manager))
|
||||
.await
|
||||
.map(|scheduler| scheduler as Arc<dyn SchedulerTrait>)?;
|
||||
let agent_config = AgentConfig::new(
|
||||
session_manager,
|
||||
PermissionManager::instance(),
|
||||
Some(scheduler),
|
||||
None,
|
||||
default_mode,
|
||||
config.get_goose_disable_session_naming().unwrap_or(false),
|
||||
GoosePlatform::GooseDesktop,
|
||||
@@ -92,13 +86,8 @@ impl AgentManager {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn scheduler(&self) -> Arc<dyn SchedulerTrait> {
|
||||
Arc::clone(
|
||||
self.agent_config
|
||||
.scheduler_service
|
||||
.as_ref()
|
||||
.expect("AgentManager scheduler is not configured"),
|
||||
)
|
||||
pub fn scheduler(&self) -> Option<Arc<dyn SchedulerTrait>> {
|
||||
self.agent_config.scheduler_service.as_ref().map(Arc::clone)
|
||||
}
|
||||
|
||||
/// Get the shared SessionManager for session-only operations
|
||||
|
||||
@@ -378,7 +378,7 @@ pub async fn spawn_acp_server_in_process(
|
||||
disable_session_naming,
|
||||
goose_platform: GoosePlatform::GooseCli,
|
||||
additional_source_roots: Vec::new(),
|
||||
scheduler: Arc::new(FixtureScheduler::new()),
|
||||
scheduler: Some(Arc::new(FixtureScheduler::new())),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -21,6 +21,7 @@ fn test_acp_router(dir: &tempfile::TempDir) -> Router {
|
||||
config_dir: dir.path().join("config"),
|
||||
goose_platform: GoosePlatform::GooseCli,
|
||||
additional_source_roots: Vec::new(),
|
||||
enable_scheduler: false,
|
||||
}));
|
||||
create_acp_router(server)
|
||||
}
|
||||
@@ -32,6 +33,7 @@ fn test_authenticated_acp_router(dir: &tempfile::TempDir) -> Router {
|
||||
config_dir: dir.path().join("config"),
|
||||
goose_platform: GoosePlatform::GooseCli,
|
||||
additional_source_roots: Vec::new(),
|
||||
enable_scheduler: false,
|
||||
}));
|
||||
create_router(server, SECRET.to_string(), true, Vec::new())
|
||||
}
|
||||
@@ -47,6 +49,7 @@ fn test_router_with_origins(
|
||||
config_dir: dir.path().join("config"),
|
||||
goose_platform: GoosePlatform::GooseCli,
|
||||
additional_source_roots: Vec::new(),
|
||||
enable_scheduler: false,
|
||||
}));
|
||||
create_router(
|
||||
server,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use goose::execution::manager::AgentManager;
|
||||
|
||||
#[tokio::test]
|
||||
async fn global_agent_manager_does_not_construct_scheduler() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let _guard = env_lock::lock_env([
|
||||
("GOOSE_DISABLE_KEYRING", Some("true")),
|
||||
("GOOSE_PATH_ROOT", root.path().to_str()),
|
||||
]);
|
||||
let data_dir = root.path().join("data");
|
||||
std::fs::create_dir_all(&data_dir).unwrap();
|
||||
let schedule_path = data_dir.join("schedule.json");
|
||||
let sentinel = b"do not touch";
|
||||
std::fs::write(&schedule_path, sentinel).unwrap();
|
||||
|
||||
let manager = AgentManager::instance().await.unwrap();
|
||||
|
||||
assert!(manager.scheduler().is_none());
|
||||
assert_eq!(std::fs::read(schedule_path).unwrap(), sentinel);
|
||||
}
|
||||
@@ -478,7 +478,7 @@ These variables configure the `goose serve` ACP server process. They are alterna
|
||||
```bash
|
||||
# Start a goose ACP server reachable on the local network over TLS
|
||||
GOOSE_SERVER__SECRET_KEY='a-long-random-secret' \
|
||||
goose serve --platform desktop --host 0.0.0.0 --port 3000 --tls
|
||||
goose serve --platform desktop --enable-scheduler --host 0.0.0.0 --port 3000 --tls
|
||||
```
|
||||
|
||||
When TLS is enabled, `goose serve` prints a `GOOSED_CERT_FINGERPRINT=...` line on startup. goose Desktop can use this fingerprint to pin the server certificate. See [Running a Remote goose Server](/docs/guides/remote-goose-server) for the full setup.
|
||||
|
||||
@@ -683,6 +683,9 @@ Run goose as an Agent Client Protocol (ACP) agent server over stdio. This enable
|
||||
|
||||
ACP is an emerging protocol specification that standardizes communication between AI agents and client applications, making it easier for clients to integrate with various AI agents.
|
||||
|
||||
**Options:**
|
||||
- **`--enable-scheduler`**: Enable scheduled recipe execution. Disabled by default.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
goose acp
|
||||
@@ -702,6 +705,7 @@ Start goose as an Agent Client Protocol (ACP) server over HTTP and WebSocket.
|
||||
- **`--port <PORT>`**: Port to listen on. Defaults to `3284`
|
||||
- **`--with-builtin <NAME>`**: Enable built-in extensions by name. Can be passed multiple times or as a comma-separated list. Defaults to `developer` when omitted.
|
||||
- **`--dangerously-unauthenticated`**: Run without ACP authentication. Use only for local trusted clients.
|
||||
- **`--enable-scheduler`**: Enable scheduled recipe execution. Disabled by default.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
|
||||
@@ -31,7 +31,7 @@ On the remote machine, launch `goose serve` with the host, port, TLS, and a shar
|
||||
|
||||
```bash
|
||||
GOOSE_SERVER__SECRET_KEY='YOUR_SECRET' \
|
||||
goose serve --platform desktop --host 0.0.0.0 --port 3000 --tls
|
||||
goose serve --platform desktop --enable-scheduler --host 0.0.0.0 --port 3000 --tls
|
||||
```
|
||||
|
||||
If you are using the binary bundled with the macOS app, the command path is `/Applications/Goose.app/Contents/Resources/bin/goose`.
|
||||
@@ -128,6 +128,7 @@ Create a LaunchAgent plist at `~/Library/LaunchAgents/com.goose.serve.external.p
|
||||
<string>serve</string>
|
||||
<string>--platform</string>
|
||||
<string>desktop</string>
|
||||
<string>--enable-scheduler</string>
|
||||
<string>--host</string>
|
||||
<string>0.0.0.0</string>
|
||||
<string>--port</string>
|
||||
|
||||
@@ -95,7 +95,7 @@ Use the existing Windows build process as documented.
|
||||
From the project root, start the ACP backend:
|
||||
|
||||
```bash
|
||||
GOOSE_SERVER__SECRET_KEY=test cargo run -p goose-cli --bin goose -- serve --platform desktop --host 127.0.0.1 --port 3000
|
||||
GOOSE_SERVER__SECRET_KEY=test cargo run -p goose-cli --bin goose -- serve --platform desktop --enable-scheduler --host 127.0.0.1 --port 3000
|
||||
```
|
||||
|
||||
Then start the desktop app from `ui/desktop`:
|
||||
|
||||
@@ -242,7 +242,9 @@ describe('startGooseServe', () => {
|
||||
expect(readinessUrls[0]).toMatch(/^https:\/\/127\.0\.0\.1:\d+\/status$/);
|
||||
expect(result.acpUrl).toMatch(/^wss:\/\/127\.0\.0\.1:\d+\/acp\?token=test-secret$/);
|
||||
expect(result.certFingerprint).toBe('DD:EE:FF');
|
||||
await expect(waitForFileLines(argsPath)).resolves.toContain('--tls');
|
||||
const args = await waitForFileLines(argsPath);
|
||||
expect(args).toContain('--tls');
|
||||
expect(args).toContain('--enable-scheduler');
|
||||
} finally {
|
||||
await result.cleanup();
|
||||
}
|
||||
|
||||
@@ -360,6 +360,7 @@ export const startGooseServe = async ({
|
||||
...(tls ? ['--tls'] : []),
|
||||
'--platform',
|
||||
'desktop',
|
||||
'--enable-scheduler',
|
||||
'--host',
|
||||
'127.0.0.1',
|
||||
'--port',
|
||||
|
||||
Reference in New Issue
Block a user