diff --git a/backend/apps/agents/manager/HaikFix/Agent.py b/backend/apps/agents/manager/HaikFix/Agent.py index 9dd0e10a..ce0d39e6 100644 --- a/backend/apps/agents/manager/HaikFix/Agent.py +++ b/backend/apps/agents/manager/HaikFix/Agent.py @@ -1,38 +1,19 @@ -"""Thin coordinator for agent sessions. +# TODO: NON HAIK DEPS: ws_manager -Heavy logic lives in sibling modules: -- agent_manager_ops – edit, close, resume, duplicate, invoke, LLM metadata -- agent_loop – the SDK query loop, streaming, mock agent -- agent_mock – session-completed analytics -- prompt_builder – system-prompt composition & context injection -- mcp_builder – MCP server construction & tool-policy helpers -- session_store – on-disk persistence, history, message copying -""" - -from __future__ import annotations - -import asyncio -import logging -import os -from datetime import datetime from uuid import uuid4 +import asyncio +import os from claude_agent_sdk import ClaudeAgentOptions -from pydantic import BaseModel, InstanceOf +from pydantic import BaseModel, Field, InstanceOf from typing import List, Literal, Optional from typeguard import typechecked -from backend.apps.agents.models import AgentSession, AgentConfig, ApprovalRequest from backend.apps.agents.manager.ws_manager import ws_manager -from backend.apps.agents.execution.prompt_builder import resolve_mode -from backend.apps.agents.execution.mcp_builder import get_all_tool_names -from backend.apps.settings.settings import load_settings -from backend.apps.agents.manager.HaikFix.agent_loop import run_agent_loop -from backend.apps.agents.manager.HaikFix.helpers.Message import Message -from backend.apps.agents.manager.HaikFix.PromptChunks import ImageChunk - - -logger = logging.getLogger(__name__) +from backend.apps.agents.manager.HaikFix.run_agent_loop.run_agent_loop import run_agent_loop +from backend.apps.agents.manager.HaikFix.shared_structs.Message import Message +from backend.apps.agents.manager.HaikFix.shared_structs.PromptChunks import ImageChunk +from backend.apps.agents.manager.HaikFix.shared_structs.ApprovalRequest import ApprovalRequest os.environ.setdefault("CLAUDE_CODE_STREAM_CLOSE_TIMEOUT", "3600000") @@ -48,66 +29,25 @@ class Skill(BaseModel): class Agent(BaseModel): model: str mode: str - session_id: str status: Literal["running", "waiting_approval", "completed", "error", "stopped"] - lock: InstanceOf[asyncio.Lock] + pending_approvals: List[ApprovalRequest] = Field(default_factory=list) + + session_id: str = Field(default_factory=lambda: uuid4().hex) config: ClaudeAgentOptions - session: Optional[AgentSession] = None + branch_id: str = "main" + children: List["Agent"] = Field(default_factory=list) parent_id: Optional[str] = None + task: Optional[asyncio.Task] = None + lock: InstanceOf[asyncio.Lock] = Field(default_factory=asyncio.Lock) @typechecked - def __init__( - self, - model: str, - mode: str, - tools: List[str], - effective_cwd: str, - config: ClaudeAgentOptions, - ) -> None: - id: str = uuid4().hex - lock: asyncio.Lock = asyncio.Lock() - super().__init__( - model=model, - mode=mode, - tools=tools, - effective_cwd=effective_cwd, - status="running", - id=id, - task=None, - lock=lock, - config=config, - ) - - async def launch_agent(self, config: AgentConfig) -> AgentSession: - session_id = uuid4().hex - mode_tools, _, mode_folder = resolve_mode(config.mode, get_all_tool_names) - global_settings = load_settings() - effective_cwd = ( - config.target_directory or mode_folder - or global_settings.default_folder or os.path.expanduser("~") - ) - if config.mode in ("view-builder", "skill-builder") and not config.target_directory: - effective_cwd = os.path.join(effective_cwd, session_id) - os.makedirs(effective_cwd, exist_ok=True) - self.session = AgentSession( - id=session_id, name=config.name, - provider=getattr(config, "provider", "anthropic"), - model=config.model, mode=config.mode, - system_prompt=config.system_prompt, allowed_tools=mode_tools, - max_turns=config.max_turns, cwd=effective_cwd, - dashboard_id=config.dashboard_id, - ) - self.session_id = session_id - await ws_manager.emit_status(session_id, "running", self.session) - return self.session - async def send_message( self, prompt: str, images: Optional[List[ImageChunk]] = None, - ): + ) -> None: async with self.lock: if self.task is not None and not self.task.done(): print("[Agent.send_message] Agent is already running") @@ -133,21 +73,22 @@ class Agent(BaseModel): parent_id=self.parent_id, )) + @typechecked async def stop_agent(self): + for child in self.children: + await child.stop_agent() + if self.task and not self.task.done(): self.task.cancel() try: await self.task except asyncio.CancelledError: pass - if self.session: - for req in list[ApprovalRequest](self.session.pending_approvals): - ws_manager.resolve_approval(req.id, {"behavior": "deny", "message": "Agent stopped"}) - self.session.pending_approvals = [] - if hasattr(self.session, '_cancel_event'): - self.session._cancel_event.set() - self.session.status = "stopped" - if not self.session.closed_at: - self.session.closed_at = datetime.now() - await ws_manager.emit_status(self.session.id, "stopped", self.session) + + # if self.session: + for req in list[ApprovalRequest](self.pending_approvals): + ws_manager.resolve_approval(req.id, {"behavior": "deny", "message": "Agent stopped"}) + self.pending_approvals = [] + self.status = "stopped" + await ws_manager.emit_status(self.session.id, "stopped", self.session) \ No newline at end of file diff --git a/backend/apps/agents/manager/HaikFix/helpers/handle_assistant_message.py b/backend/apps/agents/manager/HaikFix/run_agent_loop/helpers/handle_assistant_message.py similarity index 90% rename from backend/apps/agents/manager/HaikFix/helpers/handle_assistant_message.py rename to backend/apps/agents/manager/HaikFix/run_agent_loop/helpers/handle_assistant_message.py index ecc41360..492cd1e2 100644 --- a/backend/apps/agents/manager/HaikFix/helpers/handle_assistant_message.py +++ b/backend/apps/agents/manager/HaikFix/run_agent_loop/helpers/handle_assistant_message.py @@ -1,4 +1,4 @@ -from backend.apps.agents.manager.HaikFix.helpers.Message import Message, ToolCallContent +from backend.apps.agents.manager.HaikFix.shared_structs.Message import Message, ToolCallContent from claude_agent_sdk.types import TextBlock, ToolUseBlock, AssistantMessage from backend.apps.agents.manager.ws_manager import ws_manager from typeguard import typechecked diff --git a/backend/apps/agents/manager/HaikFix/helpers/handle_stream_event.py b/backend/apps/agents/manager/HaikFix/run_agent_loop/helpers/handle_stream_event.py similarity index 100% rename from backend/apps/agents/manager/HaikFix/helpers/handle_stream_event.py rename to backend/apps/agents/manager/HaikFix/run_agent_loop/helpers/handle_stream_event.py diff --git a/backend/apps/agents/manager/HaikFix/agent_loop.py b/backend/apps/agents/manager/HaikFix/run_agent_loop/run_agent_loop.py similarity index 76% rename from backend/apps/agents/manager/HaikFix/agent_loop.py rename to backend/apps/agents/manager/HaikFix/run_agent_loop/run_agent_loop.py index c331ced2..6300615a 100644 --- a/backend/apps/agents/manager/HaikFix/agent_loop.py +++ b/backend/apps/agents/manager/HaikFix/run_agent_loop/run_agent_loop.py @@ -1,12 +1,3 @@ -"""Main agent loop — orchestrates the Claude Agent SDK query loop. - -Heavy logic is delegated to sibling modules: -- agent_mock – mock-agent fallback, streaming helpers, session analytics -- agent_hooks – SDK hook factories (approval, permissions, post-tool) -- agent_options – MCP server construction & ClaudeAgentOptions building -""" - -from __future__ import annotations import logging from typeguard import typechecked @@ -14,15 +5,14 @@ from claude_agent_sdk import ( query, ClaudeAgentOptions, AssistantMessage, ) from claude_agent_sdk.types import StreamEvent -from backend.apps.agents.manager.HaikFix.helpers.handle_stream_event import handle_stream_event -from backend.apps.agents.manager.HaikFix.helpers.handle_assistant_message import handle_assistant_message +from backend.apps.agents.manager.HaikFix.run_agent_loop.helpers.handle_stream_event import handle_stream_event +from backend.apps.agents.manager.HaikFix.run_agent_loop.helpers.handle_assistant_message import handle_assistant_message -from backend.apps.agents.manager.HaikFix.PromptChunks import ImageChunk, ImageChunkDict, TextChunk, TextChunkDict +from backend.apps.agents.manager.HaikFix.shared_structs.PromptChunks import ( + ImageChunk, ImageChunkDict, TextChunk, TextChunkDict +) from typing import List, Dict, Literal, Union, Optional -logger = logging.getLogger(__name__) - - @typechecked def build_image_prompt_content(prompt: str, images: List[ImageChunk]) -> List[TextChunk | ImageChunk]: content: List[Union[ImageChunkDict, TextChunkDict]] = [TextChunk(text=prompt).to_dict()] diff --git a/backend/apps/agents/manager/HaikFix/shared_structs/ApprovalRequest.py b/backend/apps/agents/manager/HaikFix/shared_structs/ApprovalRequest.py new file mode 100644 index 00000000..882161e2 --- /dev/null +++ b/backend/apps/agents/manager/HaikFix/shared_structs/ApprovalRequest.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel, Field +from typing import Any +from datetime import datetime +from uuid import uuid4 + +class ApprovalRequest(BaseModel): + id: str = Field(default_factory=lambda: uuid4().hex) + session_id: str + tool_name: str + tool_input: dict[str, Any] + created_at: datetime = Field(default_factory=datetime.now) diff --git a/backend/apps/agents/manager/HaikFix/helpers/Message.py b/backend/apps/agents/manager/HaikFix/shared_structs/Message.py similarity index 96% rename from backend/apps/agents/manager/HaikFix/helpers/Message.py rename to backend/apps/agents/manager/HaikFix/shared_structs/Message.py index 556f515b..8e07b5cd 100644 --- a/backend/apps/agents/manager/HaikFix/helpers/Message.py +++ b/backend/apps/agents/manager/HaikFix/shared_structs/Message.py @@ -1,5 +1,5 @@ +from typing import Optional, Literal, Union, List from pydantic import BaseModel, Field -from typing import Optional, Literal, Union, List, Dict from datetime import datetime from uuid import uuid4 diff --git a/backend/apps/agents/manager/HaikFix/PromptChunks.py b/backend/apps/agents/manager/HaikFix/shared_structs/PromptChunks.py similarity index 100% rename from backend/apps/agents/manager/HaikFix/PromptChunks.py rename to backend/apps/agents/manager/HaikFix/shared_structs/PromptChunks.py