tenseleyflow/loader / a488ab3

Browse files

Show runtime owner and verification attempts in TUI

Authored by espadonne
SHA
a488ab3663ef5ee911445da6bab5973edd8c8412
Parents
d193644
Tree
f0b1555

6 changed files

StatusFile+-
M src/loader/runtime/runtime_api.py 2 1
M src/loader/ui/adapter.py 17 0
M src/loader/ui/app.py 28 0
M src/loader/ui/status_helpers.py 16 1
M src/loader/ui/widgets/status_line.py 22 0
M tests/test_status_surfaces.py 4 2
src/loader/runtime/runtime_api.pymodified
@@ -9,7 +9,7 @@ from typing import Literal, Protocol
99
 from ..context.project import ProjectContext
1010
 from ..tools.base import ToolRegistry
1111
 from .capabilities import CapabilityProfile
12
-from .events import AgentEvent
12
+from .events import AgentEvent, TurnSummary
1313
 from .session import ConversationSession
1414
 
1515
 RuntimeOwnerKind = Literal["runtime", "public-compat"]
@@ -23,6 +23,7 @@ class RuntimeShellOwner(Protocol):
2323
     capability_profile: CapabilityProfile
2424
     safeguards: object
2525
     session: ConversationSession
26
+    last_turn_summary: TurnSummary | None
2627
     project_context: ProjectContext | None
2728
     workflow_mode: str
2829
     active_permission_mode: str
src/loader/ui/adapter.pymodified
@@ -191,6 +191,7 @@ class DefinitionOfDoneUpdated(Message):
191191
     dod_status: str
192192
     pending_items_count: int = 0
193193
     last_verification_result: str | None = None
194
+    verification_attempt: str | None = None
194195
 
195196
 
196197
 @dataclass
@@ -460,6 +461,9 @@ class EventAdapter:
460461
                         dod_status=event.dod_status or "",
461462
                         pending_items_count=event.pending_items_count or 0,
462463
                         last_verification_result=event.last_verification_result,
464
+                        verification_attempt=_definition_of_done_verification_attempt(
465
+                            event.definition_of_done
466
+                        ),
463467
                     )
464468
                 )
465469
 
@@ -487,3 +491,16 @@ class EventAdapter:
487491
                         artifact_path=event.artifact_path or "",
488492
                     )
489493
                 )
494
+
495
+
496
+def _definition_of_done_verification_attempt(dod) -> str | None:
497
+    """Render one compact verification-attempt label from DoD state."""
498
+
499
+    if dod is None:
500
+        return None
501
+    active_number = getattr(dod, "active_verification_attempt_number", None)
502
+    if active_number is None:
503
+        return None
504
+    if getattr(dod, "last_verification_result", None) == "stale" and active_number > 1:
505
+        return f"attempt {active_number - 1} -> attempt {active_number}"
506
+    return f"attempt {active_number}"
src/loader/ui/app.pymodified
@@ -123,9 +123,21 @@ class LoaderApp(App):
123123
         status.mode = self.mode
124124
         status.capability_profile = self.capability_profile
125125
         status.session_id = self.session_id
126
+        status.runtime_owner = self.shell_owner.session.runtime_owner_path or ""
126127
         status.workflow_mode = self.workflow_mode
127128
         status.turn_phase = self.turn_phase
128129
         status.permission_mode = self.permission_mode
130
+        if (
131
+            self.shell_owner.last_turn_summary is not None
132
+            and self.shell_owner.last_turn_summary.definition_of_done is not None
133
+        ):
134
+            dod = self.shell_owner.last_turn_summary.definition_of_done
135
+            status.update_definition_of_done(
136
+                dod.status,
137
+                len(dod.pending_items),
138
+                dod.last_verification_result,
139
+                _definition_of_done_verification_attempt(dod),
140
+            )
129141
 
130142
         # Focus input
131143
         self.query_one(InputArea).focus_input()
@@ -692,6 +704,7 @@ class LoaderApp(App):
692704
             message.dod_status,
693705
             message.pending_items_count,
694706
             message.last_verification_result,
707
+            message.verification_attempt,
695708
         )
696709
 
697710
     def on_workflow_mode_changed(self, message: WorkflowModeChanged) -> None:
@@ -860,6 +873,10 @@ class LoaderApp(App):
860873
         msg_area.remove_children()
