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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
20 changes: 14 additions & 6 deletions src/toad/conversation_turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/toad/session_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/toad/widgets/channel_participants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion src/toad/widgets/observed_thread_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 9 additions & 5 deletions src/toad/widgets/session_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/toad/widgets/session_sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
8 changes: 5 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading