feat: auto-group agent tabs by default in local Chromium

This commit is contained in:
leeguooooo
2026-03-03 11:37:53 +09:00
parent 0a257ad2c1
commit d04cf59238
14 changed files with 366 additions and 31 deletions
+2
View File
@@ -2054,7 +2054,9 @@ mod tests {
annotate: false,
color_scheme: None,
download_path: None,
tab_group: None,
risk_mode: None,
cli_tab_group: false,
}
}
+7
View File
@@ -227,6 +227,7 @@ pub fn ensure_daemon(
session_name: Option<&str>,
debug: bool,
download_path: Option<&str>,
tab_group: Option<&str>,
) -> Result<DaemonResult, String> {
// Check if daemon is running AND responsive
if is_daemon_running(session) && daemon_ready(session) {
@@ -374,6 +375,9 @@ pub fn ensure_daemon(
if let Some(dp) = download_path {
cmd.env("AGENT_BROWSER_DOWNLOAD_PATH", dp);
}
if let Some(tg) = tab_group {
cmd.env("AGENT_BROWSER_TAB_GROUP", tg);
}
// Create new process group and session to fully detach
unsafe {
@@ -461,6 +465,9 @@ pub fn ensure_daemon(
if let Some(dp) = download_path {
cmd.env("AGENT_BROWSER_DOWNLOAD_PATH", dp);
}
if let Some(tg) = tab_group {
cmd.env("AGENT_BROWSER_TAB_GROUP", tg);
}
// CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
+35
View File
@@ -34,6 +34,7 @@ pub struct Config {
pub annotate: Option<bool>,
pub color_scheme: Option<String>,
pub download_path: Option<String>,
pub tab_group: Option<String>,
pub risk_mode: Option<String>,
}
@@ -69,6 +70,7 @@ impl Config {
annotate: other.annotate.or(self.annotate),
color_scheme: other.color_scheme.or(self.color_scheme),
download_path: other.download_path.or(self.download_path),
tab_group: other.tab_group.or(self.tab_group),
risk_mode: other.risk_mode.or(self.risk_mode),
}
}
@@ -136,6 +138,7 @@ fn extract_config_path(args: &[String]) -> Option<Option<String>> {
"--color-scheme",
"--channel",
"--download-path",
"--tab-group",
"--risk-mode",
];
let mut i = 0;
@@ -207,6 +210,7 @@ pub struct Flags {
pub annotate: bool,
pub color_scheme: Option<String>,
pub download_path: Option<String>,
pub tab_group: Option<String>,
/// How verification/captcha detections are handled on navigation:
/// `off` (disable), `warn` (retry and warn), `block` (fail fast).
pub risk_mode: Option<String>,
@@ -223,6 +227,7 @@ pub struct Flags {
pub cli_allow_file_access: bool,
pub cli_annotate: bool,
pub cli_download_path: bool,
pub cli_tab_group: bool,
}
pub fn parse_flags(args: &[String]) -> Flags {
@@ -291,6 +296,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
.or(config.color_scheme),
download_path: env::var("AGENT_BROWSER_DOWNLOAD_PATH").ok()
.or(config.download_path),
tab_group: env::var("AGENT_BROWSER_TAB_GROUP").ok().or(config.tab_group),
risk_mode: env::var("AGENT_BROWSER_RISK_MODE")
.ok()
.or(config.risk_mode)
@@ -305,6 +311,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
cli_allow_file_access: false,
cli_annotate: false,
cli_download_path: false,
cli_tab_group: false,
};
let mut i = 0;
@@ -466,6 +473,13 @@ pub fn parse_flags(args: &[String]) -> Flags {
i += 1;
}
}
"--tab-group" => {
if let Some(s) = args.get(i + 1) {
flags.tab_group = Some(s.clone());
flags.cli_tab_group = true;
i += 1;
}
}
"--risk-mode" => {
if let Some(s) = args.get(i + 1) {
flags.risk_mode = Some(s.to_ascii_lowercase());
@@ -516,6 +530,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
"--session-name",
"--color-scheme",
"--download-path",
"--tab-group",
"--risk-mode",
"--config",
];
@@ -714,6 +729,24 @@ mod tests {
assert!(!flags.cli_download_path);
}
#[test]
fn test_parse_tab_group_flag() {
let input = vec![
"--tab-group".to_string(),
"Agent Browser Stealth".to_string(),
"snapshot".to_string(),
];
let flags = parse_flags(&input);
assert_eq!(flags.tab_group.as_deref(), Some("Agent Browser Stealth"));
assert!(flags.cli_tab_group);
}
#[test]
fn test_clean_args_removes_tab_group() {
let cleaned = clean_args(&args("--tab-group AgentGroup open example.com"));
assert_eq!(cleaned, vec!["open", "example.com"]);
}
#[test]
fn test_parse_risk_mode_flag() {
let flags = parse_flags(&args("--risk-mode block open example.com"));
@@ -762,6 +795,7 @@ mod tests {
"cdp": "9222",
"autoConnect": true,
"headers": "{\"Auth\":\"token\"}",
"tabGroup": "Agent Browser Stealth",
"riskMode": "block"
}"#;
let config: Config = serde_json::from_str(json).unwrap();
@@ -788,6 +822,7 @@ mod tests {
assert_eq!(config.cdp.as_deref(), Some("9222"));
assert_eq!(config.auto_connect, Some(true));
assert_eq!(config.headers.as_deref(), Some("{\"Auth\":\"token\"}"));
assert_eq!(config.tab_group.as_deref(), Some("Agent Browser Stealth"));
assert_eq!(config.risk_mode.as_deref(), Some("block"));
}
+18 -2
View File
@@ -287,6 +287,7 @@ fn main() {
flags.session_name.as_deref(),
flags.debug,
flags.download_path.as_deref(),
flags.tab_group.as_deref(),
) {
Ok(result) => result,
Err(e) => {
@@ -338,6 +339,7 @@ fn main() {
flags.ignore_https_errors.then_some("--ignore-https-errors"),
flags.cli_allow_file_access.then_some("--allow-file-access"),
flags.cli_download_path.then_some("--download-path"),
flags.cli_tab_group.then_some("--tab-group"),
]
.into_iter()
.flatten()
@@ -424,6 +426,9 @@ fn main() {
if let Some(ref dp) = flags.download_path {
launch_cmd["downloadPath"] = json!(dp);
}
if let Some(ref tg) = flags.tab_group {
launch_cmd["tabGroup"] = json!(tg);
}
let err = match send_command(launch_cmd, &flags.session) {
Ok(resp) if resp.success => None,
@@ -516,6 +521,9 @@ fn main() {
if let Some(ref dp) = flags.download_path {
launch_cmd["downloadPath"] = json!(dp);
}
if let Some(ref tg) = flags.tab_group {
launch_cmd["tabGroup"] = json!(tg);
}
let err = match send_command(launch_cmd, &flags.session) {
Ok(resp) if resp.success => None,
@@ -549,6 +557,9 @@ fn main() {
if let Some(ref cs) = flags.color_scheme {
launch_cmd["colorScheme"] = json!(cs);
}
if let Some(ref tg) = flags.tab_group {
launch_cmd["tabGroup"] = json!(tg);
}
match send_command(launch_cmd, &flags.session) {
Ok(resp) => {
@@ -589,7 +600,8 @@ fn main() {
&& flags.user_agent.is_none()
&& !flags.ignore_https_errors
&& !flags.allow_file_access
&& flags.extensions.is_empty();
&& flags.extensions.is_empty()
&& flags.tab_group.is_none();
if can_try_default_cdp {
let mut launch_cmd = json!({
@@ -643,7 +655,8 @@ fn main() {
|| flags.allow_file_access
|| flags.debug
|| flags.color_scheme.is_some()
|| flags.download_path.is_some())
|| flags.download_path.is_some()
|| flags.tab_group.is_some())
&& flags.cdp.is_none()
&& flags.provider.is_none()
&& !attached_to_existing_browser
@@ -708,6 +721,9 @@ fn main() {
if let Some(ref dp) = flags.download_path {
launch_cmd["downloadPath"] = json!(dp);
}
if let Some(ref tg) = flags.tab_group {
launch_cmd["tabGroup"] = json!(tg);
}
match send_command(launch_cmd, &flags.session) {
Ok(resp) => {
+2
View File
@@ -2409,6 +2409,7 @@ Options:
Project default: try localhost:9333 first, then auto-discovery (no managed local-launch fallback)
--color-scheme <scheme> Color scheme: dark, light, no-preference (or AGENT_BROWSER_COLOR_SCHEME)
--download-path <path> Default download directory (or AGENT_BROWSER_DOWNLOAD_PATH)
--tab-group <name> Override default tab group title for agent tabs in Chromium local launch (or AGENT_BROWSER_TAB_GROUP)
--risk-mode <mode> Verify/captcha handling: off, warn, block (or AGENT_BROWSER_RISK_MODE)
--session-name <name> Auto-save/restore session state (cookies, localStorage)
--content-boundaries Wrap page output in boundary markers (or AGENT_BROWSER_CONTENT_BOUNDARIES)
@@ -2467,6 +2468,7 @@ Environment:
AGENT_BROWSER_TIMEZONE Override auto-detected timezone (e.g., Asia/Taipei)
AGENT_BROWSER_COLOR_SCHEME Color scheme preference (dark, light, no-preference)
AGENT_BROWSER_DOWNLOAD_PATH Default download directory for browser downloads
AGENT_BROWSER_TAB_GROUP Override default tab group title (Chromium local launch only)
AGENT_BROWSER_RISK_MODE Verify/captcha handling mode (off, warn, block)
AGENT_BROWSER_DEFAULT_TIMEOUT Default Playwright timeout in ms (default: 25000)
AGENT_BROWSER_SESSION_NAME Auto-save/load state persistence name