From 0b234bdcfee5343689665c4c3961737b7144359f Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Wed, 29 Jul 2026 19:54:49 +1000 Subject: [PATCH] disable scheduler by default for acp (#10781) --- CONTRIBUTING.md | 2 +- Justfile | 2 +- crates/goose-cli/src/cli.rs | 20 ++- crates/goose/src/acp/server.rs | 7 +- crates/goose/src/acp/server/recipe/mod.rs | 10 +- crates/goose/src/acp/server/schedule.rs | 118 ++++++++++++++---- crates/goose/src/acp/server_factory.rs | 41 +++++- crates/goose/src/execution/manager.rs | 17 +-- crates/goose/tests/acp_fixtures/mod.rs | 2 +- crates/goose/tests/acp_transport_auth_test.rs | 3 + .../tests/agent_manager_scheduler_disabled.rs | 20 +++ .../docs/guides/environment-variables.md | 2 +- .../docs/guides/goose-cli-commands.md | 4 + .../docs/guides/remote-goose-server.md | 3 +- ui/desktop/README.md | 2 +- ui/desktop/src/gooseServe.test.ts | 4 +- ui/desktop/src/gooseServe.ts | 1 + 17 files changed, 204 insertions(+), 54 deletions(-) create mode 100644 crates/goose/tests/agent_manager_scheduler_disabled.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee36c03dc..1d1d8763f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/Justfile b/Justfile index 63772d3d7..f140f5bb1 100644 --- a/Justfile +++ b/Justfile @@ -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 diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index 427dd8035..f999940ea 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -838,6 +838,9 @@ enum Command { value_delimiter = ',' )] builtins: Vec, + + #[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, + + #[arg(long, help = "Enable scheduled recipe execution")] + enable_scheduler: bool, }, /// Start or resume interactive chat sessions @@ -1388,6 +1394,7 @@ struct ServeCommandArgs { builtins: Vec, dangerously_unauthenticated: bool, allowed_origins: Vec, + 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 } diff --git a/crates/goose/src/acp/server.rs b/crates/goose/src/acp/server.rs index 4e959e2a7..82a249c0e 100644 --- a/crates/goose/src/acp/server.rs +++ b/crates/goose/src/acp/server.rs @@ -185,7 +185,7 @@ pub struct GooseAcpAgentOptions { pub disable_session_naming: bool, pub goose_platform: GoosePlatform, pub additional_source_roots: Vec, - pub scheduler: Arc, + pub scheduler: Option>, } 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 for GooseAgentConnection { } } -pub async fn run(builtins: Vec) -> Result<()> { +pub async fn run(builtins: Vec, 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) -> Result<()> { config_dir: Paths::config_dir(), goose_platform: GoosePlatform::GooseCli, additional_source_roots: Vec::new(), + enable_scheduler, }, ); let agent = server.create_agent().await?; diff --git a/crates/goose/src/acp/server/recipe/mod.rs b/crates/goose/src/acp/server/recipe/mod.rs index 32fa32ec7..78a99a916 100644 --- a/crates/goose/src/acp/server/recipe/mod.rs +++ b/crates/goose/src/acp/server/recipe/mod.rs @@ -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 { + 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 { diff --git a/crates/goose/src/acp/server/schedule.rs b/crates/goose/src/acp/server/schedule.rs index 62b43c8ca..8e22fc850 100644 --- a/crates/goose/src/acp/server/schedule.rs +++ b/crates/goose/src/acp/server/schedule.rs @@ -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, 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 { let jobs = self - .agent_manager - .scheduler() + .require_scheduler()? .list_scheduled_jobs() .await .into_iter() @@ -142,8 +152,7 @@ impl GooseAcpAgent { req: ListScheduleSessionsRequest, ) -> Result { 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 { + 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 { - 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 { - 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 { - 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 { 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 { - 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 { - 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 { 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); + } +} diff --git a/crates/goose/src/acp/server_factory.rs b/crates/goose/src/acp/server_factory.rs index 2f4fa3fbd..4f33ddafc 100644 --- a/crates/goose/src/acp/server_factory.rs +++ b/crates/goose/src/acp/server_factory.rs @@ -14,6 +14,7 @@ pub struct AcpServerFactoryConfig { pub config_dir: std::path::PathBuf, pub goose_platform: GoosePlatform, pub additional_source_roots: Vec, + pub enable_scheduler: bool, } pub struct AcpServer { @@ -29,7 +30,11 @@ impl AcpServer { } } - async fn scheduler(&self) -> Result> { + async fn scheduler(&self) -> Result>> { + 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> { @@ -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()); + } +} diff --git a/crates/goose/src/execution/manager.rs b/crates/goose/src/execution/manager.rs index c21752e7f..2b2d45a30 100644 --- a/crates/goose/src/execution/manager.rs +++ b/crates/goose/src/execution/manager.rs @@ -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)?; 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 { - Arc::clone( - self.agent_config - .scheduler_service - .as_ref() - .expect("AgentManager scheduler is not configured"), - ) + pub fn scheduler(&self) -> Option> { + self.agent_config.scheduler_service.as_ref().map(Arc::clone) } /// Get the shared SessionManager for session-only operations diff --git a/crates/goose/tests/acp_fixtures/mod.rs b/crates/goose/tests/acp_fixtures/mod.rs index 88ba449de..20f41638c 100644 --- a/crates/goose/tests/acp_fixtures/mod.rs +++ b/crates/goose/tests/acp_fixtures/mod.rs @@ -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(); diff --git a/crates/goose/tests/acp_transport_auth_test.rs b/crates/goose/tests/acp_transport_auth_test.rs index 9a3f4f42c..0ad1ecbb5 100644 --- a/crates/goose/tests/acp_transport_auth_test.rs +++ b/crates/goose/tests/acp_transport_auth_test.rs @@ -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, diff --git a/crates/goose/tests/agent_manager_scheduler_disabled.rs b/crates/goose/tests/agent_manager_scheduler_disabled.rs new file mode 100644 index 000000000..250324614 --- /dev/null +++ b/crates/goose/tests/agent_manager_scheduler_disabled.rs @@ -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); +} diff --git a/documentation/docs/guides/environment-variables.md b/documentation/docs/guides/environment-variables.md index 3dbd125f4..53ab56138 100644 --- a/documentation/docs/guides/environment-variables.md +++ b/documentation/docs/guides/environment-variables.md @@ -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. diff --git a/documentation/docs/guides/goose-cli-commands.md b/documentation/docs/guides/goose-cli-commands.md index d02b821c9..e74843179 100644 --- a/documentation/docs/guides/goose-cli-commands.md +++ b/documentation/docs/guides/goose-cli-commands.md @@ -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 to listen on. Defaults to `3284` - **`--with-builtin `**: 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 diff --git a/documentation/docs/guides/remote-goose-server.md b/documentation/docs/guides/remote-goose-server.md index 4d197bb22..3ca87f805 100644 --- a/documentation/docs/guides/remote-goose-server.md +++ b/documentation/docs/guides/remote-goose-server.md @@ -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 serve --platform desktop + --enable-scheduler --host 0.0.0.0 --port diff --git a/ui/desktop/README.md b/ui/desktop/README.md index 40f8960e4..5028d91b2 100644 --- a/ui/desktop/README.md +++ b/ui/desktop/README.md @@ -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`: diff --git a/ui/desktop/src/gooseServe.test.ts b/ui/desktop/src/gooseServe.test.ts index 2ee41f2ea..42aea403d 100644 --- a/ui/desktop/src/gooseServe.test.ts +++ b/ui/desktop/src/gooseServe.test.ts @@ -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(); } diff --git a/ui/desktop/src/gooseServe.ts b/ui/desktop/src/gooseServe.ts index f22274681..4deb00d8f 100644 --- a/ui/desktop/src/gooseServe.ts +++ b/ui/desktop/src/gooseServe.ts @@ -360,6 +360,7 @@ export const startGooseServe = async ({ ...(tls ? ['--tls'] : []), '--platform', 'desktop', + '--enable-scheduler', '--host', '127.0.0.1', '--port',