feat: release 2.2.0 with PC-only tests and execution reliability fixes
Unify task scheduling and queue state, fix Android input and observation recovery, preserve screenshot paths, and match final review criteria accurately. Add collapsible copyable results and publish the complete 2.1.0 upgrade notes. Validation: 3189 backend tests, 326 frontend tests, 24 client tests, production build and quality gates.
This commit is contained in:
1 parent
fac4ccb760
commit
be6e8c2abd
79 files changed
+2263
-277
No files matched your search
@@ -28,9 +28,11 @@ import asyncio
|
||||
import base64
|
||||
import hashlib
|
||||
import math
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from PIL import Image
|
||||
|
||||
from artemis.agents.validator.categories import ValidationErrorCategory
|
||||
from artemis.context import ArtemisContext
|
||||
@@ -56,6 +58,21 @@ _MAX_ATTEMPTS = 3
|
||||
_RETRY_DELAY_SECONDS = 0.3
|
||||
|
||||
|
||||
def _annotate_target(image_bytes, coords, coordinate_size, **kwargs) -> bytes:
|
||||
"""Map device pixels into each encoded screenshot before marking/cropping it."""
|
||||
if coordinate_size is not None:
|
||||
width, height = coordinate_size
|
||||
with Image.open(BytesIO(image_bytes)) as image:
|
||||
image_width, image_height = image.size
|
||||
if abs((image_width / image_height) / (width / height) - 1) > 0.01:
|
||||
raise ValueError("Screenshot orientation/geometry changed; observe the target again.")
|
||||
x, y = coords
|
||||
if not (0 <= x < width and 0 <= y < height):
|
||||
raise ValueError("Target lies outside the observed device screen.")
|
||||
coords = [round(x * image_width / width), round(y * image_height / height)]
|
||||
return visualization.crop_and_annotate_target(image_bytes, coords, **kwargs)
|
||||
|
||||
|
||||
def _strict_target(action_item: dict) -> bool:
|
||||
return any(
|
||||
action_item.get(key)
|
||||
@@ -71,7 +88,7 @@ def _strict_target(action_item: dict) -> bool:
|
||||
|
||||
def _unverified(action_item: dict, message: str) -> tuple[bool, ValidationErrorCategory, str]:
|
||||
if _strict_target(action_item):
|
||||
return False, ValidationErrorCategory.PIXEL_TARGET_DISAPPEARED, message
|
||||
return False, ValidationErrorCategory.PIXEL_VERIFICATION_UNAVAILABLE, message
|
||||
return True, ValidationErrorCategory.PIXEL_BYPASSED, ""
|
||||
|
||||
|
||||
@@ -182,6 +199,8 @@ async def _run_attempt(
|
||||
action_item: dict,
|
||||
state: State | None,
|
||||
attempt: int,
|
||||
*,
|
||||
coordinate_size: tuple[int, int] | None = None,
|
||||
) -> tuple[bool, ValidationErrorCategory, str] | None:
|
||||
"""Runs one pixel validation attempt.
|
||||
|
||||
@@ -190,20 +209,18 @@ async def _run_attempt(
|
||||
Raises on infrastructure errors so the caller's retry loop can decide.
|
||||
"""
|
||||
# A. Take fresh live screenshot (captures state changes / dynamic loading settling)
|
||||
live_screenshot_b64 = None
|
||||
try:
|
||||
live_screenshot_b64 = await session.screenshot_b64()
|
||||
success_img = True
|
||||
except Exception:
|
||||
success_img, live_screenshot_b64 = False, None
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"Failed to acquire live screenshot: {exc}") from exc
|
||||
|
||||
if not success_img or not live_screenshot_b64:
|
||||
raise Exception("Failed to acquire live screenshot from controller/MCP tool.")
|
||||
if not live_screenshot_b64:
|
||||
raise RuntimeError("Live screenshot was empty; target identity could not be checked.")
|
||||
|
||||
# B. Decode and crop the live screen target
|
||||
live_bytes = base64.b64decode(live_screenshot_b64)
|
||||
live_crop_bytes = visualization.crop_and_annotate_target(
|
||||
live_bytes, current_coords, crop_size=None, dot_radius=15
|
||||
live_crop_bytes = _annotate_target(
|
||||
live_bytes, current_coords, coordinate_size, crop_size=None, dot_radius=15
|
||||
)
|
||||
|
||||
# C. Formulate multi-modal verification message
|
||||
@@ -308,9 +325,19 @@ async def validate_action_precondition_pixel(
|
||||
if not current_coords or len(current_coords) != 2:
|
||||
current_coords = orig_coords
|
||||
|
||||
device = getattr(ctx, "device", None)
|
||||
dimensions = (getattr(device, "device_width", None), getattr(device, "device_height", None))
|
||||
coordinate_size = (
|
||||
dimensions if all(type(value) is int and value > 0 for value in dimensions) else None
|
||||
)
|
||||
|
||||
try:
|
||||
neighborhood = visualization.crop_and_annotate_target(
|
||||
base64.b64decode(pre_screenshot_b64), orig_coords, crop_size=256, dot_radius=0
|
||||
neighborhood = _annotate_target(
|
||||
base64.b64decode(pre_screenshot_b64),
|
||||
orig_coords,
|
||||
coordinate_size,
|
||||
crop_size=256,
|
||||
dot_radius=0,
|
||||
)
|
||||
except (ValueError, TypeError, OSError):
|
||||
neighborhood = pre_screenshot_b64.encode()
|
||||
@@ -342,8 +369,8 @@ async def validate_action_precondition_pixel(
|
||||
# 2. Decode and crop the static reference (original) image ONCE outside the loop to save CPU
|
||||
try:
|
||||
orig_bytes = base64.b64decode(pre_screenshot_b64)
|
||||
orig_crop_bytes = visualization.crop_and_annotate_target(
|
||||
orig_bytes, orig_coords, crop_size=None, dot_radius=15
|
||||
orig_crop_bytes = _annotate_target(
|
||||
orig_bytes, orig_coords, coordinate_size, crop_size=None, dot_radius=15
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
@@ -378,6 +405,7 @@ async def validate_action_precondition_pixel(
|
||||
action_item,
|
||||
state,
|
||||
attempt,
|
||||
coordinate_size=coordinate_size,
|
||||
)
|
||||
if verdict is not None:
|
||||
if not verdict[0]:
|
||||
@@ -396,7 +424,9 @@ async def validate_action_precondition_pixel(
|
||||
f" error: {last_error}. Target identity remains unverified."
|
||||
)
|
||||
return _unverified(
|
||||
action_item, "Pixel verification failed; observe and locate the target again."
|
||||
action_item,
|
||||
f"Pixel verification unavailable after {_MAX_ATTEMPTS} attempts: {last_error}. "
|
||||
"The action was not executed; restore observation before locating the target again.",
|
||||
)
|
||||
|
||||
return _unverified(action_item, "Pixel validation did not establish target identity.")
|
||||
Reference in new issue
Block a user