From fc73ee6c903400f65a6c3583211289f36bd70f5b Mon Sep 17 00:00:00 2001 From: leeguooooo Date: Thu, 11 Jun 2026 22:55:32 +0900 Subject: [PATCH] feat(pick): atomic combobox select for react-select / ARIA / native (issue #2 P1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The biggest manual-cost point in the dogfood reports: `select @ref` is a silent no-op on non-native dropdowns, and click+wait+Enter on react-select/ARIA/portal menus took ~20 turns of hand-written eval to get right. New `pick --option ""` does it atomically in one in-page async routine: native → "Gamma"; portal combobox → "欧洲" (non-ASCII); missing option → explicit error. Documented in the skill. --- cli/src/commands.rs | 27 +++++++++++ cli/src/native/actions.rs | 98 +++++++++++++++++++++++++++++++++++++++ skill-data/core/SKILL.md | 10 +++- 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 73c9332..c725a75 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -418,6 +418,33 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result { + // pick --option "" — atomic combobox select: + // open the control, wait for options (incl. portal menus), match by + // text, fire the right event sequence, verify. Covers native `, +/// ARIA combobox/listbox, and react-select, which a bare `click`+`press Enter` +/// can't do reliably. Runs as one in-page async routine so the open→render→pick +/// dance happens without round-trips that let the menu collapse between commands. +async fn handle_pick(cmd: &Value, state: &mut DaemonState) -> Result { + let mgr = state.browser.as_ref().ok_or("Browser not launched")?; + let session_id = mgr.active_session_id()?.to_string(); + let selector = cmd + .get("selector") + .and_then(|v| v.as_str()) + .ok_or("Missing 'selector' parameter")?; + let option = cmd + .get("option") + .and_then(|v| v.as_str()) + .ok_or("Missing 'option' parameter")?; + + let (object_id, effective_session_id) = super::element::resolve_element_object_id( + &mgr.client, + &session_id, + &state.ref_map, + selector, + &state.iframe_sessions, + ) + .await?; + + let func = format!( + r#"async function() {{ + const want = {opt}; + const norm = s => (s || '').replace(/\s+/g, ' ').trim(); + const matches = el => norm(el.textContent).toLowerCase().includes(want.toLowerCase()); + const el = this; + const fire = (n, t) => n.dispatchEvent(new MouseEvent(t, {{ bubbles: true, cancelable: true, view: window }})); + + // Native only agent-browser select @e4 "a" "b" # select multiple +agent-browser pick @e4 --option "Europe" # ANY combobox (react-select / ARIA / + # native): opens it, waits for the menu + # (incl. portal-rendered), matches by + # visible text, fires the right events, + # and ERRORS if the option never shows + # (no silent no-op). Use this for custom + # dropdowns where `select` returns ✓ but + # changes nothing. agent-browser upload @e5 file1.pdf # upload file(s) agent-browser scroll down 500 # scroll page (up/down/left/right) agent-browser scrollintoview @e1 # scroll element into view