861874
         self.shell_owner.clear_history()
862875
         self.query_one(StatusLine).clear_definition_of_done()
876
+        self.query_one(StatusLine).update_session_id(self.shell_owner.session.session_id)
877
+        self.query_one(StatusLine).update_runtime_owner(
878
+            self.shell_owner.session.runtime_owner_path or ""
879
+        )
863880
         self.query_one(StatusLine).update_workflow_mode("execute")
864881
         self._add_message("[dim]Conversation cleared.[/dim]")
865882
 
@@ -870,3 +887,14 @@ class LoaderApp(App):
870887
         self.is_generating = False
871888
         self._stop_timer()
872889
         self.query_one(StatusLine).set_generating(False)
890
+
891
+
892
+def _definition_of_done_verification_attempt(dod) -> str | None:
893
+    """Render one compact verification-attempt label from DoD state."""
894
+
895
+    active_number = getattr(dod, "active_verification_attempt_number", None)
896
+    if active_number is None:
897
+        return None
898
+    if getattr(dod, "last_verification_result", None) == "stale" and active_number > 1:
899
+        return f"attempt {active_number - 1} -> attempt {active_number}"
900
+    return f"attempt {active_number}"
src/loader/ui/status_helpers.pymodified
@@ -2,11 +2,14 @@
22
 
33
 from __future__ import annotations
44
 
5
+from ..runtime.owner_metadata import normalize_runtime_owner_path
6
+
57
 
68
 def format_definition_of_done_parts(
79
     status: str,
810
     pending_items_count: int,
911
     last_verification_result: str,
12
+    verification_attempt: str = "",
1013
 ) -> list[str]:
1114
     """Format definition-of-done state for the status line."""
1215
     if not status:
@@ -27,8 +30,11 @@ def format_definition_of_done_parts(
2730
         verify_color = "green" if last_verification_result == "passed" else "red"
2831
         if last_verification_result == "skipped":
2932
             verify_color = "dim"
33
+        verify_label = f"verify {last_verification_result}"
34
+        if verification_attempt:
35
+            verify_label = f"{verify_label} ({verification_attempt})"
3036
         parts.append(
31
-            f"[{verify_color}]verify {last_verification_result}[/{verify_color}]"
37
+            f"[{verify_color}]{verify_label}[/{verify_color}]"
3238
         )
3339
 
3440
     return parts
@@ -65,6 +71,15 @@ def format_session_part(session_id: str) -> str | None:
6571
     return f"[dim]session {session_id[-8:]}[/dim]"
6672
 
6773
 
74
+def format_runtime_owner_part(owner_path: str) -> str | None:
75
+    """Format the active runtime-owner path for the status line."""
76
+
77
+    normalized_path = normalize_runtime_owner_path(owner_path)
78
+    if not normalized_path:
79
+        return None
80
+    return f"[dim]owner {normalized_path}[/dim]"
81
+
82
+
6883
 def format_workflow_mode_part(mode: str) -> str | None:
6984
     """Format the active workflow mode for the status line."""
7085
 
src/loader/ui/widgets/status_line.pymodified
@@ -7,6 +7,7 @@ from ..status_helpers import (
77
     format_capability_part,
88
     format_definition_of_done_parts,
99
     format_permission_mode_part,
10
+    format_runtime_owner_part,
1011
     format_session_part,
1112
     format_turn_phase_part,
1213
     format_workflow_mode_part,
@@ -20,6 +21,7 @@ class StatusLine(Static):
2021
     mode: reactive[str] = reactive("Native")
2122
     capability_profile: reactive[str] = reactive("")
2223
     session_id: reactive[str] = reactive("")
24
+    runtime_owner: reactive[str] = reactive("")
2325
     workflow_mode: reactive[str] = reactive("")
2426
     turn_phase: reactive[str] = reactive("")
2527
     permission_mode: reactive[str] = reactive("")
@@ -29,6 +31,7 @@ class StatusLine(Static):
2931
     dod_status: reactive[str] = reactive("")
3032
     pending_items_count: reactive[int] = reactive(0)
3133
     last_verification_result: reactive[str] = reactive("")
34
+    verification_attempt: reactive[str] = reactive("")
3235
 
3336
     def render(self) -> str:
3437
         """Render the status line."""
@@ -51,6 +54,7 @@ class StatusLine(Static):
5154
                 self.dod_status,
5255
                 self.pending_items_count,
5356
                 self.last_verification_result,
57
+                self.verification_attempt,
5458
             )
5559
         )
