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:
chenmingxuan committed 2026-10-27 01:13:41 +08:00
1 parent fac4ccb760
commit be6e8c2abd
79 files changed
+2263 -277

No files matched your search

+52 -5
View File
@@ -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
+6
View File
@@ -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]});"