mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-20 11:42:23 +02:00
100 lines
3.7 KiB
Python
100 lines
3.7 KiB
Python
"""spawn_agent: back the SpawnAgent MCP tool with a FRESH sub-agent session (no history
|
|
copy; the prompt must be self-contained). Replaces the CLI's built-in Agent tool, which is
|
|
blocked in RunOptions: its subagent types resolve to models router setups can't serve, and
|
|
its schema drags description/subagent_type/model/isolation along. Mixin, same MRO pattern
|
|
as AgentLaunch."""
|
|
|
|
import asyncio
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Dict, Optional
|
|
from uuid import uuid4
|
|
|
|
from typeguard import typechecked
|
|
|
|
from backend.apps.agents.core.models import AgentSession, Message
|
|
from backend.apps.agents.core.ws_manager import ws_manager
|
|
from backend.apps.agents.manager.AgentManagerProtocol import AgentManagerProtocol
|
|
from backend.apps.agents.manager.session.apply_context_window import apply_context_window
|
|
from backend.apps.agents.manager.session.session_store import load_session_data
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def last_assistant_text(session: AgentSession) -> Optional[str]:
|
|
for msg in reversed(session.messages):
|
|
if msg.role == "assistant":
|
|
content = msg.content
|
|
if isinstance(content, str):
|
|
return content
|
|
if isinstance(content, list):
|
|
texts = [b.get("text", "") for b in content if isinstance(b, dict) and b.get("type") == "text"]
|
|
return "\n".join(texts)
|
|
return str(content)
|
|
return None
|
|
|
|
|
|
class SpawnAgentRun(AgentManagerProtocol):
|
|
@typechecked
|
|
async def spawn_agent(
|
|
self,
|
|
prompt: str,
|
|
parent_session_id: str,
|
|
dashboard_id: Optional[str] = None,
|
|
run_in_background: bool = False,
|
|
) -> Dict:
|
|
parent = self.sessions.get(parent_session_id)
|
|
if not parent:
|
|
data = load_session_data(parent_session_id)
|
|
if data is None:
|
|
raise ValueError(f"Parent session {parent_session_id} not found")
|
|
parent = AgentSession(**data)
|
|
|
|
title = (prompt.strip().splitlines() or [""])[0][:60] or "Sub-agent"
|
|
child = AgentSession(
|
|
id=uuid4().hex,
|
|
name=title,
|
|
status="running",
|
|
model=parent.model,
|
|
mode="sub-agent",
|
|
system_prompt=parent.system_prompt,
|
|
allowed_tools=list(parent.allowed_tools),
|
|
max_turns=parent.max_turns or 25,
|
|
cwd=parent.cwd,
|
|
created_at=datetime.now(),
|
|
dashboard_id=dashboard_id or parent.dashboard_id,
|
|
parent_session_id=parent_session_id,
|
|
)
|
|
apply_context_window(child)
|
|
self.sessions[child.id] = child
|
|
|
|
await ws_manager.broadcast_global("agent:status", {
|
|
"session_id": child.id,
|
|
"status": child.status,
|
|
"session": child.model_dump(mode="json"),
|
|
})
|
|
|
|
user_msg = Message(
|
|
role="user",
|
|
content=prompt,
|
|
branch_id=child.active_branch_id,
|
|
)
|
|
child.messages.append(user_msg)
|
|
await ws_manager.send_to_session(child.id, "agent:message", {
|
|
"session_id": child.id,
|
|
"message": user_msg.model_dump(mode="json"),
|
|
})
|
|
|
|
if run_in_background:
|
|
# Fire-and-forget; the child's card carries its progress and result. Keep a handle in self.tasks so stop/shutdown machinery sees it.
|
|
task = asyncio.create_task(self.run_agent_loop(child.id, prompt))
|
|
self.register_turn_task(child.id, task)
|
|
return {"session_id": child.id, "background": True}
|
|
|
|
await self.run_agent_loop(child.id, prompt)
|
|
return {
|
|
"session_id": child.id,
|
|
"response": last_assistant_text(child) or "No response from sub-agent.",
|
|
"cost_usd": child.cost_usd,
|
|
}
|