| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
| from typing import Any, Dict, Iterable, Optional, Tuple |
|
|
|
|
| @dataclass |
| class SafeActionCheck: |
| safe: bool |
| reason: Any |
|
|
|
|
| def element_text(element: Optional[Dict[str, Any]]) -> str: |
| if not element: |
| return "" |
| fields = [ |
| element.get("element_id"), |
| element.get("stable_uid"), |
| element.get("role"), |
| element.get("name"), |
| element.get("text"), |
| element.get("href"), |
| element.get("tag"), |
| element.get("type"), |
| ] |
| return " ".join(str(x or "") for x in fields).lower() |
|
|
|
|
| def find_element(elements: Iterable[Dict[str, Any]], element_id: Optional[str]) -> Optional[Dict[str, Any]]: |
| if not element_id: |
| return None |
| for e in elements: |
| if e.get("element_id") == element_id or e.get("stable_uid") == element_id: |
| return e |
| return None |
|
|
|
|
| def check_safe_action( |
| *, |
| action: Dict[str, Any], |
| elements: Iterable[Dict[str, Any]], |
| safe_target_contains: Dict[str, list[str]], |
| allow_done: bool = True, |
| allow_wait: bool = True, |
| ) -> SafeActionCheck: |
| act = action.get("action") |
| target = action.get("target_element_id") |
|
|
| if act == "done": |
| return SafeActionCheck(bool(allow_done), "done_allowed" if allow_done else "done_blocked") |
| if act == "wait": |
| return SafeActionCheck(bool(allow_wait), "wait_allowed" if allow_wait else "wait_blocked") |
|
|
| rules = safe_target_contains.get(act) |
| if not rules: |
| return SafeActionCheck(False, {"reason": "no_safe_rule_for_action", "action": act}) |
|
|
| e = find_element(elements, target) |
| txt = element_text(e) |
|
|
| if any(str(rule).lower() in txt for rule in rules): |
| return SafeActionCheck(True, {"reason": "target_matches_safe_rule", "target_text": txt}) |
|
|
| return SafeActionCheck(False, { |
| "reason": "target_mismatch", |
| "action": act, |
| "target": target, |
| "target_text": txt, |
| "required_any": rules, |
| }) |
|
|
|
|
| def strict_url_success(url: str, required_substrings: list[str]) -> bool: |
| u = (url or "").lower() |
| return any(x.lower() in u for x in required_substrings) |
|
|