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
@@ -575,6 +575,10 @@ class OperatorNode:
|
||||
state.execution_report = None
|
||||
report_requested = False
|
||||
other_tool_failed = False
|
||||
follow_up_pending = bool(
|
||||
getattr(state, "injected_instruction", None)
|
||||
or getattr(state, "operator_feedback", None)
|
||||
)
|
||||
|
||||
for iteration in range(max_iterations):
|
||||
control = getattr(self.ctx, "task_control", None)
|
||||
@@ -584,6 +588,14 @@ class OperatorNode:
|
||||
):
|
||||
# The device may have changed while another task held its lease.
|
||||
return None, None, None, False
|
||||
if (
|
||||
not follow_up_pending
|
||||
and not other_tool_failed
|
||||
and self._pro_ready_for_settlement(state)
|
||||
):
|
||||
# Let the graph settle checkpoints and run final review. A
|
||||
# completed ordinary plan does not need a synthetic device action.
|
||||
break
|
||||
if (
|
||||
not report_requested
|
||||
and not other_tool_failed
|
||||
@@ -707,6 +719,9 @@ class OperatorNode:
|
||||
current_messages.append(response)
|
||||
logger.info("Exploration final report prepared; returning for final review.")
|
||||
break
|
||||
if report_text and not other_tool_failed and self._pro_ready_for_settlement(state):
|
||||
current_messages.append(response)
|
||||
break
|
||||
|
||||
if getattr(self.ctx, "exploration_id", None):
|
||||
from artemis.exploration.runtime import publish_response
|
||||
@@ -751,11 +766,15 @@ class OperatorNode:
|
||||
"缺少证据时如实说明,不要为了结束再操作设备。"
|
||||
if report_requested
|
||||
else "No tool call was returned, so no device action occurred. "
|
||||
"Use the available tool interface now, not a prose promise or JSON "
|
||||
"inside text. Use an index from the current element list or "
|
||||
"normalized coordinates with target_description. If blocked, "
|
||||
"use an observation/diagnostic tool; do not invent a click or "
|
||||
"claim success. This recovery has a limited retry budget."
|
||||
"If the requested operations are already finished, read task_plan "
|
||||
"and reconcile any stale pending items against the recorded evidence "
|
||||
"using the note tools. Do not click, refocus, or type merely to "
|
||||
"produce a tool call or finish the turn. Do not mark genuinely "
|
||||
"unfinished work complete. Otherwise use the available tool "
|
||||
"interface for the remaining requested work, not a prose promise "
|
||||
"or JSON inside text. Use current observations for targets. If "
|
||||
"blocked, use an observation/diagnostic tool; do not invent an "
|
||||
"action or claim success. This recovery has a limited retry budget."
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1111,6 +1130,19 @@ class OperatorNode:
|
||||
logger.warning("Some action calls failed validation. Feeding back to LLM.")
|
||||
continue
|
||||
no_progress_attempts = 0
|
||||
if not other_tool_failed and any(
|
||||
normalize_name(tc["name"]) in {"save_note", "update_note", "append_note"}
|
||||
and tc["args"].get("key") == "task_plan"
|
||||
for tc in other_calls
|
||||
):
|
||||
follow_up_pending = False
|
||||
if (
|
||||
not follow_up_pending
|
||||
and not other_tool_failed
|
||||
and self._pro_ready_for_settlement(state)
|
||||
):
|
||||
# Also handle completion in the final permitted helper iteration.
|
||||
break
|
||||
else:
|
||||
if no_progress_attempts:
|
||||
raise OperatorNoProgressError(
|
||||
@@ -1272,6 +1304,21 @@ class OperatorNode:
|
||||
"operator_tool_limit_exceeded": tool_limit_exceeded,
|
||||
}
|
||||
|
||||
def _pro_ready_for_settlement(self, state: State) -> bool:
|
||||
if getattr(self.ctx, "exploration_id", None):
|
||||
return False # Exploration must deliver its report before settlement.
|
||||
if getattr(state, "checker_success", None) is False:
|
||||
return False
|
||||
control = getattr(self.ctx, "task_control", None)
|
||||
if isinstance(control, TaskControl) and has_pending_instruction(control.directory):
|
||||
return False
|
||||
snapshot = parse_plan(self._read_task_plan_text() or "")
|
||||
return bool(
|
||||
snapshot.all_top_level_done
|
||||
and not any(item.is_pending or item.is_active for item in snapshot.items)
|
||||
and (not snapshot.continuous_top_level or getattr(state, "user_stop_requested", False))
|
||||
)
|
||||
|
||||
def _exploration_ready_for_report(self, state: State) -> bool:
|
||||
if not getattr(self.ctx, "exploration_id", None):
|
||||
return False
|
||||
|
||||
@@ -586,6 +586,12 @@ def render_execution_incident(incident: dict, steps: list) -> str:
|
||||
" appeared on top of your target (dialog, sheet, banner, keyboard, or a"
|
||||
" re-laid-out screen)."
|
||||
)
|
||||
elif category == "pixel_verification_unavailable":
|
||||
lines.append(
|
||||
"Evidence: target verification could not be completed; this does not show that"
|
||||
" the target disappeared. Restore a working observation before locating the"
|
||||
" target again. Do not infer a business application failure from this incident."
|
||||
)
|
||||
elif category in ("target_disappeared", "pixel_target_disappeared"):
|
||||
prior_str = (
|
||||
f" Your last dispatched action was `{prior[0]}` (Step {prior[1]});"
|
||||
|
||||
Reference in new issue
Block a user