"""The no-SDK mock agent loop, split out of AgentLaunch so each file is one concern. This fires only when claude_agent_sdk isn't installed (dev fallback): it fakes one Bash approval + tool-result + assistant reply so the UI is exercisable without a real model. self.p_stream_* / self.sessions resolve across the MRO exactly as when this lived on AgentLaunch.""" import asyncio import json import logging from datetime import datetime from uuid import uuid4 from typeguard import typechecked from backend.apps.agents.core.models import ApprovalRequest, Message from backend.apps.agents.core.ws_manager import ws_manager logger = logging.getLogger(__name__) from backend.apps.agents.manager.AgentManagerProtocol import AgentManagerProtocol class MockAgent(AgentManagerProtocol): @typechecked async def run_mock_agent(self, session_id: str, prompt: str): """Mock agent loop for development without claude_agent_sdk installed.""" session = self.sessions.get(session_id) if not session: return await asyncio.sleep(1) request_id = uuid4().hex approval_req = ApprovalRequest( id=request_id, session_id=session_id, tool_name="Bash", tool_input={"command": f"echo 'Processing: {prompt}'", "description": "Echo the user prompt"}, ) session.pending_approvals.append(approval_req) session.status = "waiting_approval" await ws_manager.send_to_session(session_id, "agent:status", { "session_id": session_id, "status": "waiting_approval", }) decision = await ws_manager.send_approval_request( session_id, request_id, "Bash", {"command": f"echo 'Processing: {prompt}'", "description": "Echo the user prompt"} ) session.pending_approvals = [a for a in session.pending_approvals if a.id != request_id] session.status = "running" await ws_manager.send_to_session(session_id, "agent:status", { "session_id": session_id, "status": "running", }) tool_input_content = {"tool": "Bash", "input": {"command": f"echo 'Processing: {prompt}'"}, "approved": decision.get("behavior") == "allow"} tool_msg_id = uuid4().hex await self.stream_tool_input( session_id, tool_msg_id, "Bash", json.dumps(tool_input_content["input"], indent=2), ) tool_msg = Message(id=tool_msg_id, role="tool_call", content=tool_input_content, branch_id=session.active_branch_id) session.messages.append(tool_msg) await ws_manager.send_to_session(session_id, "agent:message", { "session_id": session_id, "message": tool_msg.model_dump(mode="json"), }) await asyncio.sleep(1) if decision.get("behavior") == "allow": tool_result = Message(role="tool_result", content=f"Processing: {prompt}", branch_id=session.active_branch_id) session.messages.append(tool_result) await ws_manager.send_to_session(session_id, "agent:message", { "session_id": session_id, "message": tool_result.model_dump(mode="json"), }) await asyncio.sleep(1) asst_text = ( f"I've processed your request: \"{prompt}\"\n\n" "This is a mock response because `claude-agent-sdk` is not installed. " "Install it with `pip install claude-agent-sdk` to use real Claude Code instances.\n\n" f"The agent was configured with:\n- Model: {session.model}\n- Mode: {session.mode}" ) asst_msg_id = uuid4().hex await self.stream_text(session_id, asst_msg_id, asst_text) asst_msg = Message(id=asst_msg_id, role="assistant", content=asst_text, branch_id=session.active_branch_id) session.messages.append(asst_msg) await ws_manager.send_to_session(session_id, "agent:message", { "session_id": session_id, "message": asst_msg.model_dump(mode="json"), }) session.status = "completed" session.closed_at = datetime.now() # Mock branch (claude_agent_sdk missing): leave cost untouched so it stays at its 0.0 default. A fake nonzero value here would poison the cost shown in the session header during dev. The `_mock_run` flag is read by the close path so a mock session doesn't get reported to the cloud as a real one. setattr(session, "_mock_run", True) await ws_manager.send_to_session(session_id, "agent:status", { "session_id": session_id, "status": "completed", "session": session.model_dump(mode="json"), }) await ws_manager.send_to_session(session_id, "agent:cost_update", { "session_id": session_id, "cost_usd": session.cost_usd, })