4f62adf820
- view_agent_graph status summary now derives buckets from the canonical
Status literal via get_args, so adding a new status in core.agents
auto-flows into the summary. The previous hardcoded five-bucket list
silently omitted "failed" — buckets stopped summing to total whenever
an agent failed.
- stop_agent rejects targets that are already in a terminal status
(completed / stopped / crashed / failed) with a model-readable error
pointing at view_agent_graph and send_message_to_agent. request_stop
unconditionally overwrites status, so without this guard calling
stop_agent on a completed agent erased the "completed" history.
- StopAgentRenderer added — was falling back to the generic key/value
renderer; the rest of the agents_graph tools have purpose-built ones.
- agent_finish root-rejection payload trimmed from
{success, agent_completed, error, parent_notified} to {success, error}.
The lifecycle gate only reads success+agent_completed and they were
always False/False on this branch, so the extra fields were dead weight.
- wait_for_message renames its top-level outcome field from "status" to
"wait_outcome" — "status" overloaded with the coordinator's agent
status literal (which also has "stopped" as a value, different
meaning). Redundant "agent_waiting" boolean dropped (true iff
wait_outcome == "waiting"). Consumer at factory._wait_tool_parked
updated to match.
- send_message_to_agent now refuses self-send with a pointer at think /
agent_finish / finish_scan instead of looping a message into your
own session.
- SendMessageToAgentRenderer read args.get("agent_id") but the tool's
param is target_agent_id, so the TUI silently never showed the target.
Fixed.
- Restored skill validation lost during the SDK migration: skills
module re-exports get_all_skill_names and validate_requested_skills
(excluding internal scan_modes/coordination categories from the
user-selectable set). create_agent now validates skills before
spawning instead of silently accepting unknown names.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
176 lines
5.5 KiB
Python
176 lines
5.5 KiB
Python
from typing import Any, ClassVar
|
|
|
|
from rich.text import Text
|
|
from textual.widgets import Static
|
|
|
|
from .base_renderer import BaseToolRenderer
|
|
from .registry import register_tool_renderer
|
|
|
|
|
|
@register_tool_renderer
|
|
class ViewAgentGraphRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "view_agent_graph"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
status = tool_data.get("status", "unknown")
|
|
|
|
text = Text()
|
|
text.append("◇ ", style="#a78bfa")
|
|
text.append("viewing agents graph", style="dim")
|
|
|
|
css_classes = cls.get_css_classes(status)
|
|
return Static(text, classes=css_classes)
|
|
|
|
|
|
@register_tool_renderer
|
|
class CreateAgentRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "create_agent"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
args = tool_data.get("args", {})
|
|
status = tool_data.get("status", "unknown")
|
|
|
|
task = args.get("task", "")
|
|
name = args.get("name", "Agent")
|
|
|
|
text = Text()
|
|
text.append("◈ ", style="#a78bfa")
|
|
text.append("spawning ", style="dim")
|
|
text.append(name, style="bold #a78bfa")
|
|
|
|
if task:
|
|
text.append("\n ")
|
|
text.append(task, style="dim")
|
|
|
|
css_classes = cls.get_css_classes(status)
|
|
return Static(text, classes=css_classes)
|
|
|
|
|
|
@register_tool_renderer
|
|
class SendMessageToAgentRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "send_message_to_agent"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
args = tool_data.get("args", {})
|
|
status = tool_data.get("status", "unknown")
|
|
|
|
message = args.get("message", "")
|
|
target_agent_id = args.get("target_agent_id", "")
|
|
|
|
text = Text()
|
|
text.append("→ ", style="#60a5fa")
|
|
if target_agent_id:
|
|
text.append(f"to {target_agent_id}", style="dim")
|
|
else:
|
|
text.append("sending message", style="dim")
|
|
|
|
if message:
|
|
text.append("\n ")
|
|
text.append(message, style="dim")
|
|
|
|
css_classes = cls.get_css_classes(status)
|
|
return Static(text, classes=css_classes)
|
|
|
|
|
|
@register_tool_renderer
|
|
class AgentFinishRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "agent_finish"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
args = tool_data.get("args", {})
|
|
|
|
result_summary = args.get("result_summary", "")
|
|
findings = args.get("findings", [])
|
|
success = args.get("success", True)
|
|
|
|
text = Text()
|
|
|
|
if success:
|
|
text.append("◆ ", style="#22c55e")
|
|
text.append("Agent completed", style="bold #22c55e")
|
|
else:
|
|
text.append("◆ ", style="#ef4444")
|
|
text.append("Agent failed", style="bold #ef4444")
|
|
|
|
if result_summary:
|
|
text.append("\n ")
|
|
text.append(result_summary, style="bold")
|
|
|
|
if findings and isinstance(findings, list):
|
|
for finding in findings:
|
|
text.append("\n • ")
|
|
text.append(str(finding), style="dim")
|
|
else:
|
|
text.append("\n ")
|
|
text.append("Completing task...", style="dim")
|
|
|
|
css_classes = cls.get_css_classes("completed")
|
|
return Static(text, classes=css_classes)
|
|
|
|
|
|
@register_tool_renderer
|
|
class WaitForMessageRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "wait_for_message"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
args = tool_data.get("args", {})
|
|
status = tool_data.get("status", "unknown")
|
|
|
|
reason = args.get("reason", "")
|
|
|
|
text = Text()
|
|
text.append("○ ", style="#6b7280")
|
|
text.append("waiting", style="dim")
|
|
|
|
if reason:
|
|
text.append("\n ")
|
|
text.append(reason, style="dim")
|
|
|
|
css_classes = cls.get_css_classes(status)
|
|
return Static(text, classes=css_classes)
|
|
|
|
|
|
@register_tool_renderer
|
|
class StopAgentRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "stop_agent"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
args = tool_data.get("args", {})
|
|
result = tool_data.get("result")
|
|
status = tool_data.get("status", "unknown")
|
|
|
|
target_agent_id = args.get("target_agent_id", "")
|
|
cascade = args.get("cascade", True)
|
|
reason = args.get("reason", "")
|
|
|
|
text = Text()
|
|
text.append("◼ ", style="#ef4444")
|
|
text.append("stopping", style="dim")
|
|
if target_agent_id:
|
|
text.append(f" {target_agent_id}", style="bold #ef4444")
|
|
if cascade:
|
|
text.append(" + descendants", style="dim italic")
|
|
|
|
if reason:
|
|
text.append("\n ")
|
|
text.append(reason, style="dim")
|
|
|
|
if isinstance(result, dict) and result.get("success") is False and result.get("error"):
|
|
text.append("\n ")
|
|
text.append(str(result["error"]), style="#ef4444")
|
|
|
|
css_classes = cls.get_css_classes(status)
|
|
return Static(text, classes=css_classes)
|