fix(replay): guard single in-flight dispatch

Add a private in-flight helper for replay dispatch, block machine retargeting while an action is still pending on the previous session, and warn on duplicate in-flight entries for the same replay triplet.

Freeze the Notepad runtime dialog success path and add integration coverage for single in-flight dispatch, watchdog late-report documentation, and the known concurrent-poll race as an xfail.
This commit is contained in:
Dom
2026-05-25 11:00:59 +02:00
parent 7bb8d543ab
commit 4ba426c205
3 changed files with 805 additions and 10 deletions

View File

@@ -391,6 +391,8 @@ class TestSetupActionsSkipPixelChange:
class TestRuntimeDialogHandling:
def test_handle_confirm_save_dialog_clicks_oui_via_server(self):
exe = _make_executor_skeleton()
exe._active_window_rect_for_dialog = MagicMock(return_value=None)
exe._try_click_runtime_dialog_button_uia = MagicMock(return_value=None)
exe._capture_screenshot_b64 = MagicMock(return_value="abc")
exe._server_resolve_target = MagicMock(
return_value={
@@ -403,6 +405,7 @@ class TestRuntimeDialogHandling:
)
exe._find_text_on_screen = MagicMock(return_value=None)
exe._click = MagicMock()
exe._wait_until_title_changes = MagicMock(return_value="Enregistrer sous")
spec = ActionExecutorV1._match_known_runtime_dialog(
"Confirmer l'enregistrement"
@@ -418,6 +421,22 @@ class TestRuntimeDialogHandling:
exe._server_resolve_target.assert_called_once()
exe._click.assert_called_once_with((480, 810), "left")
def test_confirm_save_geometry_fallback_targets_yes_button_in_active_window(self):
exe = _make_executor_skeleton()
spec = ActionExecutorV1._match_known_runtime_dialog(
"Confirmer l'enregistrement"
)
rect = {
"left": 1000,
"top": 600,
"width": 520,
"height": 200,
}
pos = exe._runtime_dialog_button_geometry_fallback(spec, "Oui", rect)
assert pos == (1338, 764)
def test_runtime_dialog_before_pause_returns_skip_result(self):
exe = _make_executor_skeleton()
exe._check_and_pause_on_system_dialog = MagicMock(return_value=False)
@@ -491,6 +510,60 @@ class TestRuntimeDialogHandling:
assert res["actual_position"] == {"x_pct": 0.5, "y_pct": 0.5}
exe._handle_known_runtime_dialog.assert_called_once()
def test_live_notepad_confirm_save_dialog_is_frozen_offline(self):
exe = _make_executor_skeleton()
exe._click = MagicMock()
exe._quick_screenshot_hash = MagicMock(return_value="hash_before")
exe._wait_for_screen_change = MagicMock(return_value=True)
exe._capture_human_correction = MagicMock(return_value=[])
exe._handle_known_runtime_dialog = MagicMock(
return_value={
"handled": True,
"button_text": "Oui",
"x_pct": 0.63,
"y_pct": 0.76,
"resolution_score": 0.9,
"post_title": "http192.168.1.408765dossier.htmlid=.txt - Bloc-notes",
}
)
action = {
"action_id": "act_raw_a8dbaaac",
"type": "click",
"x_pct": 0.5,
"y_pct": 0.5,
"expected_window_title": (
"http192.168.1.408765dossier.htmlid=.txt - Bloc-notes"
),
}
titles = iter(
[
{"title": "Confirmer l'enregistrement"},
{"title": "http192.168.1.408765dossier.htmlid=.txt - Bloc-notes"},
]
)
with patch("agent_v0.agent_v1.core.executor.time.sleep", lambda *_a, **_k: None):
with patch(
"agent_v0.agent_v1.window_info_crossplatform.get_active_window_info",
side_effect=lambda: next(titles),
):
res = exe.execute_replay_action(action)
assert res["success"] is True
assert res["warning"] == "runtime_dialog_handled_post_verify"
assert res["action_id"] == "act_raw_a8dbaaac"
assert res.get("needs_human") is not True
assert "correction" not in res
assert res["runtime_dialog"] == {
"dialog_id": "confirm_save_overwrite",
"dialog_title": "Confirmer l'enregistrement",
"button_text": "Oui",
}
exe._handle_known_runtime_dialog.assert_called_once()
exe._capture_human_correction.assert_not_called()
def test_post_verify_can_retry_same_runtime_dialog_before_recovery(self):
exe = _make_executor_skeleton()
exe._click = MagicMock()