feat(android): add state conditions and measured gesture feedback

Add optional ensure_state, gesture region and final value feedback shared by Flash and Pro. Preserve completed inputs when verification fails and stop repeated ineffective strategies. Regenerate pinned tool schemas for the new optional arguments.
This commit is contained in:
chenmingxuan committed 2026-11-02 23:20:34 +08:00
1 parent 6a69a595ef
commit 4ea7939d9c
20 files changed
+2779 -33

No files matched your search

+42 -4
View File
@@ -158,10 +158,10 @@ async def _pause(deadline: float, interval: float) -> None:
def _condition(request: WaitUntilRequest, node: dict) -> bool:
state = request.state
if state == "text":
if node.get("password") is True or not isinstance(node.get("text"), str):
if state in {"text", "content_desc"}:
if node.get("password") is True or not isinstance(node.get(state), str):
raise ValueError("An exact readable native text value is required.")
return node["text"] == request.expected_text
return node[state] == request.expected_text
if state == "value":
native_range = _range(node)
if native_range is None or request.expected_value is None:
@@ -268,6 +268,23 @@ async def _control(request: ControlActionRequest, native: NativeCaller) -> dict:
if not inspection["success"]:
return _result("control_action", inspection)
node = inspection["node"]
condition = None
if request.ensure_state is not None:
condition = WaitUntilRequest(
selector=request.selector,
timeout_ms=request.verify_timeout_ms,
**request.ensure_state.model_dump(),
)
try:
satisfied = _condition(condition, node)
except ValueError as exc:
return _result(
"control_action", _failure("INVALID_STATE", str(exc)), preflight=inspection
)
if satisfied:
return _result(
"control_action", inspection, skipped=True, verification="condition_satisfied"
)
invalid = _write_guard(node, request.action, request.expected_token)
if invalid is None and request.action == "set_progress":
native_range = _range(node)
@@ -284,9 +301,30 @@ async def _control(request: ControlActionRequest, native: NativeCaller) -> dict:
)
if invalid is not None:
return _result("control_action", invalid, preflight=inspection, node_before=node)
args = request.model_dump(mode="json", exclude_none=True)
args = request.model_dump(
mode="json", exclude_none=True, exclude={"ensure_state", "verify_timeout_ms"}
)
args["expected_token"] = node["token"]
receipt = await _call(native, "control_action", args)
if receipt["success"] and condition is not None:
verification = await _wait(condition, native)
outcome = dict(receipt)
if not verification["success"]:
outcome.update(
success=False,
code=verification["code"],
error="The input completed, but its requested state was not verified. Do not toggle again blindly.",
)
return _result(
"control_action",
outcome,
native_receipt=receipt,
preflight=inspection,
postcondition=verification,
node_before=node,
node_after=verification.get("node"),
verification="condition_satisfied" if verification["success"] else "condition_not_met",
)
return _result(
"control_action",
receipt,