Files
usestrix__strix/strix/interface/tui/renderers/agents_graph_renderer.py
T

176 lines
5.5 KiB
Python
Raw Normal View History

2025-08-08 20:36:44 -07:00
from typing import Any, ClassVar
from rich.text import Text
2025-08-08 20:36:44 -07:00
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")
2025-08-08 20:36:44 -07:00
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)
2025-08-08 20:36:44 -07:00
@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")
2025-08-08 20:36:44 -07:00
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")
2025-08-08 20:36:44 -07:00
if task:
text.append("\n ")
text.append(task, style="dim")
2025-08-08 20:36:44 -07:00
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)
2025-08-08 20:36:44 -07:00
@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")
2025-08-08 20:36:44 -07:00
message = args.get("message", "")
Agents graph sweep: status taxonomy, stop_agent safety, skill validation - 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>
2026-05-25 01:35:57 -07:00
target_agent_id = args.get("target_agent_id", "")
2025-08-08 20:36:44 -07:00
text = Text()
text.append("", style="#60a5fa")
Agents graph sweep: status taxonomy, stop_agent safety, skill validation - 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>
2026-05-25 01:35:57 -07:00
if target_agent_id:
text.append(f"to {target_agent_id}", style="dim")
else:
text.append("sending message", style="dim")
2025-08-08 20:36:44 -07:00
if message:
text.append("\n ")
text.append(message, style="dim")
2025-08-08 20:36:44 -07:00
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)
2025-08-08 20:36:44 -07:00
@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")
2025-08-08 20:36:44 -07:00
if result_summary:
text.append("\n ")
text.append(result_summary, style="bold")
2025-08-08 20:36:44 -07:00
if findings and isinstance(findings, list):
for finding in findings:
text.append("\n")
text.append(str(finding), style="dim")
2025-08-08 20:36:44 -07:00
else:
text.append("\n ")
text.append("Completing task...", style="dim")
2025-08-08 20:36:44 -07:00
css_classes = cls.get_css_classes("completed")
return Static(text, classes=css_classes)
2025-08-08 20:36:44 -07:00
@register_tool_renderer
class WaitForAgentsRenderer(BaseToolRenderer):
tool_name: ClassVar[str] = "wait_for_agents"
2025-08-08 20:36:44 -07:00
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")
2025-08-08 20:36:44 -07:00
reason = args.get("reason", "")
2025-08-08 20:36:44 -07:00
text = Text()
text.append("", style="#6b7280")
text.append("waiting", style="dim")
2025-08-08 20:36:44 -07:00
if reason:
text.append("\n ")
text.append(reason, style="dim")
2025-08-08 20:36:44 -07:00
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)
Agents graph sweep: status taxonomy, stop_agent safety, skill validation - 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>
2026-05-25 01:35:57 -07:00
@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)