You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stop the browser agent acting on the wrong element, and say why it refused
Four findings from the module audit, the first of which could silently do the
wrong thing rather than merely fail.
- A ref whose node is gone is re-adopted by structural resemblance, matching on
ORIGIN only so a pushState between snapshot and act does not kill every ref.
That leniency also let a ref to a row control in one view rebind to the
identical control in a view the app had since navigated to — acting on the
wrong message, signalled by nothing louder than recovered: true. Adoption now
requires the same path; a view swap reports the ref stale, and the caller
re-snapshots. Revalidating a still-connected node stays lenient, because that
is literally the node the model chose.
- A hit INSIDE the requested element is its own nested control, not an overlay.
Both produced 'covered by X — close or move the overlay', advice that cannot
be followed because there is nothing to close. Nested hits now say so and
point at retargeting.
- browser_click_at, browser_insert_text, and browser_drag listed targetChanged
in their effect formulas, but none passes an elementId, so no targetState is
ever captured and the term was always false — coverage that read as real.
Removed, with a test pinning the dependency.
- The seven effect formulas are deliberately NOT collapsed into one predicate:
drag must trust domChanged where others must not, hover must ignore field and
focus changes, click counts focus only for editables. Forcing one would make
each tool wrong differently. The differences are now documented in one place
next to the shared computation, so divergence is a declared policy rather
than an accident.
`The point you targeted lands on ${blocker}, which is its own control inside that element — nothing is covering it. Take a fresh browser_snapshot and use the id of the control you actually want.`
893
+
)
894
+
}
889
895
if(code==='suggestions-open'){
890
896
thrownewToolError(
891
897
'That editable field is already focused and covered by its own suggestions popup. Use browser_type on the same element; do not dismiss the popup first.'
@@ -1298,6 +1304,29 @@ async function pageActionState(
1298
1304
returntoRecord(state)
1299
1305
}
1300
1306
1307
+
/**
1308
+
* Which signals each tool accepts as proof its action reached the page.
1309
+
*
1310
+
* The tools deliberately do NOT share one predicate — the differences are real,
1311
+
* and flattening them would make every tool wrong in a different direction:
1312
+
*
1313
+
* - `browser_drag` is the only tool that trusts `domChanged`, because a drop
1314
+
* that reorders a list may change nothing else observable. Everywhere else
1315
+
* background churn (Slack, Gmail) would forge success for an ignored action.
1316
+
* - `browser_hover` ignores `fieldChanged` and `focusChanged`: hovering does not
1317
+
* type or focus, so those would only ever be someone else's effect.
1318
+
* - `browser_click` / `browser_click_at` count `focusChanged` only when the
1319
+
* target was editable — otherwise a click that merely moved focus reads as
1320
+
* success.
1321
+
* - `targetChanged` requires a `targetState`, which `pageActionState` captures
1322
+
* only when given an `elementId`. Tools without one (click_at, insert_text,
1323
+
* drag, press_key) cannot use it; listing it there read as coverage they did
1324
+
* not have, and it was silently always false.
1325
+
*
1326
+
* What IS shared is this function: every signal is computed here once, so a
1327
+
* tool's formula is a statement about which evidence it trusts, not a private
1328
+
* re-derivation of what changed.
1329
+
*/
1301
1330
functionpageEffect(
1302
1331
beforePage: Record<string,unknown>,
1303
1332
afterPage: Record<string,unknown>,
@@ -3207,11 +3236,13 @@ async function executeToolInner(
0 commit comments