5660
 
@@ -76,6 +80,9 @@ class StatusLine(Static):
7680
         session_part = format_session_part(self.session_id)
7781
         if session_part:
7882
             parts.append(session_part)
83
+        runtime_owner = format_runtime_owner_part(self.runtime_owner)
84
+        if runtime_owner:
85
+            parts.append(runtime_owner)
7986
 
8087
         return " · ".join(parts) if parts else "[dim]Ready[/dim]"
8188
 
@@ -103,6 +110,10 @@ class StatusLine(Static):
103110
         """React to session id changes."""
104111
         self.refresh()
105112
 
113
+    def watch_runtime_owner(self, runtime_owner: str) -> None:
114
+        """React to runtime owner changes."""
115
+        self.refresh()
116
+
106117
     def watch_workflow_mode(self, workflow_mode: str) -> None:
107118
         """React to workflow mode changes."""
108119
         self.refresh()
@@ -123,6 +134,10 @@ class StatusLine(Static):
123134
         """React to verification result changes."""
124135
         self.refresh()
125136
 
137
+    def watch_verification_attempt(self, verification_attempt: str) -> None:
138
+        """React to verification attempt changes."""
139
+        self.refresh()
140
+
126141
     def set_generating(self, is_generating: bool) -> None:
127142
         """Set generating state."""
128143
         if is_generating:
@@ -151,6 +166,10 @@ class StatusLine(Static):
151166
         """Update the active session id."""
152167
         self.session_id = session_id
153168
 
169
+    def update_runtime_owner(self, runtime_owner: str) -> None:
170
+        """Update the active runtime owner path."""
171
+        self.runtime_owner = runtime_owner
172
+
154173
     def update_workflow_mode(self, workflow_mode: str) -> None:
155174
         """Update the active workflow mode."""
156175
         self.workflow_mode = workflow_mode
@@ -164,14 +183,17 @@ class StatusLine(Static):
164183
         status: str,
165184
         pending_items_count: int,
166185
         last_verification_result: str | None,
186
+        verification_attempt: str | None = None,
167187
     ) -> None:
168188
         """Update definition-of-done status."""
169189
         self.dod_status = status
170190
         self.pending_items_count = pending_items_count
171191
         self.last_verification_result = last_verification_result or ""
192
+        self.verification_attempt = verification_attempt or ""
172193
 
173194
     def clear_definition_of_done(self) -> None:
174195
         """Clear definition-of-done status."""
175196
         self.dod_status = ""
176197
         self.pending_items_count = 0
177198
         self.last_verification_result = ""
199
+        self.verification_attempt = ""
tests/test_status_surfaces.pymodified
@@ -10,6 +10,7 @@ from loader.ui.status_helpers import (
1010
     format_capability_part,
1111
     format_definition_of_done_parts,
1212
     format_permission_mode_part,
13
+    format_runtime_owner_part,
1314
     format_session_part,
1415
     format_turn_phase_part,
1516
     format_workflow_mode_part,
@@ -17,12 +18,12 @@ from loader.ui.status_helpers import (
1718
 
1819
 
1920
 def test_status_helper_formats_definition_of_done_parts() -> None:
20
-    parts = format_definition_of_done_parts("verifying", 1, "failed")
21
+    parts = format_definition_of_done_parts("verifying", 1, "failed", "attempt 2")
2122
 
2223
     assert parts == [
2324
         "[yellow]DoD: verifying[/yellow]",
2425
         "[dim]1 pending[/dim]",
25
-        "[red]verify failed[/red]",
26
+        "[red]verify failed (attempt 2)[/red]",
2627
     ]
2728
 
2829
 
@@ -63,3 +64,4 @@ def test_turn_phase_helpers_use_expected_colors() -> None:
6364
 def test_status_helpers_format_capability_and_session_parts() -> None:
6465
     assert format_capability_part("native/strict") == "[dim]cap native/strict[/dim]"
6566
     assert format_session_part("20260406T120000Z-abcdef01") == "[dim]session abcdef01[/dim]"
67
+    assert format_runtime_owner_part("runtime-handle") == "[dim]owner runtime-handle[/dim]"