diff --git a/src/toad/acp/transcript_reader.py b/src/toad/acp/transcript_reader.py index f4e1fb1dc..3f59843b2 100644 --- a/src/toad/acp/transcript_reader.py +++ b/src/toad/acp/transcript_reader.py @@ -40,7 +40,7 @@ async def read(self, request: AwaitedRead): observed = await self.access.preparation.run_thread( self.access.observation.observed ) - return await observed.process.read(request) + return await observed.read(request) async def service(self, root): return await self.access.preparation.run_thread( diff --git a/src/toad/comms_root.py b/src/toad/comms_root.py index 4efacaace..5d74faeb1 100644 --- a/src/toad/comms_root.py +++ b/src/toad/comms_root.py @@ -13,6 +13,7 @@ import asyncio import threading +from abc import ABC, abstractmethod from collections.abc import Callable, Mapping from dataclasses import dataclass, field from functools import partial @@ -32,6 +33,7 @@ from agent_comms.route_selection import RouteSelection from agent_comms.root_runtime import RootRuntime from agent_comms.ui_model.observation import ( + AwaitedRead, CommanderHudObserved, CommanderHudUnreadable, ObserveCommanderHud, @@ -96,7 +98,7 @@ class UnselectedRoute: @dataclass(frozen=True, kw_only=True) -class Observation: +class Observation(ABC): access: CoordinationAccess = field(compare=False) subscriptions: ResourceRelease = field(default_factory=ResourceRelease, compare=False) # View updates end before the views close; `subscriptions` retires the observation after them. @@ -105,6 +107,9 @@ class Observation: def observed(self): return self + @abstractmethod + async def read(self, request: AwaitedRead) -> object: ... + def is_current(self) -> bool: return self is self.access.observation @@ -163,12 +168,18 @@ class NotObserving(Observation): def observed(self): return self.access.open_current() + async def read(self, request: AwaitedRead) -> object: + return await self.observed().read(request) + @dataclass(frozen=True, eq=False) class FailedObservation(Observation): error: Exception = field(compare=False, repr=False) selection: RouteSelection | UnselectedRoute + async def read(self, request: AwaitedRead) -> object: + raise self.error + def __eq__(self, other: object) -> bool: # Exceptions declare their type, constructor fields and extra state through # reconstruction; OSError includes filenames that are absent from args. @@ -219,6 +230,9 @@ class Observing(Observation): sidebar: SidebarModel = field() process: ObservationProcess = field(compare=False) + async def read(self, request: AwaitedRead) -> object: + return await self.process.read(request) + def observes(self, root: str | Path) -> bool: return self.service.root == Path(root).resolve()