from __future__ import annotations import logging from dataclasses import dataclass, field from typing import Any logger = logging.getLogger(__name__) @dataclass class SubAgentLifecycle: agent_id: str parent_chat_id: str status: str = "pending" created_at: float = 0.0 completed_at: float | None = None result: dict[str, Any] = field(default_factory=dict) error: str = "" class FeishuSubAgentManager: def __init__(self, max_concurrent: int = 10): self._sub_agents: dict[str, SubAgentLifecycle] = {} self._max_concurrent = max_concurrent def start_sub_agent( self, agent_id: str, parent_chat_id: str, *, metadata: dict[str, Any] | None = None, ) -> SubAgentLifecycle: if len(self._sub_agents) >= self._max_concurrent: oldest = next(iter(self._sub_agents)) self._sub_agents.pop(oldest) logger.info("[FeishuSubAgent] Evicted oldest sub-agent: %s", oldest) import time lifecycle = SubAgentLifecycle( agent_id=agent_id, parent_chat_id=parent_chat_id, status="active", created_at=time.monotonic(), result=metadata or {}, ) self._sub_agents[agent_id] = lifecycle logger.info("[FeishuSubAgent] Started sub-agent %s for chat %s", agent_id, parent_chat_id) return lifecycle def complete_sub_agent(self, agent_id: str, *, result: dict[str, Any] | None = None) -> SubAgentLifecycle | None: lifecycle = self._sub_agents.get(agent_id) if lifecycle is None: logger.warning("[FeishuSubAgent] Sub-agent %s not found for completion", agent_id) return None import time lifecycle.status = "completed" lifecycle.completed_at = time.monotonic() if result: lifecycle.result.update(result) logger.info("[FeishuSubAgent] Completed sub-agent %s", agent_id) return lifecycle def fail_sub_agent(self, agent_id: str, error: str) -> SubAgentLifecycle | None: lifecycle = self._sub_agents.get(agent_id) if lifecycle is None: return None lifecycle.status = "failed" lifecycle.error = error logger.warning("[FeishuSubAgent] Sub-agent %s failed: %s", agent_id, error) return lifecycle def end_sub_agent(self, agent_id: str, *, result: dict[str, Any] | None = None) -> SubAgentLifecycle | None: lifecycle = self._sub_agents.pop(agent_id, None) if lifecycle is None: return None import time lifecycle.completed_at = time.monotonic() lifecycle.status = "ended" if result: lifecycle.result.update(result) logger.info("[FeishuSubAgent] Ended sub-agent %s", agent_id) return lifecycle def get_sub_agent(self, agent_id: str) -> SubAgentLifecycle | None: return self._sub_agents.get(agent_id) def list_active(self) -> list[SubAgentLifecycle]: return [s for s in self._sub_agents.values() if s.status == "active"] def list_by_parent(self, parent_chat_id: str) -> list[SubAgentLifecycle]: return [s for s in self._sub_agents.values() if s.parent_chat_id == parent_chat_id] def clean_completed(self, max_age_s: float = 3600) -> int: import time now = time.monotonic() to_remove = [ aid for aid, s in self._sub_agents.items() if s.status in ("completed", "failed", "ended") and s.completed_at and (now - s.completed_at) > max_age_s ] for aid in to_remove: self._sub_agents.pop(aid, None) if to_remove: logger.info("[FeishuSubAgent] Cleaned %d completed sub-agents", len(to_remove)) return len(to_remove) def clear(self) -> None: self._sub_agents.clear()