"""Tests for mcp_serve handlers using mcp_bridge.

Tests the refactored _handle_status, _handle_interact, and _handle_kill
handlers to verify bridge path vs fallback path behavior.
"""

import asyncio
import json
import os
import shutil
import uuid
from collections.abc import Iterator
from pathlib import Path
from unittest.mock import MagicMock, patch

import httpx
import pytest
from returns.result import Failure, Success

from anima.core.schemas import (
    AgentNotFoundError,
    AgentState,
    AgentStateData,
    LifecycleType,
    MCPSocketNotFoundError,
    MCPConnectionError,
    TokenUsage,
)
from anima.shell.mcp_agent import AgentMCPServer
from anima.shell.mcp_serve import ServeMCPServer


@pytest.fixture
def tmp_state_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """Create a temporary state directory and set ANIMA_STATE."""
    state_dir = tmp_path / "anima_state"
    state_dir.mkdir()
    monkeypatch.setenv("ANIMA_STATE", str(state_dir))
    return state_dir


@pytest.fixture
def sample_agent_state() -> AgentStateData:
    """Create sample agent state data."""
    return AgentStateData(
        instance_id="inst_test123",
        trace_id="trace_abc",
        persona_name="test_persona",
        spec_id="spec_001",
        spec_digest="abc123",
        model="test-model",
        state=AgentState.running,
        lifecycle=LifecycleType.ephemeral,
        parent_id=None,
        children=[],
        depth=0,
        max_depth=3,
        pid=12345,
        mcp_socket="/tmp/anima/agents/inst_test123/mcp.sock",
        created_at="2024-01-01T00:00:00Z",
        updated_at="2024-01-01T00:00:00Z",
        token_usage=TokenUsage(),
        tools_called=[],
        current_turn=0,
        max_turns=100,
        budget_total=100000,
        grace_budget=1000,
        error=None,
        result_ref=None,
    )


@pytest.fixture
def server() -> ServeMCPServer:
    """Create a ServeMCPServer instance."""
    return ServeMCPServer(host="127.0.0.1", port=8420)


@pytest.fixture
def short_state_dir(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]:
    """Create short ANIMA_STATE path for Unix socket length constraints."""
    state_dir = Path(f"/tmp/anima-sse-{uuid.uuid4().hex[:8]}")
    state_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.setenv("ANIMA_STATE", str(state_dir))
    try:
        yield state_dir
    finally:
        shutil.rmtree(state_dir, ignore_errors=True)


def _write_running_state(
    *,
    state_dir: Path,
    state: AgentStateData,
    socket_path: Path,
) -> None:
    """Persist running agent state so serve bridge resolves real socket path."""
    state.pid = os.getpid()
    state.state = AgentState.running
    state.mcp_socket = str(socket_path)

    agent_dir = state_dir / "agents" / state.instance_id
    agent_dir.mkdir(parents=True, exist_ok=True)
    state_file = agent_dir / "state.json"
    state_file.write_text(state.model_dump_json())


async def _collect_sse_frames(response: object) -> list[tuple[str, str]]:
    """Collect event/data pairs from EventSourceResponse body iterator."""
    iterator = getattr(response, "body_iterator")
    frames: list[tuple[str, str]] = []
    chunks: list[str] = []
    async for chunk in iterator:
        if isinstance(chunk, dict):
            event_type = str(chunk.get("event", "")).strip()
            data_value = str(chunk.get("data", "")).strip()
            if event_type and data_value:
                frames.append((event_type, data_value))
            continue
        if isinstance(chunk, bytes):
            chunks.append(chunk.decode("utf-8"))
        else:
            chunks.append(str(chunk))

    if frames:
        return frames

    payload = "".join(chunks)
    blocks = payload.replace("\r\n", "\n").split("\n\n")
    parsed_frames: list[tuple[str, str]] = []
    for block in blocks:
        event_type = ""
        data_value = ""
        for line in block.split("\n"):
            if line.startswith("event:"):
                event_type = line[6:].strip()
            elif line.startswith("data:"):
                data_value = line[5:].strip()
        if event_type and data_value:
            parsed_frames.append((event_type, data_value))
    return parsed_frames


