1- use std:: path:: PathBuf ;
1+ use std:: path:: { Path , PathBuf } ;
22use std:: time:: { Duration , Instant } ;
33
44use tokio:: process:: { Child , Command } ;
@@ -7,6 +7,12 @@ use tokio::sync::OnceCell;
77const GOOSE_SERVE_CONNECT_TIMEOUT : Duration = Duration :: from_secs ( 30 ) ;
88const GOOSE_SERVE_CONNECT_RETRY_DELAY : Duration = Duration :: from_millis ( 100 ) ;
99const LOCALHOST : & str = "127.0.0.1" ;
10+ const COMMON_GOOSE_PATHS : & [ & str ] = & [
11+ "/opt/homebrew/bin" ,
12+ "/usr/local/bin" ,
13+ "/usr/bin" ,
14+ "/home/linuxbrew/.linuxbrew/bin" ,
15+ ] ;
1016// ---------------------------------------------------------------------------
1117// GooseServeProcess — singleton that owns the long-lived `goose serve` child
1218// ---------------------------------------------------------------------------
@@ -161,21 +167,18 @@ pub(crate) fn resolve_goose_binary() -> Result<PathBuf, String> {
161167 log:: info!( "Using GOOSE_BIN override: {override_path}" ) ;
162168 path
163169 } else {
164- let agent = acp_client :: find_acp_agent_by_id ( "goose" )
170+ let path = find_goose_binary ( )
165171 . ok_or_else ( || "Unknown or unavailable agent provider: goose" . to_string ( ) ) ?;
166172
167- if !goose_binary_supports_serve ( & agent . binary_path ) ? {
173+ if !goose_binary_supports_serve ( & path ) ? {
168174 return Err ( format ! (
169175 "Resolved goose binary does not support `serve`: {}. Set GOOSE_BIN to a newer goose binary." ,
170- agent . binary_path . display( )
176+ path . display( )
171177 ) ) ;
172178 }
173179
174- log:: info!(
175- "Resolved goose binary via login-shell discovery: {}" ,
176- agent. binary_path. display( )
177- ) ;
178- agent. binary_path
180+ log:: info!( "Resolved goose binary via local discovery: {}" , path. display( ) ) ;
181+ path
179182 } ;
180183
181184 // Log the binary version for debugging.
@@ -203,6 +206,62 @@ pub(crate) fn resolve_goose_binary() -> Result<PathBuf, String> {
203206 Ok ( binary_path)
204207}
205208
209+ fn find_goose_binary ( ) -> Option < PathBuf > {
210+ find_goose_via_login_shell ( ) . or_else ( find_goose_in_common_paths)
211+ }
212+
213+ fn find_goose_via_login_shell ( ) -> Option < PathBuf > {
214+ #[ cfg( target_os = "windows" ) ]
215+ {
216+ None
217+ }
218+
219+ #[ cfg( not( target_os = "windows" ) ) ]
220+ {
221+ for shell in [ "/bin/zsh" , "/bin/bash" ] {
222+ let Ok ( output) = std:: process:: Command :: new ( shell)
223+ . args ( [ "-l" , "-c" , "which goose" ] )
224+ . output ( )
225+ else {
226+ continue ;
227+ } ;
228+
229+ if !output. status . success ( ) {
230+ continue ;
231+ }
232+
233+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
234+ if let Some ( path_str) = stdout. lines ( ) . rfind ( |line| !line. trim ( ) . is_empty ( ) ) {
235+ let path = PathBuf :: from ( path_str. trim ( ) ) ;
236+ if path. is_file ( ) {
237+ return Some ( path) ;
238+ }
239+ }
240+ }
241+
242+ None
243+ }
244+ }
245+
246+ fn find_goose_in_common_paths ( ) -> Option < PathBuf > {
247+ COMMON_GOOSE_PATHS
248+ . iter ( )
249+ . map ( |dir| Path :: new ( dir) . join ( goose_binary_name ( ) ) )
250+ . find ( |path| path. is_file ( ) )
251+ }
252+
253+ fn goose_binary_name ( ) -> & ' static str {
254+ #[ cfg( target_os = "windows" ) ]
255+ {
256+ "goose.exe"
257+ }
258+
259+ #[ cfg( not( target_os = "windows" ) ) ]
260+ {
261+ "goose"
262+ }
263+ }
264+
206265fn goose_binary_supports_serve ( binary_path : & PathBuf ) -> Result < bool , String > {
207266 let output = std:: process:: Command :: new ( binary_path)
208267 . env ( "GOOSE_PATH_ROOT" , goose_probe_root ( ) )
0 commit comments