Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions src/toad/acp/comms_updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from __future__ import annotations

from agent_comms.errors import RouteChangedError

from dataclasses import dataclass
from typing import Any

Expand All @@ -16,8 +14,8 @@
InputDeliveryChangedUpdate,
InputFailedUpdate,
InputStartedUpdate,
PromptCancelledUpdate,
McpClientAcknowledgementUpdate,
PromptCancelledUpdate,
QueueChangedUpdate,
RequestFailedUpdate,
SelectedWriteAcceptedUpdate,
Expand All @@ -27,11 +25,12 @@
TurnChangedUpdate,
)
from agent_comms.context_reading import UnmeasuredContext
from agent_comms.errors import RouteChangedError
from agent_comms.mro_dispatch import MroDispatch, handles
from agent_comms.thread_presentation import ThreadPresentation

from toad.acp.client_session import ClientSessionRequest
from toad.acp.context_measurement import ContextMeasurement

from toad.core import events as core_events


Expand All @@ -43,15 +42,15 @@ def __init__(
*,
cursor_token: int | None = None,
queue_token: int | None = None,
):
) -> None:
self.agent = agent
self.session_id = session_id
self.route = None
self.compaction_update = None
self.cursor_token = cursor_token
self.queue_token = queue_token

async def consume_metadata(self, metadata):
async def consume_metadata(self, metadata) -> None:
from .sdk_boundary import DecodeCommsMetadataTask

authority = ClientSessionRequest(self.agent, self.session_id)
Expand Down Expand Up @@ -173,11 +172,11 @@ def mcp_status(self, update: McpClientAcknowledgementUpdate) -> None:
self.agent.events.publish(core_events.CommsUpdated(update, self.session_id))

@handles(SelectedWriteAcceptedUpdate)
def selected_write_accepted(self, update):
def selected_write_accepted(self, update) -> None:
pass

@handles(RequestFailedUpdate)
def request_failed(self, update):
def request_failed(self, update) -> None:
from toad.core.events import RequestAgentFail

# One request failed; the session that carried it is still open.
Expand All @@ -186,14 +185,14 @@ def request_failed(self, update):
)

@handles(PromptCancelledUpdate)
def prompt_cancelled(self, update):
def prompt_cancelled(self, update) -> None:
self.agent.events.publish(core_events.CommsUpdated(update, self.session_id))


class OwnerSnapshotConsumer(CommsUpdateConsumer):
"""Canonical reads reconcile a turn without overtaking this client's request."""

def __init__(self, agent, session_id, *, turn_token):
def __init__(self, agent, session_id, *, turn_token) -> None:
super().__init__(agent, session_id)
self.turn_token = turn_token

Expand All @@ -202,7 +201,7 @@ def accepts_turn(self):
and self.agent.controller.accepts_turn_snapshot(self.turn_token))

@handles(ThreadPresentation)
def thread_presentation(self, presentation):
def thread_presentation(self, presentation) -> None:
identity = presentation.read_identity
if identity is None:
return # An unavailable source supplies no turn settlement.
Expand Down
15 changes: 9 additions & 6 deletions src/toad/acp/sdk_boundary.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""Decode the specification once; typed SDK models own ACP values thereafter."""
from __future__ import annotations
from typing import Any, TYPE_CHECKING

from abc import abstractmethod
from dataclasses import dataclass
from agent_comms.declared_family import DeclaredFamily
from toad.render_backend import RenderTask
from typing import TYPE_CHECKING

from acp.schema import SessionNotification, ToolCall
from agent_comms.acp_extension import AgentCommsUpdate, decode_updates
from agent_comms.declared_family import DeclaredFamily
from agent_comms.field_codec import FieldRepresentation

from toad.acp.notification_items import NotificationItems
from agent_comms.acp_extension import AgentCommsUpdate, decode_updates
from toad.render_backend import RenderTask

if TYPE_CHECKING:
from toad.acp.status import ToolCallStatus
Expand Down Expand Up @@ -41,15 +44,15 @@ class AcceptedSessionUpdateValidation(SessionUpdateValidation):
notification: SessionNotification
updates: tuple[AgentCommsUpdate, ...]

def publish(self, owner, session_id, raw, metadata):
def publish(self, owner, session_id, raw, metadata) -> None:
owner.publish(session_id, self.notification, self.updates)


@dataclass(frozen=True)
class RejectedSessionUpdateValidation(SessionUpdateValidation):
error: str

def publish(self, owner, session_id, raw, metadata):
def publish(self, owner, session_id, raw, metadata) -> None:
owner.reject(session_id, raw, metadata, self.error)


Expand Down
10 changes: 6 additions & 4 deletions src/toad/render_processes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
"""

