diff --git a/pyproject.toml b/pyproject.toml index 402feec0d..ab9d33799 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ members = ["toad"] [tool.uv.sources] ui-theme = { git = "https://github.com/OpenHCSDev/ui-theme.git", rev = "5e58d8b5907cec4dbc8810a9329aaf902f69f675" } -agent-comms = { git = "https://github.com/OpenHCSDev/agent-comms.git", rev = "47f9856b6762553ca10ef94e26de85b2ba069c0d" } +agent-comms = { git = "https://github.com/OpenHCSDev/agent-comms.git", rev = "ca7b57b3d10ee4e769e9fe2c8ff33ab540f8536f" } textual = { git = "https://github.com/OpenHCSDev/textual.git", rev = "18f4e7a67403d56846ef5abf55c93e3e0639be33" } textual-diff-view = { git = "https://github.com/OpenHCSDev/textual-diff-view.git", rev = "8fa7d4d0db993ea3b761c2760ca9b6a56a6251e9" } diff --git a/src/toad/conversation_turn.py b/src/toad/conversation_turn.py index 6372969aa..c09692126 100644 --- a/src/toad/conversation_turn.py +++ b/src/toad/conversation_turn.py @@ -2,6 +2,7 @@ from abc import abstractmethod from dataclasses import dataclass, replace +from agent_comms.activity import ActivityState from agent_comms.declared_family import DeclaredFamily from agent_comms.turn_lease import TurnState @@ -10,10 +11,15 @@ class TurnOwner(DeclaredFamily, affix="Turn"): managed_id = None activity = "" started_at = None + activity_state = ActivityState.IDLE @property - @abstractmethod - def busy(self) -> bool: ... + def presentation_status(self): + return self.activity_state.presentation_status + + @property + def busy(self) -> bool: + return self.activity_state.busy @property def status(self) -> str: @@ -57,20 +63,18 @@ def response_stream(self, delivery): @dataclass(frozen=True) class NoTurn(TurnOwner): - busy = False session_state = None @dataclass(frozen=True) class ClientTurn(TurnOwner): - busy = False session_state = "idle" @dataclass(frozen=True) class ActivityTurn(TurnOwner): activity: str - busy = True + activity_state = ActivityState.WORKING session_state = "busy" def with_activity(self, activity: str) -> TurnOwner: @@ -83,7 +87,7 @@ class AgentTurn(TurnOwner): managed_id: str | None = None activity: str = "Thinking…" started_at: float | None = None - busy = True + activity_state = ActivityState.WORKING session_state = "busy" def matches_settlement(self, turn_id) -> bool: @@ -106,6 +110,10 @@ class ManagedTurn(TurnOwner): def busy(self): return self.state.busy + @property + def activity_state(self): + return self.state.phase.activity_state + @property def managed_id(self): return self.state.managed_id diff --git a/src/toad/session_tracker.py b/src/toad/session_tracker.py index b70b5b615..f88554663 100644 --- a/src/toad/session_tracker.py +++ b/src/toad/session_tracker.py @@ -58,7 +58,7 @@ def unread(self) -> UnreadPresentation: @property def busy(self) -> bool: - return self.row.busy + return self.row.status.busy @dataclass(frozen=True) diff --git a/src/toad/widgets/channel_participants.py b/src/toad/widgets/channel_participants.py index 50ba9b144..4c10f42de 100644 --- a/src/toad/widgets/channel_participants.py +++ b/src/toad/widgets/channel_participants.py @@ -37,7 +37,7 @@ def prepare_participants(people: tuple[ThreadRowModel, ...]) -> tuple[Content, C for presentation in people: summaries.append(f"{presentation.name}: {presentation.summary}") names.append(Content.styled( - presentation.label, "$warning" if presentation.busy else "$text-muted", + presentation.label, "$warning" if presentation.status.busy else "$text-muted", ).stylize(Style.from_meta({"@click": OpenTarget(presentation.name).click}))) content = Content.assemble("Active: ", Content(" · ").join(names)) if names else Content("No active turns") return content, Content("\n".join(summaries)) diff --git a/src/toad/widgets/observed_thread_activity.py b/src/toad/widgets/observed_thread_activity.py index 8129fd571..1d6ba0ca0 100644 --- a/src/toad/widgets/observed_thread_activity.py +++ b/src/toad/widgets/observed_thread_activity.py @@ -58,7 +58,7 @@ def activity_changed(self) -> None: @property def working(self) -> bool: - return self.row is not None and self.row.working + return self.row is not None and self.row.status.busy class ThreadStatusOwner: diff --git a/src/toad/widgets/session_details.py b/src/toad/widgets/session_details.py index 3465c354f..fc2fda009 100644 --- a/src/toad/widgets/session_details.py +++ b/src/toad/widgets/session_details.py @@ -68,11 +68,15 @@ def title_role(self) -> type[LineRole]: return NoticeRole row = self.row if row is not None and row.shown: - if row.attention: - return NoticeRole - if row.working: - return WorkingRole - elif self.turns is not None and self.turns.owner.busy: + status = row.status + elif self.turns is not None: + status = self.turns.owner.presentation_status + else: + return MutedRole + # Core owns the state; this is its one frontend paint decision. + if status.attention: + return NoticeRole + if status.busy: return WorkingRole return MutedRole diff --git a/src/toad/widgets/session_sidebar.py b/src/toad/widgets/session_sidebar.py index cdfe51787..5d73c7c93 100644 --- a/src/toad/widgets/session_sidebar.py +++ b/src/toad/widgets/session_sidebar.py @@ -35,7 +35,7 @@ def __init__(self, *args, **kwargs): @property def busy(self) -> bool: - return self.thread_row is not None and (self.action_status is not None or self.thread_row.busy) + return self.thread_row is not None and (self.action_status is not None or self.thread_row.status.busy) def show(self, row: ThreadRowModel, *, pinned: bool = False, action_status: str | None = None) -> None: """Render the model's row with this view's own decoration: pin and pending action.""" diff --git a/uv.lock b/uv.lock index 91bd434fa..2c4a3f03e 100644 --- a/uv.lock +++ b/uv.lock @@ -21,10 +21,12 @@ wheels = [ [[package]] name = "agent-comms" version = "0.1.0" -source = { git = "https://github.com/OpenHCSDev/agent-comms.git?rev=47f9856b6762553ca10ef94e26de85b2ba069c0d#47f9856b6762553ca10ef94e26de85b2ba069c0d" } +source = { git = "https://github.com/OpenHCSDev/agent-comms.git?rev=ca7b57b3d10ee4e769e9fe2c8ff33ab540f8536f#ca7b57b3d10ee4e769e9fe2c8ff33ab540f8536f" } dependencies = [ + { name = "aiohttp" }, { name = "metaclass-registry" }, { name = "nominal-refactor-audit" }, + { name = "pydantic" }, ] [package.optional-dependencies] @@ -304,7 +306,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "agent-client-protocol", specifier = "==0.12.1" }, - { name = "agent-comms", extras = ["acp"], git = "https://github.com/OpenHCSDev/agent-comms.git?rev=47f9856b6762553ca10ef94e26de85b2ba069c0d" }, + { name = "agent-comms", extras = ["acp"], git = "https://github.com/OpenHCSDev/agent-comms.git?rev=ca7b57b3d10ee4e769e9fe2c8ff33ab540f8536f" }, { name = "aiosqlite", specifier = "==0.22.1" }, { name = "bashlex", specifier = "==0.18" }, { name = "click", specifier = "==8.4.0" }, @@ -1023,7 +1025,7 @@ wheels = [ [[package]] name = "nominal-refactor-audit" version = "0.1.0" -source = { git = "https://github.com/OpenHCSDev/NominalRefactorAdvisor.git?subdirectory=skills%2Frefactor-audit&rev=9a98d72e10cd7ebae688b2e70d096f0ddc9a4c10#9a98d72e10cd7ebae688b2e70d096f0ddc9a4c10" } +source = { git = "https://github.com/OpenHCSDev/NominalRefactorAdvisor.git?subdirectory=skills%2Frefactor-audit&rev=120c04fb9a0eb4afafab6d621d1c5fd421bb7552#120c04fb9a0eb4afafab6d621d1c5fd421bb7552" } [[package]] name = "notify-py"