class TestHandleStatusBridgeAndFallback:
    """Tests for _handle_status using bridge and fallback."""

    @pytest.mark.asyncio
    async def test_status_uses_bridge_for_alive_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that status uses bridge (MCP call) for alive agents."""
        # Create agent state with socket (alive agent)
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Mock the bridge to return live status
        mock_status_response = {
            "instance_id": sample_agent_state.instance_id,
            "state": "running",
            "persona_name": "test_persona",
            "lifecycle": "ephemeral",
            "token_usage": {
                "prompt_tokens": 100,
                "completion_tokens": 50,
                "total_tokens": 150,
            },
            "tools_called": [],
            "uptime_seconds": 10,
            "parent_id": None,
            "current_turn": 1,
            "max_turns": 100,
            "_meta": {"trace_id": "trace_abc"},
        }

        with patch(
            "anima.shell.mcp_serve.read_agent_state_with_fallback_async"
        ) as mock_fallback:
            mock_fallback.return_value = Success(mock_status_response)

            result = await server._handle_status(sample_agent_state.instance_id)

            # Bridge should be called
            mock_fallback.assert_called_once_with(sample_agent_state.instance_id)
            # Result should contain the bridge response
            assert result["state"] == "running"
            assert result["instance_id"] == sample_agent_state.instance_id
            assert result["_meta"]["trace_id"] == "trace_abc"
            assert result["_meta"]["instance_id"] == sample_agent_state.instance_id

    @pytest.mark.asyncio
    async def test_status_fallback_for_dead_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that status uses file fallback for dead agents."""
        # Create agent state directory but no socket (dead agent)
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Mock the bridge to return file fallback
        mock_status_response = {
            "instance_id": sample_agent_state.instance_id,
            "trace_id": sample_agent_state.trace_id,
            "state": "completed",
            "persona_name": "test_persona",
            "lifecycle": "ephemeral",
            "token_usage": {
                "prompt_tokens": 100,
                "completion_tokens": 50,
                "total_tokens": 150,
            },
            "tools_called": [],
            "uptime_seconds": 60,
            "parent_id": None,
            "current_turn": 5,
            "max_turns": 100,
        }

        with patch(
            "anima.shell.mcp_serve.read_agent_state_with_fallback_async"
        ) as mock_fallback:
            mock_fallback.return_value = Success(mock_status_response)

            result = await server._handle_status(sample_agent_state.instance_id)

            # Fallback should be used (bridge returns file when socket missing)
            mock_fallback.assert_called_once()
            assert result["state"] == "completed"
            assert result["_meta"]["trace_id"] == sample_agent_state.trace_id

    @pytest.mark.asyncio
    async def test_status_generates_trace_id_when_fallback_payload_missing_trace_id(
        self,
        server: ServeMCPServer,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Status success envelope always has non-empty _meta.trace_id."""
        fallback_without_trace = {
            "instance_id": sample_agent_state.instance_id,
            "state": "running",
            "persona_name": sample_agent_state.persona_name,
            "lifecycle": "ephemeral",
            "token_usage": {
                "prompt_tokens": 0,
                "completion_tokens": 0,
                "total_tokens": 0,
            },
            "tools_called": [],
            "uptime_seconds": 0,
            "parent_id": None,
            "current_turn": 0,
            "max_turns": 0,
        }
        with patch(
            "anima.shell.mcp_serve.read_agent_state_with_fallback_async"
        ) as mock_fallback:
            mock_fallback.return_value = Success(fallback_without_trace)

            result = await server._handle_status(sample_agent_state.instance_id)

            assert result["_meta"]["instance_id"] == sample_agent_state.instance_id
            assert result["_meta"]["trace_id"]

    @pytest.mark.asyncio
    async def test_status_error_when_agent_not_found(
        self,
        server: ServeMCPServer,
    ) -> None:
        """Test that status returns error envelope when agent not found."""
        with patch(
            "anima.shell.mcp_serve.read_agent_state_with_fallback_async"
        ) as mock_fallback:
            mock_fallback.return_value = Failure(
                AgentNotFoundError("Agent not found: nonexistent")
            )

            result = await server._handle_status("nonexistent")

            assert "error" in result
            # Error code is numeric_code (300) in the envelope
            assert result["error"]["numeric_code"] == 300

    @pytest.mark.asyncio
    async def test_status_bridges_to_live_agent_socket(
        self,
        server: ServeMCPServer,
        short_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Integration: serve status reaches a live per-agent MCP socket."""
        sample_agent_state.state = AgentState.running
        agent_dir = short_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True, exist_ok=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        runner = MagicMock()
        runner.instance_id = sample_agent_state.instance_id
        live_server = AgentMCPServer(
            instance_id=sample_agent_state.instance_id,
            agent_runner=runner,
            interact_timeout_seconds=0.1,
        )

        start_result = await live_server.start()
        assert isinstance(start_result, Success)

        try:
            result = await server._handle_status(sample_agent_state.instance_id)
            assert "error" not in result
            assert result["instance_id"] == sample_agent_state.instance_id
            assert result["state"] == "running"
            assert result["persona_name"] == sample_agent_state.persona_name
        finally:
            stop_result = await live_server.stop()
            assert isinstance(stop_result, Success)


class TestRegisterToolsDocstring:
    """Regression test for serve _register_tools doc coverage."""

    def test_register_tools_docstring_mentions_full_surface(
        self,
        server: ServeMCPServer,
    ) -> None:
        """Docstring lists all currently registered serve MCP tools."""
        doc = server._register_tools.__doc__
        assert doc is not None
        for tool_name in (
            "anima.spawn",
            "anima.list",
            "anima.status",
            "anima.interact",
            "anima.collect",
            "anima.kill",
            "anima.logs",
            "anima.approve",
            "anima.reject",
            "anima.resume",
        ):
            assert tool_name in doc


class TestHandleInteractBridge:
    """Tests for _handle_interact using bridge."""

    @pytest.mark.asyncio
    async def test_interact_uses_bridge(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that interact uses bridge to call agent MCP."""
        # Create agent state with socket (alive agent)
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.running
        state_file.write_text(sample_agent_state.model_dump_json())

        # Mock the bridge to return interact response
        mock_interact_response = {
            "response": "Hello, human!",
            "turn_number": 1,
            "_meta": {"trace_id": "trace_abc"},
        }

        with (
            patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find,
            patch("anima.shell.mcp_serve.call_agent_mcp_tool_async") as mock_bridge,
        ):
            mock_find.return_value = Success(sample_agent_state)
            mock_bridge.return_value = Success(mock_interact_response)

            result = await server._handle_interact(
                sample_agent_state.instance_id, "Hello"
            )

            # Bridge should be called with interact tool
            mock_bridge.assert_awaited_once_with(
                sample_agent_state.instance_id,
                "anima.interact",
                arguments={"message": "Hello"},
            )
            assert result["response"] == "Hello, human!"
            assert result["turn_number"] == 1

    @pytest.mark.asyncio
    async def test_interact_returns_error_when_not_interactive(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that interact returns error when agent not in interactive state."""
        # Create agent state in completed state
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.completed
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            result = await server._handle_interact(
                sample_agent_state.instance_id, "Hello"
            )

            assert "error" in result
            assert result["error"]["code"] == "AGENT_NOT_INTERACTIVE"


class TestHandleKillBridge:
    """Tests for _handle_kill using bridge for interrupt."""

    @pytest.mark.asyncio
    async def test_kill_uses_bridge_for_interrupt(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that kill uses bridge to call interrupt for alive agents."""
        # Create agent state with socket (alive agent)
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.running
        state_file.write_text(sample_agent_state.model_dump_json())

        with (
            patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find,
            patch(
                "anima.shell.mcp_serve.build_shell_control_service"
            ) as mock_build_control,
            patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive,
            patch("anima.shell.mcp_serve.call_agent_mcp_tool_async") as mock_bridge,
        ):
            mock_control_service = MagicMock()
            mock_find.return_value = Success(sample_agent_state)
            mock_control_service.kill.return_value = Success(None)
            mock_build_control.return_value = mock_control_service
            mock_alive.return_value = True  # Agent is alive
            mock_bridge.return_value = Success({"acknowledged": True})

            result = await server._handle_kill(sample_agent_state.instance_id)

            # Kill should succeed
            assert result["acknowledged"] is True
            mock_control_service.kill.assert_called_once()
            # Bridge should be called to interrupt the agent
            mock_bridge.assert_awaited_once_with(
                sample_agent_state.instance_id,
                "anima.interrupt",
            )

    @pytest.mark.asyncio
    async def test_kill_fallback_when_agent_dead(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that kill still works when agent is already dead."""
        # Create agent state directory but no socket (dead agent)
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.running
        state_file.write_text(sample_agent_state.model_dump_json())

        with (
            patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find,
            patch(
                "anima.shell.mcp_serve.build_shell_control_service"
            ) as mock_build_control,
            patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive,
            patch("anima.shell.mcp_serve.call_agent_mcp_tool_async") as mock_bridge,
        ):
            mock_control_service = MagicMock()
            mock_find.return_value = Success(sample_agent_state)
            mock_control_service.kill.return_value = Success(None)
            mock_build_control.return_value = mock_control_service
            mock_alive.return_value = False  # Agent is dead

            result = await server._handle_kill(sample_agent_state.instance_id)

            # Kill should still succeed via fallback
            assert result["acknowledged"] is True
            mock_control_service.kill.assert_called_once()
            # Bridge should NOT be called since agent is dead
            mock_bridge.assert_not_awaited()

    @pytest.mark.asyncio
    async def test_kill_error_when_already_terminal(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that kill returns error when agent already in terminal state."""
        # Create agent state in completed state
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.completed
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            result = await server._handle_kill(sample_agent_state.instance_id)

            assert "error" in result
            # AgentTerminatedError has code "AGENT_TERMINATED"
            assert result["error"]["code"] == "AGENT_TERMINATED"


class TestHandleLogsBridgeAndFallback:
    """Tests for _handle_logs using bridge and fallback."""

    @pytest.mark.asyncio
    async def test_logs_uses_bridge_for_alive_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that logs uses bridge (MCP call) for alive agents."""
        # Create agent state with socket (alive agent)
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Mock bridge response
        mock_logs_response = {
            "instance_id": sample_agent_state.instance_id,
            "entries": [
                {"event": "spawn", "timestamp": "2024-01-01T00:00:00Z"},
                {"event": "response", "content": "Hello", "turn": 1},
            ],
            "count": 2,
            "_meta": {"trace_id": "trace_abc"},
        }

        with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
            mock_alive.return_value = True
            with patch(
                "anima.shell.mcp_serve.call_agent_mcp_tool_async"
            ) as mock_bridge:
                mock_bridge.return_value = Success(mock_logs_response)

                result = await server._handle_logs(
                    sample_agent_state.instance_id, tail=None
                )

                # Bridge should be called
                mock_bridge.assert_called_once()
                assert mock_bridge.call_args[0][0] == sample_agent_state.instance_id
                assert mock_bridge.call_args[0][1] == "anima.logs"
                # Result should contain the bridge response
                assert result["instance_id"] == sample_agent_state.instance_id
                assert result["count"] == 2
                assert len(result["entries"]) == 2

    @pytest.mark.asyncio
    async def test_logs_fallback_for_dead_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that logs uses file fallback for dead agents."""
        # Create agent state directory but no socket (dead agent)
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Create log.jsonl with entries
        log_file = agent_dir / "log.jsonl"
        log_file.write_text(
            '{"event": "spawn", "timestamp": "2024-01-01T00:00:00Z"}\n'
            '{"event": "response", "content": "Hello", "turn": 1}\n'
            '{"event": "response", "content": "World", "turn": 2}\n'
        )

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            result = await server._handle_logs(
                sample_agent_state.instance_id, tail=None
            )

            # Should return entries from file
            assert result["instance_id"] == sample_agent_state.instance_id
            assert result["count"] == 3
            assert len(result["entries"]) == 3

    @pytest.mark.asyncio
    async def test_logs_fallback_with_tail(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that logs file fallback respects tail parameter."""
        # Create agent state directory but no socket (dead agent)
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Create log.jsonl with entries
        log_file = agent_dir / "log.jsonl"
        log_file.write_text(
            '{"event": "spawn", "timestamp": "2024-01-01T00:00:00Z"}\n'
            '{"event": "response", "content": "Hello", "turn": 1}\n'
            '{"event": "response", "content": "World", "turn": 2}\n'
        )

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            result = await server._handle_logs(sample_agent_state.instance_id, tail=2)

            # Should return only last 2 entries
            assert result["count"] == 2
            assert len(result["entries"]) == 2

    @pytest.mark.asyncio
    async def test_logs_error_when_agent_not_found(
        self,
        server: ServeMCPServer,
    ) -> None:
        """Test that logs returns error envelope when agent not found."""
        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Failure(
                AgentNotFoundError("Agent not found: nonexistent")
            )

            result = await server._handle_logs("nonexistent", tail=None)

            assert "error" in result
            # Error code is numeric_code (300) in the envelope
            assert result["error"]["numeric_code"] == 300
            assert result["entries"] == []
            assert result["count"] == 0

    @pytest.mark.asyncio
    async def test_logs_returns_empty_for_missing_log_file(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that logs returns empty entries when log.jsonl doesn't exist."""
        # Create agent state directory but no socket or log file
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            result = await server._handle_logs(
                sample_agent_state.instance_id, tail=None
            )

            # Should return empty entries
            assert result["entries"] == []
            assert result["count"] == 0


class TestHandleApproveAndReject:
    """Tests for _handle_approve and _handle_reject using bridge and fallback."""

    @pytest.mark.asyncio
    async def test_approve_uses_bridge_for_alive_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that approve uses bridge (MCP call) for alive agents."""
        # Create agent state with socket (alive agent) in awaiting_approval state
        sample_agent_state.state = AgentState.awaiting_approval
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Mock bridge response
        mock_approve_response = {
            "instance_id": sample_agent_state.instance_id,
            "acknowledged": True,
        }

        with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
            mock_alive.return_value = True
            with patch(
                "anima.shell.mcp_serve.call_agent_mcp_tool_async"
            ) as mock_bridge:
                mock_bridge.return_value = Success(mock_approve_response)

                result = await server._handle_approve(
                    sample_agent_state.instance_id, note="LGTM"
                )

                # Bridge should be called
                mock_bridge.assert_called_once()
                assert mock_bridge.call_args[0][0] == sample_agent_state.instance_id
                assert mock_bridge.call_args[0][1] == "anima.approve"
                # Result should contain the bridge response
                assert result["instance_id"] == sample_agent_state.instance_id
                assert result["acknowledged"] is True

    @pytest.mark.asyncio
    async def test_approve_fallback_for_dead_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that approve uses file fallback for dead agents."""
        # Create agent state directory but no socket (dead agent) in awaiting_approval state
        sample_agent_state.state = AgentState.awaiting_approval
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            with patch("anima.shell.state.write_approval_response") as mock_write:
                mock_write.return_value = Success(None)
                with patch("anima.shell.state.transition_state") as mock_transition:
                    mock_transition.return_value = Success(None)

                    result = await server._handle_approve(
                        sample_agent_state.instance_id, note="Approved"
                    )

                    # Should call file fallback (write_approval_response)
                    mock_write.assert_called_once()
                    # Verify call args using a simpler check - verify function was called
                    call_args = mock_write.call_args
                    assert call_args is not None
                    # Should transition state
                    mock_transition.assert_called_once()
                    # Result should indicate success
                    assert result["acknowledged"] is True

    @pytest.mark.asyncio
    async def test_approve_does_not_fallback_when_bridge_fails_for_alive_agent(
        self,
        server: ServeMCPServer,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Bridge failure on alive agent returns MCP error, no dead fallback mutation."""
        sample_agent_state.state = AgentState.awaiting_approval
        with (
            patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find,
            patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive,
            patch("anima.shell.mcp_serve.call_agent_mcp_tool_async") as mock_bridge,
            patch("anima.shell.state.write_approval_response") as mock_write,
            patch("anima.shell.state.transition_state") as mock_transition,
        ):
            mock_find.return_value = Success(sample_agent_state)
            mock_alive.return_value = True
            mock_bridge.return_value = Failure(MCPConnectionError("socket timeout"))

            result = await server._handle_approve(sample_agent_state.instance_id)

            mock_write.assert_not_called()
            mock_transition.assert_not_called()
            assert result["acknowledged"] is False
            assert result["error"]["code"] == "MCP_CONNECTION_ERROR"
            assert result["error"]["numeric_code"] == 323

    @pytest.mark.asyncio
    async def test_approve_error_when_agent_not_found(
        self,
        server: ServeMCPServer,
    ) -> None:
        """Test that approve returns error envelope when agent not found."""
        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Failure(
                AgentNotFoundError("Agent not found: nonexistent")
            )

            result = await server._handle_approve("nonexistent", note="test")

            assert "error" in result
            assert result["error"]["numeric_code"] == 300
            assert result["error"]["code"] == "AGENT_NOT_FOUND"
            assert result["acknowledged"] is False

    @pytest.mark.asyncio
    async def test_approve_error_when_not_awaiting_approval(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that approve returns INVALID_STATE when agent not awaiting approval."""
        # Create agent in running state (not awaiting_approval)
        sample_agent_state.state = AgentState.running
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            result = await server._handle_approve(sample_agent_state.instance_id)

            assert "error" in result
            assert result["error"]["numeric_code"] == 400
            assert result["error"]["code"] == "INVALID_STATE"
            assert result["acknowledged"] is False

    @pytest.mark.asyncio
    async def test_reject_uses_bridge_for_alive_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that reject uses bridge (MCP call) for alive agents."""
        # Create agent state with socket (alive agent) in awaiting_approval state
        sample_agent_state.state = AgentState.awaiting_approval
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Mock bridge response
        mock_reject_response = {
            "instance_id": sample_agent_state.instance_id,
            "acknowledged": True,
        }

        with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
            mock_alive.return_value = True
            with patch(
                "anima.shell.mcp_serve.call_agent_mcp_tool_async"
            ) as mock_bridge:
                mock_bridge.return_value = Success(mock_reject_response)

                result = await server._handle_reject(
                    sample_agent_state.instance_id, reason="Not approved"
                )

                # Bridge should be called
                mock_bridge.assert_called_once()
                assert mock_bridge.call_args[0][0] == sample_agent_state.instance_id
                assert mock_bridge.call_args[0][1] == "anima.reject"
                # Result should contain the bridge response
                assert result["instance_id"] == sample_agent_state.instance_id
                assert result["acknowledged"] is True

    @pytest.mark.asyncio
    async def test_reject_fallback_for_dead_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that reject uses file fallback for dead agents."""
        # Create agent state directory but no socket (dead agent) in awaiting_approval state
        sample_agent_state.state = AgentState.awaiting_approval
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            with patch("anima.shell.state.write_approval_response") as mock_write:
                mock_write.return_value = Success(None)
                with patch("anima.shell.state.transition_state") as mock_transition:
                    mock_transition.return_value = Success(None)

                    result = await server._handle_reject(
                        sample_agent_state.instance_id, reason="Rejected"
                    )

                    # Should call file fallback (write_approval_response)
                    mock_write.assert_called_once()
                    # Verify call args using a simpler check - verify function was called
                    call_args = mock_write.call_args
                    assert call_args is not None
                    # Should transition state
                    mock_transition.assert_called_once()
                    # Result should indicate success
                    assert result["acknowledged"] is True

    @pytest.mark.asyncio
    async def test_reject_does_not_fallback_when_bridge_fails_for_alive_agent(
        self,
        server: ServeMCPServer,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Bridge failure on alive agent returns MCP error, no dead fallback mutation."""
        sample_agent_state.state = AgentState.awaiting_approval
        with (
            patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find,
            patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive,
            patch("anima.shell.mcp_serve.call_agent_mcp_tool_async") as mock_bridge,
            patch("anima.shell.state.write_approval_response") as mock_write,
            patch("anima.shell.state.transition_state") as mock_transition,
        ):
            mock_find.return_value = Success(sample_agent_state)
            mock_alive.return_value = True
            mock_bridge.return_value = Failure(MCPConnectionError("socket timeout"))

            result = await server._handle_reject(sample_agent_state.instance_id)

            mock_write.assert_not_called()
            mock_transition.assert_not_called()
            assert result["acknowledged"] is False
            assert result["error"]["code"] == "MCP_CONNECTION_ERROR"
            assert result["error"]["numeric_code"] == 323

    @pytest.mark.asyncio
    async def test_reject_error_when_agent_not_found(
        self,
        server: ServeMCPServer,
    ) -> None:
        """Test that reject returns error envelope when agent not found."""
        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Failure(
                AgentNotFoundError("Agent not found: nonexistent")
            )

            result = await server._handle_reject("nonexistent", reason="test")

            assert "error" in result
            assert result["error"]["numeric_code"] == 300
            assert result["error"]["code"] == "AGENT_NOT_FOUND"
            assert result["acknowledged"] is False

    @pytest.mark.asyncio
    async def test_reject_error_when_not_awaiting_approval(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that reject returns INVALID_STATE when agent not awaiting approval."""
        # Create agent in running state (not awaiting_approval)
        sample_agent_state.state = AgentState.running
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            result = await server._handle_reject(sample_agent_state.instance_id)

            assert "error" in result
            assert result["error"]["numeric_code"] == 400
            assert result["error"]["code"] == "INVALID_STATE"
            assert result["acknowledged"] is False


class TestHandleResume:
    """Tests for _handle_resume using bridge and fallback."""

    @pytest.mark.asyncio
    async def test_resume_uses_bridge_for_alive_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that resume uses bridge (MCP call) for alive agents."""
        # Create agent state with socket (alive agent) in suspended state
        sample_agent_state.state = AgentState.suspended
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Mock bridge response
        mock_resume_response = {
            "instance_id": sample_agent_state.instance_id,
            "acknowledged": True,
        }

        with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
            mock_alive.return_value = True
            with patch(
                "anima.shell.mcp_serve.call_agent_mcp_tool_async"
            ) as mock_bridge:
                mock_bridge.return_value = Success(mock_resume_response)

                result = await server._handle_resume(sample_agent_state.instance_id)

                # Bridge should be called
                mock_bridge.assert_called_once()
                assert mock_bridge.call_args[0][0] == sample_agent_state.instance_id
                assert mock_bridge.call_args[0][1] == "anima.resume"
                # Result should contain the bridge response
                assert result["instance_id"] == sample_agent_state.instance_id
                assert result["acknowledged"] is True

    @pytest.mark.asyncio
    async def test_resume_fallback_for_dead_agent(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that resume falls back to direct state transition for dead agents."""
        # Create agent in suspended state (no socket)
        sample_agent_state.state = AgentState.suspended
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
            mock_alive.return_value = False
            with patch(
                "anima.shell.mcp_serve.build_shell_control_service"
            ) as mock_build_control:
                mock_control_service = MagicMock()
                mock_control_service.resume.return_value = Success(None)
                mock_build_control.return_value = mock_control_service

                result = await server._handle_resume(sample_agent_state.instance_id)

                # Fallback control service should be called
                mock_control_service.resume.assert_called_once()
                # Result should indicate success
                assert result["instance_id"] == sample_agent_state.instance_id
                assert result["acknowledged"] is True

    @pytest.mark.asyncio
    async def test_resume_does_not_fallback_when_bridge_fails_for_alive_agent(
        self,
        server: ServeMCPServer,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Bridge failure on alive suspended agent returns MCP error."""
        sample_agent_state.state = AgentState.suspended
        with (
            patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find,
            patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive,
            patch("anima.shell.mcp_serve.call_agent_mcp_tool_async") as mock_bridge,
            patch(
                "anima.shell.mcp_serve.build_shell_control_service"
            ) as mock_build_control,
        ):
            mock_control_service = MagicMock()
            mock_find.return_value = Success(sample_agent_state)
            mock_alive.return_value = True
            mock_bridge.return_value = Failure(MCPConnectionError("socket timeout"))
            mock_build_control.return_value = mock_control_service

            result = await server._handle_resume(sample_agent_state.instance_id)

            mock_control_service.resume.assert_not_called()
            assert result["acknowledged"] is False
            assert result["error"]["code"] == "MCP_CONNECTION_ERROR"
            assert result["error"]["numeric_code"] == 323

    @pytest.mark.asyncio
    async def test_resume_not_found_error(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
    ) -> None:
        """Test that resume returns AGENT_NOT_FOUND for nonexistent agents."""
        result = await server._handle_resume("nonexistent")

        assert "error" in result
        assert result["error"]["numeric_code"] == 300
        assert result["error"]["code"] == "AGENT_NOT_FOUND"
        assert result["acknowledged"] is False

    @pytest.mark.asyncio
    async def test_resume_invalid_state_when_not_suspended(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that resume returns INVALID_STATE when agent not suspended."""
        # Create agent in running state (not suspended)
        sample_agent_state.state = AgentState.running
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)

            result = await server._handle_resume(sample_agent_state.instance_id)

            assert "error" in result
            assert result["error"]["numeric_code"] == 400
            assert result["error"]["code"] == "INVALID_STATE"
            assert result["acknowledged"] is False

    @pytest.mark.asyncio
    async def test_resume_prefix_resolution(
        self,
        server: ServeMCPServer,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that resume correctly resolves agent by prefix."""
        # Create agent in suspended state with full ID
        sample_agent_state.state = AgentState.suspended
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        state_file.write_text(sample_agent_state.model_dump_json())

        # Try to resume using prefix (first 8 chars)
        prefix = sample_agent_state.instance_id[:8]

        with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
            mock_alive.return_value = False
            with patch(
                "anima.shell.mcp_serve.build_shell_control_service"
            ) as mock_build_control:
                mock_control_service = MagicMock()
                mock_control_service.resume.return_value = Success(None)
                mock_build_control.return_value = mock_control_service

                result = await server._handle_resume(prefix)

                # Should resolve prefix to full ID and succeed
                assert result["instance_id"] == sample_agent_state.instance_id
                assert result["acknowledged"] is True
                mock_control_service.resume.assert_called_once()


class TestHandleStreamBridge:
    """Tests for _handle_stream_bridge SSE proxy."""

    @pytest.fixture
    def mock_request(self) -> MagicMock:
        """Create a mock HTTP request."""
        request = MagicMock(spec=["path_params"])
        request.path_params = {}
        return request

    @pytest.mark.asyncio
    async def test_stream_bridge_missing_agent_id(
        self,
        server: ServeMCPServer,
        mock_request: MagicMock,
    ) -> None:
        """Test 400 response when agent_id is missing."""
        result = await server._handle_stream_bridge(mock_request)

        assert result.status_code == 400
        # Check it's a JSONResponse
        assert hasattr(result, "body")

    @pytest.mark.asyncio
    async def test_stream_bridge_agent_not_found(
        self,
        server: ServeMCPServer,
        mock_request: MagicMock,
    ) -> None:
        """Test 404 response when agent doesn't exist."""
        mock_request.path_params = {"agent_id": "nonexistent"}

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Failure(
                AgentNotFoundError("Agent not found: nonexistent")
            )

            result = await server._handle_stream_bridge(mock_request)

            assert result.status_code == 404

    @pytest.mark.asyncio
    async def test_stream_bridge_agent_not_alive(
        self,
        server: ServeMCPServer,
        mock_request: MagicMock,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test 404 response when agent is not alive."""
        mock_request.path_params = {"agent_id": sample_agent_state.instance_id}

        # Create agent state file but no socket (dead agent)
        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        agent_dir.mkdir(parents=True)
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.running
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)
            with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
                mock_alive.return_value = False  # Agent not alive

                result = await server._handle_stream_bridge(mock_request)

                assert result.status_code == 404

    @pytest.mark.asyncio
    async def test_stream_bridge_connection_failed(
        self,
        server: ServeMCPServer,
        mock_request: MagicMock,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test 502 response when agent socket connection fails."""
        mock_request.path_params = {"agent_id": sample_agent_state.instance_id}

        # Create agent state with socket (alive agent)
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.running
        state_file.write_text(sample_agent_state.model_dump_json())

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)
            with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
                mock_alive.return_value = True  # Agent alive
                with patch("anima.shell.mcp_serve.get_mcp_socket_path") as mock_socket:
                    mock_socket.return_value = socket_path
                    # Mock httpx.AsyncClient to raise connection error
                    with patch(
                        "anima.shell.mcp_serve.httpx.AsyncClient"
                    ) as mock_client:
                        mock_client.side_effect = httpx.ConnectError(
                            "Connection refused"
                        )

                        result = await server._handle_stream_bridge(mock_request)

                        assert result.status_code == 502

    @pytest.mark.asyncio
    async def test_stream_bridge_forwards_events(
        self,
        server: ServeMCPServer,
        mock_request: MagicMock,
        short_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Integration: bridge forwards real upstream SSE event/data frames in order."""
        mock_request.path_params = {"agent_id": sample_agent_state.instance_id}

        runner = MagicMock()
        runner.instance_id = sample_agent_state.instance_id
        upstream_server = AgentMCPServer(
            instance_id=sample_agent_state.instance_id,
            agent_runner=runner,
            interact_timeout_seconds=0.1,
        )

        start_result = await upstream_server.start()
        assert isinstance(start_result, Success)
        socket_path = upstream_server.socket_path
        assert socket_path is not None

        _write_running_state(
            state_dir=short_state_dir,
            state=sample_agent_state,
            socket_path=socket_path,
        )

        try:
            with patch("anima.shell.mcp_serve.is_agent_alive", return_value=True):
                result = await server._handle_stream_bridge(mock_request)

            collect_task = asyncio.create_task(_collect_sse_frames(result))
            await asyncio.sleep(0.05)

            await upstream_server.start_stream_session(turn_number=1)
            await upstream_server.enqueue_stream_tool_call(
                turn_number=1,
                tool_name="bash",
                tool_arg_preview="ls",
            )
            await upstream_server.enqueue_stream_chunk(turn_number=1, chunk="Hello")
            await upstream_server.enqueue_stream_done(
                turn_number=1,
                stop_reason="complete",
                final_response_chars=5,
                prompt_tokens=1,
                completion_tokens=2,
                total_tokens=3,
            )

            frames = await asyncio.wait_for(collect_task, timeout=2.0)

            assert [event for event, _ in frames] == ["tool_call", "chunk", "done"]

            parsed_payloads = [json.loads(data) for _, data in frames]
            assert [payload["seq"] for payload in parsed_payloads] == [1, 2, 3]
            assert parsed_payloads[0]["tool_name"] == "bash"
            assert parsed_payloads[1]["chunk"] == "Hello"
            assert parsed_payloads[2]["stop_reason"] == "complete"
            assert parsed_payloads[2]["done"] is True
            assert len(frames) == 3
        finally:
            stop_result = await upstream_server.stop()
            assert isinstance(stop_result, Success)


class TestStreamEndpointRuntime:
    """Runtime tests for /agent/{agent_id}/stream endpoint - happy and error paths."""

    @pytest.fixture
    def mock_request(self) -> MagicMock:
        """Create a mock HTTP request."""
        request = MagicMock(spec=["path_params"])
        request.path_params = {}
        return request

    @pytest.mark.asyncio
    async def test_stream_endpoint_happy_path_yields_sse_dicts(
        self,
        server: ServeMCPServer,
        mock_request: MagicMock,
        short_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Bridge completes after upstream terminal event without trailing events."""
        mock_request.path_params = {"agent_id": sample_agent_state.instance_id}

        runner = MagicMock()
        runner.instance_id = sample_agent_state.instance_id
        upstream_server = AgentMCPServer(
            instance_id=sample_agent_state.instance_id,
            agent_runner=runner,
            interact_timeout_seconds=0.1,
        )

        start_result = await upstream_server.start()
        assert isinstance(start_result, Success)
        socket_path = upstream_server.socket_path
        assert socket_path is not None

        _write_running_state(
            state_dir=short_state_dir,
            state=sample_agent_state,
            socket_path=socket_path,
        )

        try:
            with patch("anima.shell.mcp_serve.is_agent_alive", return_value=True):
                result = await server._handle_stream_bridge(mock_request)

            collect_task = asyncio.create_task(_collect_sse_frames(result))
            await asyncio.sleep(0.05)

            await upstream_server.start_stream_session(turn_number=7)
            await upstream_server.enqueue_stream_chunk(turn_number=7, chunk="A")
            await upstream_server.enqueue_stream_done(
                turn_number=7,
                stop_reason="complete",
                final_response_chars=1,
                prompt_tokens=4,
                completion_tokens=1,
                total_tokens=5,
            )
            await upstream_server.enqueue_stream_chunk(turn_number=7, chunk="IGNORED")

            frames = await asyncio.wait_for(collect_task, timeout=2.0)
            assert [event for event, _ in frames] == ["chunk", "done"]
            done_payload = json.loads(frames[-1][1])
            assert done_payload["stop_reason"] == "complete"
            assert done_payload["seq"] == 2
        finally:
            stop_result = await upstream_server.stop()
            assert isinstance(stop_result, Success)

    @pytest.mark.asyncio
    async def test_stream_endpoint_upstream_404_returns_502(
        self,
        server: ServeMCPServer,
        mock_request: MagicMock,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that 404 from upstream agent returns 502 Bad Gateway.

        This test patches at the client send() method level rather than
        the class level to exercise more of the real HTTP client construction
        and status code handling path.
        """
        mock_request.path_params = {"agent_id": sample_agent_state.instance_id}

        # Create agent state with socket
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.running
        state_file.write_text(sample_agent_state.model_dump_json())

        # Create mock HTTP response returning 404 - more realistic than pure client mock
        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_response.aiter_lines = MagicMock(return_value=iter([]))

        # Mock the client's send method directly to return a 404 response
        async def mock_send(request, stream=False):
            return mock_response

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)
            with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
                mock_alive.return_value = True
                with patch("anima.shell.mcp_serve.get_mcp_socket_path") as mock_socket:
                    mock_socket.return_value = socket_path
                    # Patch at client instance level - let client be created with real transport
                    with patch.object(httpx.AsyncClient, "send", mock_send):
                        result = await server._handle_stream_bridge(mock_request)

                        assert result.status_code == 502

    @pytest.mark.asyncio
    async def test_stream_endpoint_upstream_error_returns_502(
        self,
        server: ServeMCPServer,
        mock_request: MagicMock,
        tmp_state_dir: Path,
        sample_agent_state: AgentStateData,
    ) -> None:
        """Test that non-200 status from upstream returns 502 Bad Gateway.

        This test patches at the client send() method level rather than
        the class level to exercise more of the real HTTP client construction
        and status code handling path.
        """
        mock_request.path_params = {"agent_id": sample_agent_state.instance_id}

        # Create agent state with socket
        socket_path = (
            tmp_state_dir / "agents" / sample_agent_state.instance_id / "mcp.sock"
        )
        socket_path.parent.mkdir(parents=True)
        socket_path.touch()

        agent_dir = tmp_state_dir / "agents" / sample_agent_state.instance_id
        state_file = agent_dir / "state.json"
        sample_agent_state.state = AgentState.running
        state_file.write_text(sample_agent_state.model_dump_json())

        # Create mock HTTP response returning 500 - more realistic than pure client mock
        mock_response = MagicMock()
        mock_response.status_code = 500
        mock_response.aiter_lines = MagicMock(return_value=iter([]))

        # Mock the client's send method directly to return a 500 response
        async def mock_send(request, stream=False):
            return mock_response

        with patch("anima.shell.mcp_serve.find_agent_by_prefix") as mock_find:
            mock_find.return_value = Success(sample_agent_state)
            with patch("anima.shell.mcp_serve.is_agent_alive") as mock_alive:
                mock_alive.return_value = True
                with patch("anima.shell.mcp_serve.get_mcp_socket_path") as mock_socket:
                    mock_socket.return_value = socket_path
                    # Patch at client instance level - let client be created with real transport
                    with patch.object(httpx.AsyncClient, "send", mock_send):
                        result = await server._handle_stream_bridge(mock_request)

                        assert result.status_code == 502