import asyncio
from collections.abc import Callable
from concurrent.futures import ProcessPoolExecutor
import multiprocessing
import os
from collections.abc import Callable
from concurrent.futures import ProcessPoolExecutor
from typing import TYPE_CHECKING, Any, TypeVar

from toad.render_backend import Renderer
Expand All @@ -33,8 +33,10 @@ def _initialize_worker() -> None:
No notification or rendering job is made.
"""
os.environ.pop("AGENT_COMMS_THREAD", None)
from toad.acp import sdk_boundary # noqa: F401 -- declare worker ingress capabilities
from toad import render_tasks # noqa: F401 -- declare worker rendering capabilities
from toad.acp import (
sdk_boundary, # noqa: F401 -- declare worker ingress capabilities
)


class RenderProcessPool(Renderer):
Expand Down Expand Up @@ -98,7 +100,7 @@ def start(self) -> None:
)
self.start_workers(self._executor)

async def run(self, function: Callable[..., Result], *args: Any) -> Result:
async def run[*Args, Value](self, function: Callable[[*Args], Value], *args: *Args) -> Value:
"""Execute a data-only function in a child, propagating its result/error."""
loop = self._bind_loop()
while not self._closed and len(self._pending) >= self._max_pending:
Expand Down
42 changes: 28 additions & 14 deletions src/toad/textual_colours.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,53 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass, replace
from functools import lru_cache
from typing import Any, ClassVar, TYPE_CHECKING
from typing import TYPE_CHECKING, Any, ClassVar

from rich.color import Color as RichColor
from rich.console import RenderableType
from rich.segment import Segment
from rich.style import Style as RichStyle
from rich.terminal_theme import TerminalTheme
from textual.ansi_palette import AnsiPalette, NativeAnsiPalette, MappedAnsiPalette
from textual.ansi_palette import AnsiPalette, MappedAnsiPalette, NativeAnsiPalette
from textual.color import Color
from textual.design import ColorSystem
from textual.content import Content
from textual.design import ColorSystem
from textual.driver import TerminalGaveNoPalette
from textual.style import Style as VisualStyle
from textual.strip import Strip
from toad.preferences import SavedSettings, ToadSettings
from textual.screen import Screen
from textual.strip import Strip
from textual.style import Style as VisualStyle
from textual.theme import Theme
from textual.timer import Timer
from watchdog.events import FileClosedEvent, FileSystemEvent, FileSystemEventHandler
from watchdog.observers import Observer

from ui_theme.backend import Backend, TerminalBackend
from ui_theme.catalog import SCHEMES
from ui_theme.clock import AnimationClock
from ui_theme.colour import AnsiColour, Colour, Rgb, TerminalPalette, require_terminal_palette
from ui_theme.colour import (
AnsiColour,
Colour,
Rgb,
TerminalPalette,
require_terminal_palette,
)
from ui_theme.depth import ColourDepth
from ui_theme.effects import Band, Gradient, Mix, PlayedText, TextFrame
from ui_theme.scheme import (
BackgroundColour,
BlendColour,
ColourScheme,
ForegroundColour,
LayerColour,
TextStyle,
ThemeColour,
)
from ui_theme.splash import TextEffect
from ui_theme.scheme import BackgroundColour, BlendColour, ColourScheme, ForegroundColour, LayerColour, ThemeColour, TextStyle
from ui_theme.wal import WalFile
from toad.widgets.transcript_lines import LineTheme, RichLineRenderer
from watchdog.events import FileClosedEvent, FileSystemEvent, FileSystemEventHandler
from watchdog.observers import Observer

from toad.line_blocks import LineRole
from toad.preferences import SavedSettings, ToadSettings
from toad.render_tasks import TextEffectRenderTask
from toad.widgets.transcript_lines import LineTheme, RichLineRenderer

if TYPE_CHECKING:
from toad.app import ToadApp
Expand Down Expand Up @@ -186,7 +200,7 @@ class TextualScheme:
palette: PaletteState


class TextualColours(Backend[Theme, Color, Any, Strip], TerminalBackend[TerminalTheme]):
class TextualColours(Backend[Theme, Color, Strip | Content, Strip], TerminalBackend[TerminalTheme]):
"""The app's colour depth, schemes, terminal palettes, line styles and animation clock."""

def __init__(self, app: ToadApp) -> None:
Expand Down Expand Up @@ -402,7 +416,7 @@ def paint_rising(self, frame: Mix, target: Strip) -> Strip:
"""The palette state owns RGB blending or the native dim-to-normal step."""
return self.schemes[self.app.theme].palette.paint_rising(self, frame, target)

def paint_text(self, frame: TextFrame, target: None) -> Content:
def paint_text(self, frame: TextFrame, target: Strip | None) -> Content:
"""Text in Textual's own styled text; each frame is built once."""
return _text(frame)

Expand Down
Loading
Loading