Consistent Tab IDs & Global Tag Targeting (#892)
Introduces stable per-tab IDs and a global `--tab <id>` flag for scoping individual commands to a specific tab.
Breaking change: response payloads for `tab_list`, `tab_new`, `tab_switch`, `tab_close`, and `window_new` now use `tabId` instead of `index`. `tab_close` returns `{tabId, closed: true}` instead of `{closed, activeIndex}`. `agent-browser tab <unknown>` now errors instead of silently listing tabs.
Follow-up PR to land immediately after this fixes a compile error on the provider direct-page path, clears per-tab daemon state around scoped switches, and implements active-tab restoration so `--tab N` is non-intrusive as intended.
This commit is contained in:
+64
-20
@@ -1010,28 +1010,35 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
|
||||
}
|
||||
|
||||
// === Tabs ===
|
||||
"tab" => match rest.first().copied() {
|
||||
Some("new") => {
|
||||
let mut cmd = json!({ "id": id, "action": "tab_new" });
|
||||
if let Some(url) = rest.get(1) {
|
||||
cmd["url"] = json!(url);
|
||||
"tab" => {
|
||||
const VALID: &[&str] = &["list", "new", "close", "<id>"];
|
||||
match rest.first().copied() {
|
||||
Some("new") => {
|
||||
let mut cmd = json!({ "id": id, "action": "tab_new" });
|
||||
if let Some(url) = rest.get(1) {
|
||||
cmd["url"] = json!(url);
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
Some("list") => Ok(json!({ "id": id, "action": "tab_list" })),
|
||||
Some("close") => {
|
||||
let mut cmd = json!({ "id": id, "action": "tab_close" });
|
||||
if let Some(index) = rest.get(1).and_then(|s| s.parse::<i32>().ok()) {
|
||||
cmd["index"] = json!(index);
|
||||
Some("list") => Ok(json!({ "id": id, "action": "tab_list" })),
|
||||
Some("close") => {
|
||||
let mut cmd = json!({ "id": id, "action": "tab_close" });
|
||||
if let Some(tab_id) = rest.get(1).and_then(|s| s.parse::<i32>().ok()) {
|
||||
cmd["tabId"] = json!(tab_id);
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
Ok(cmd)
|
||||
Some(n) if n.parse::<i32>().is_ok() => {
|
||||
let tab_id = n.parse::<i32>().expect("already checked parse succeeds");
|
||||
Ok(json!({ "id": id, "action": "tab_switch", "tabId": tab_id }))
|
||||
}
|
||||
Some(sub) => Err(ParseError::UnknownSubcommand {
|
||||
subcommand: sub.to_string(),
|
||||
valid_options: VALID,
|
||||
}),
|
||||
None => Ok(json!({ "id": id, "action": "tab_list" })),
|
||||
}
|
||||
Some(n) if n.parse::<i32>().is_ok() => {
|
||||
let index = n.parse::<i32>().expect("already checked parse succeeds");
|
||||
Ok(json!({ "id": id, "action": "tab_switch", "index": index }))
|
||||
}
|
||||
_ => Ok(json!({ "id": id, "action": "tab_list" })),
|
||||
},
|
||||
}
|
||||
|
||||
// === Window ===
|
||||
"window" => {
|
||||
@@ -2335,6 +2342,7 @@ mod tests {
|
||||
fn default_flags() -> Flags {
|
||||
Flags {
|
||||
session: "test".to_string(),
|
||||
tab: None,
|
||||
json: false,
|
||||
headed: false,
|
||||
debug: false,
|
||||
@@ -2847,7 +2855,7 @@ mod tests {
|
||||
fn test_tab_switch() {
|
||||
let cmd = parse_command(&args("tab 2"), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "tab_switch");
|
||||
assert_eq!(cmd["index"], 2);
|
||||
assert_eq!(cmd["tabId"], 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2856,6 +2864,42 @@ mod tests {
|
||||
assert_eq!(cmd["action"], "tab_close");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tab_close_with_id() {
|
||||
let cmd = parse_command(&args("tab close 2"), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "tab_close");
|
||||
assert_eq!(cmd["tabId"], 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tab_switch_sends_tab_id() {
|
||||
let cmd = parse_command(&args("tab 2"), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["tabId"], 2);
|
||||
assert!(cmd.get("index").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tab_close_sends_tab_id() {
|
||||
let cmd = parse_command(&args("tab close 3"), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["tabId"], 3);
|
||||
assert!(cmd.get("index").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tab_no_args_defaults_to_list() {
|
||||
let cmd = parse_command(&args("tab"), &default_flags()).unwrap();
|
||||
assert_eq!(cmd["action"], "tab_list");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tab_unknown_subcommand_errors() {
|
||||
let result = parse_command(&args("tab select 3"), &default_flags());
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"tab select should error, not silently fall through to tab_list"
|
||||
);
|
||||
}
|
||||
|
||||
// === Network ===
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user