Newer
Older
navi-1 / clients / terminal / tui / renderers / subagent.py
"""Renderers for the spawn_agent tool (sub-agent delegation cards)."""

from __future__ import annotations

from rich.box import ROUNDED
from rich.console import RenderableType
from rich.panel import Panel
from rich.text import Text

from clients.terminal.tui.themes import get_active_theme

from .base import ContentRenderer

_SPAWN_TOOL = "spawn_agent"


def _truncate(text: str, limit: int) -> str:
    text = (text or "").strip()
    if len(text) <= limit:
        return text
    return text[:limit].rstrip() + " …"


class SpawnAgentStartedRenderer(ContentRenderer):
    """Render the start of a spawn_agent call as a distinct sub-agent card."""

    def accepts(self, msg: dict) -> bool:
        return msg.get("type") == "tool_started" and msg.get("tool") == _SPAWN_TOOL

    def render(self, msg: dict) -> RenderableType:
        theme = get_active_theme()
        args = msg.get("args") or {}
        profile_id = args.get("profile_id") or "default"
        task = args.get("task") or ""
        briefing = args.get("briefing") or ""

        title = f"↘ subagent · {profile_id}"
        body = Text(_truncate(task, 240), style=theme.text.hex)
        if briefing:
            body.append("\n", style=theme.text_dim.hex)
            body.append(f"briefing: {_truncate(briefing, 160)}", style=theme.text_dim.hex)
        return Panel(
            body,
            title=title,
            title_align="left",
            border_style=theme.secondary.hex,
            box=ROUNDED,
        )


class SpawnAgentResultRenderer(ContentRenderer):
    """Render the result of a spawn_agent call as a distinct sub-agent card."""

    def accepts(self, msg: dict) -> bool:
        return msg.get("type") == "tool_call" and msg.get("tool") == _SPAWN_TOOL

    def render(self, msg: dict) -> RenderableType:
        theme = get_active_theme()
        args = msg.get("args") or {}
        profile_id = args.get("profile_id") or "default"
        success = msg.get("success", True)
        result = msg.get("result")
        color = theme.tool_success if success else theme.tool_error
        title = f"↙ subagent · {profile_id} {'✓' if success else '✗'}"
        body = Text(_truncate(str(result) if result is not None else "", 600), style=theme.text_dim.hex)
        return Panel(
            body,
            title=title,
            title_align="left",
            border_style=color.hex,
            box=ROUNDED,
        )