diff --git a/loopx/attached_session.py b/loopx/attached_session.py index 9a5715e7a9..cc77c6f776 100644 --- a/loopx/attached_session.py +++ b/loopx/attached_session.py @@ -181,9 +181,10 @@ def _require_attached_host( session_id: str, host_surface: str, host_session_id: str, + allow_closed: bool = False, ) -> dict[str, Any]: session = store.load_session(session_id) - if session is None or session.get("status") == "closed": + if session is None or (session.get("status") == "closed" and not allow_closed): raise KeyError("attached Agent session was not found") if session.get("session_mode") != CHAT_SESSION_MODE_ATTACHED: raise ValueError("the selected Session is not an attached host session") @@ -273,6 +274,7 @@ def complete_attached_agent_turn( session_id=session_id, host_surface=host_surface, host_session_id=host_session_id, + allow_closed=True, ) normalized_response = normalize_agent_response( response, diff --git a/tests/test_attached_session_broker.py b/tests/test_attached_session_broker.py index 29b429d2cf..46b3873eaa 100644 --- a/tests/test_attached_session_broker.py +++ b/tests/test_attached_session_broker.py @@ -608,6 +608,43 @@ def test_attached_close_rejects_active_claim_and_preserves_completion( assert runtime.wait_for_turn(session_id=session_id, turn_id=turn_id)["status"] == "completed" assert runtime.close_session(session_id) is True assert store.load_session(session_id)["status"] == "closed" # type: ignore[index] + with pytest.raises(KeyError, match="attached Agent session was not found"): + claim_attached_agent_turn( + store=store, + session_id=session_id, + host_surface=HOST_SURFACE, + host_session_id=HOST_SESSION_ID, + claim_id="claim-after-close", + ) + + replay = complete_attached_agent_turn( + store=store, + session_id=session_id, + turn_id=turn_id, + host_surface=HOST_SURFACE, + host_session_id=HOST_SESSION_ID, + claim_id="close-active-claim", + completion_id="close-active-completion", + response={"message": "retry payload"}, + ) + + assert replay["created"] is False + assert [ + message["text"] + for message in store.messages(session_id) + if message["role"] == "agent" + ] == ["completed before close"] + with pytest.raises(ValueError, match="already completed by another receipt"): + complete_attached_agent_turn( + store=store, + session_id=session_id, + turn_id=turn_id, + host_surface=HOST_SURFACE, + host_session_id=HOST_SESSION_ID, + claim_id="close-active-claim", + completion_id="different-receipt", + response={"message": "conflicting retry"}, + ) def test_attached_close_rejects_pending_queue_and_preserves_claimability(