Add persistent ChromeDriver support for faster WebDriver startup
When webdriver_start is called, now checks if chromedriver is already running on the configured port and reuses it instead of spawning a new process. This significantly reduces startup time for subsequent sessions. New config option: [webdriver] persistent_chrome = true # Keep chromedriver running between sessions When enabled, webdriver_quit closes the browser session but leaves chromedriver running for reuse by the next session.
This commit is contained in:
@@ -80,3 +80,4 @@ model = "claude-sonnet-4-5"
|
|||||||
# browser = "chrome-headless" # Default. Alternative: "safari"
|
# browser = "chrome-headless" # Default. Alternative: "safari"
|
||||||
# chrome_binary = "/path/to/chrome" # Optional: custom Chrome path
|
# chrome_binary = "/path/to/chrome" # Optional: custom Chrome path
|
||||||
# chromedriver_binary = "/path/to/driver" # Optional: custom ChromeDriver path
|
# chromedriver_binary = "/path/to/driver" # Optional: custom ChromeDriver path
|
||||||
|
# persistent_chrome = true # Keep chromedriver running between sessions for faster startup
|
||||||
|
|||||||
@@ -179,6 +179,10 @@ pub struct WebDriverConfig {
|
|||||||
pub chromedriver_binary: Option<String>,
|
pub chromedriver_binary: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub browser: WebDriverBrowser,
|
pub browser: WebDriverBrowser,
|
||||||
|
#[serde(default)]
|
||||||
|
/// Keep chromedriver running after session ends for faster subsequent startups
|
||||||
|
/// When true, chromedriver process is not killed on webdriver_quit
|
||||||
|
pub persistent_chrome: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AgentConfig {
|
impl Default for AgentConfig {
|
||||||
|
|||||||
@@ -11,6 +11,25 @@ use crate::ToolCall;
|
|||||||
|
|
||||||
use super::executor::ToolContext;
|
use super::executor::ToolContext;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Port checking helpers
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Check if chromedriver is already running on the given port.
|
||||||
|
async fn check_chromedriver_running(port: u16) -> bool {
|
||||||
|
// Try to connect to the chromedriver status endpoint
|
||||||
|
let url = format!("http://localhost:{}/status", port);
|
||||||
|
match reqwest::Client::new()
|
||||||
|
.get(&url)
|
||||||
|
.timeout(std::time::Duration::from_millis(500))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(response) => response.status().is_success(),
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// Session helpers
|
// Session helpers
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -119,6 +138,32 @@ async fn start_safari_driver<W: UiWriter>(ctx: &ToolContext<'_, W>) -> Result<St
|
|||||||
async fn start_chrome_driver<W: UiWriter>(ctx: &ToolContext<'_, W>) -> Result<String> {
|
async fn start_chrome_driver<W: UiWriter>(ctx: &ToolContext<'_, W>) -> Result<String> {
|
||||||
let port = ctx.config.webdriver.chrome_port;
|
let port = ctx.config.webdriver.chrome_port;
|
||||||
|
|
||||||
|
// Check if chromedriver is already running on this port
|
||||||
|
let already_running = check_chromedriver_running(port).await;
|
||||||
|
|
||||||
|
if already_running {
|
||||||
|
// Try to connect to existing chromedriver
|
||||||
|
let driver_result = match &ctx.config.webdriver.chrome_binary {
|
||||||
|
Some(binary) => {
|
||||||
|
g3_computer_control::ChromeDriver::with_port_headless_and_binary(port, Some(binary))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
None => g3_computer_control::ChromeDriver::with_port_headless(port).await,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(driver) = driver_result {
|
||||||
|
let session =
|
||||||
|
std::sync::Arc::new(tokio::sync::Mutex::new(WebDriverSession::Chrome(driver)));
|
||||||
|
*ctx.webdriver_session.write().await = Some(session);
|
||||||
|
// Don't store process - we didn't start it
|
||||||
|
return Ok(
|
||||||
|
"✅ WebDriver session started (reusing existing chromedriver)."
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// If connection failed, fall through to start a new one
|
||||||
|
}
|
||||||
|
|
||||||
// Use configured chromedriver binary or fall back to 'chromedriver' in PATH
|
// Use configured chromedriver binary or fall back to 'chromedriver' in PATH
|
||||||
let chromedriver_cmd = ctx
|
let chromedriver_cmd = ctx
|
||||||
.config
|
.config
|
||||||
@@ -599,22 +644,33 @@ pub async fn execute_webdriver_quit<W: UiWriter>(
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("WebDriver session closed successfully");
|
debug!("WebDriver session closed successfully");
|
||||||
|
|
||||||
// Kill the driver process
|
// Kill the driver process (unless persistent_chrome is enabled for Chrome)
|
||||||
if let Some(mut process) = ctx.webdriver_process.write().await.take() {
|
use g3_config::WebDriverBrowser;
|
||||||
|
let is_chrome = matches!(&ctx.config.webdriver.browser, WebDriverBrowser::ChromeHeadless);
|
||||||
|
let keep_running = is_chrome && ctx.config.webdriver.persistent_chrome;
|
||||||
|
|
||||||
|
if keep_running {
|
||||||
|
debug!("Keeping chromedriver running (persistent_chrome enabled)");
|
||||||
|
// Still take the process handle but don't kill it
|
||||||
|
let _ = ctx.webdriver_process.write().await.take();
|
||||||
|
} else if let Some(mut process) = ctx.webdriver_process.write().await.take() {
|
||||||
if let Err(e) = process.kill().await {
|
if let Err(e) = process.kill().await {
|
||||||
warn!("Failed to kill driver process: {}", e);
|
warn!("Failed to kill driver process: {}", e);
|
||||||
} else {
|
} else {
|
||||||
debug!("Driver process terminated");
|
debug!("Driver process terminated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return appropriate message based on browser type
|
// Return appropriate message based on browser type
|
||||||
use g3_config::WebDriverBrowser;
|
|
||||||
let driver_name = match &ctx.config.webdriver.browser {
|
let driver_name = match &ctx.config.webdriver.browser {
|
||||||
WebDriverBrowser::Safari => "safaridriver",
|
WebDriverBrowser::Safari => "safaridriver",
|
||||||
WebDriverBrowser::ChromeHeadless => "chromedriver",
|
WebDriverBrowser::ChromeHeadless => "chromedriver",
|
||||||
};
|
};
|
||||||
Ok(format!("✅ WebDriver session closed and {} stopped", driver_name))
|
if keep_running {
|
||||||
|
Ok(format!("✅ WebDriver session closed ({} still running for reuse)", driver_name))
|
||||||
|
} else {
|
||||||
|
Ok(format!("✅ WebDriver session closed and {} stopped", driver_name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => Ok(format!("❌ Failed to quit WebDriver: {}", e)),
|
Err(e) => Ok(format!("❌ Failed to quit WebDriver: {}", e)),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user