mirror of
https://github.com/usestrix/strix.git
synced 2026-08-18 17:52:32 +02:00
- Add new todo tool with create, list, update, mark_done, mark_pending, delete actions - Each subagent has isolated todo storage keyed by agent_id - Support bulk todo creation via JSON array or bullet list - Add TUI renderers for all todo actions with status markers - Update notes tool to remove priority and todo-related functionality - Add task tracking guidance to StrixAgent system prompt - Fix instruction file error handling in CLI
129 lines
4.2 KiB
Python
129 lines
4.2 KiB
Python
from typing import Any, ClassVar
|
|
|
|
from textual.widgets import Static
|
|
|
|
from .base_renderer import BaseToolRenderer
|
|
from .registry import register_tool_renderer
|
|
|
|
|
|
def _truncate(text: str, length: int = 800) -> str:
|
|
if len(text) <= length:
|
|
return text
|
|
return text[: length - 3] + "..."
|
|
|
|
|
|
@register_tool_renderer
|
|
class CreateNoteRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "create_note"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "notes-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
args = tool_data.get("args", {})
|
|
|
|
title = args.get("title", "")
|
|
content = args.get("content", "")
|
|
category = args.get("category", "general")
|
|
|
|
header = f"📝 [bold #fbbf24]Note[/] [dim]({category})[/]"
|
|
|
|
lines = [header]
|
|
if title:
|
|
title_display = _truncate(title.strip(), 300)
|
|
lines.append(f" {cls.escape_markup(title_display)}")
|
|
|
|
if content:
|
|
content_display = _truncate(content.strip(), 800)
|
|
lines.append(f" [dim]{cls.escape_markup(content_display)}[/]")
|
|
|
|
if len(lines) == 1:
|
|
lines.append(" [dim]Capturing...[/]")
|
|
|
|
css_classes = cls.get_css_classes("completed")
|
|
return Static("\n".join(lines), classes=css_classes)
|
|
|
|
|
|
@register_tool_renderer
|
|
class DeleteNoteRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "delete_note"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "notes-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: ARG003
|
|
header = "📝 [bold #94a3b8]Note Removed[/]"
|
|
content_text = header
|
|
|
|
css_classes = cls.get_css_classes("completed")
|
|
return Static(content_text, classes=css_classes)
|
|
|
|
|
|
@register_tool_renderer
|
|
class UpdateNoteRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "update_note"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "notes-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
args = tool_data.get("args", {})
|
|
|
|
title = args.get("title")
|
|
content = args.get("content")
|
|
|
|
header = "📝 [bold #fbbf24]Note Updated[/]"
|
|
lines = [header]
|
|
|
|
if title:
|
|
lines.append(f" {cls.escape_markup(_truncate(title, 300))}")
|
|
|
|
if content:
|
|
content_display = _truncate(content.strip(), 800)
|
|
lines.append(f" [dim]{cls.escape_markup(content_display)}[/]")
|
|
|
|
if len(lines) == 1:
|
|
lines.append(" [dim]Updating...[/]")
|
|
|
|
css_classes = cls.get_css_classes("completed")
|
|
return Static("\n".join(lines), classes=css_classes)
|
|
|
|
|
|
@register_tool_renderer
|
|
class ListNotesRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "list_notes"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "notes-tool"]
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
result = tool_data.get("result")
|
|
|
|
header = "📝 [bold #fbbf24]Notes[/]"
|
|
|
|
if result and isinstance(result, dict) and result.get("success"):
|
|
count = result.get("total_count", 0)
|
|
notes = result.get("notes", []) or []
|
|
lines = [header]
|
|
|
|
if count == 0:
|
|
lines.append(" [dim]No notes[/]")
|
|
else:
|
|
for note in notes[:5]:
|
|
title = note.get("title", "").strip() or "(untitled)"
|
|
category = note.get("category", "general")
|
|
content = note.get("content", "").strip()
|
|
|
|
lines.append(
|
|
f" - {cls.escape_markup(_truncate(title, 300))} [dim]({category})[/]"
|
|
)
|
|
if content:
|
|
content_preview = _truncate(content, 400)
|
|
lines.append(f" [dim]{cls.escape_markup(content_preview)}[/]")
|
|
|
|
remaining = max(count - 5, 0)
|
|
if remaining:
|
|
lines.append(f" [dim]... +{remaining} more[/]")
|
|
content_text = "\n".join(lines)
|
|
else:
|
|
content_text = f"{header}\n [dim]Loading...[/]"
|
|
|
|
css_classes = cls.get_css_classes("completed")
|
|
return Static(content_text, classes=css_classes)
|