feat(tui): integrate with Herdr host
This commit is contained in:
@@ -539,8 +539,8 @@ class WebSocketChannel(BaseChannel):
|
||||
self._webui_connections.discard(connection)
|
||||
self._discard_webui_request_lock_if_idle(connection)
|
||||
|
||||
async def _maybe_push_active_goal_state(self, chat_id: str) -> None:
|
||||
"""Replay an active sustained goal from session metadata after *chat_id* is subscribed.
|
||||
async def _maybe_push_persisted_goal_state(self, chat_id: str) -> None:
|
||||
"""Replay actionable goal state after *chat_id* is subscribed.
|
||||
|
||||
Goal metadata lives on the session JSONL and survives gateway restarts, but
|
||||
connected clients normally see it via ``goal_state`` / ``turn_end`` frames.
|
||||
@@ -554,7 +554,7 @@ class WebSocketChannel(BaseChannel):
|
||||
if not isinstance(meta, dict):
|
||||
meta = {}
|
||||
blob = goal_state_ws_blob(cast(dict[str, Any], meta))
|
||||
if not blob.get("active"):
|
||||
if not blob.get("active") and blob.get("status") != "blocked":
|
||||
return
|
||||
await self.send_goal_state(chat_id, blob)
|
||||
|
||||
@@ -572,7 +572,7 @@ class WebSocketChannel(BaseChannel):
|
||||
|
||||
async def _hydrate_after_subscribe(self, chat_id: str) -> None:
|
||||
"""Replay persisted or actively running per-chat state after subscribe."""
|
||||
await self._maybe_push_active_goal_state(chat_id)
|
||||
await self._maybe_push_persisted_goal_state(chat_id)
|
||||
await self._maybe_push_turn_run_wall_clock(chat_id)
|
||||
|
||||
async def _send_event(
|
||||
|
||||
@@ -3186,7 +3186,7 @@ async def test_maybe_push_active_goal_state_noop_without_session_manager() -> No
|
||||
channel = WebSocketChannel({"enabled": True, "allowFrom": ["*"]}, bus, gateway=_basic_handler(bus))
|
||||
mock_ws = AsyncMock()
|
||||
channel._attach(mock_ws, "chat-1")
|
||||
await channel._maybe_push_active_goal_state("chat-1")
|
||||
await channel._maybe_push_persisted_goal_state("chat-1")
|
||||
mock_ws.send.assert_not_called()
|
||||
|
||||
|
||||
@@ -3202,7 +3202,7 @@ async def test_maybe_push_active_goal_state_skips_when_no_goal_on_disk() -> None
|
||||
)
|
||||
mock_ws = AsyncMock()
|
||||
channel._attach(mock_ws, "chat-1")
|
||||
await channel._maybe_push_active_goal_state("chat-1")
|
||||
await channel._maybe_push_persisted_goal_state("chat-1")
|
||||
mock_ws.send.assert_not_called()
|
||||
|
||||
|
||||
@@ -3227,7 +3227,7 @@ async def test_maybe_push_active_goal_state_notifies_when_goal_active_on_disk()
|
||||
)
|
||||
mock_ws = AsyncMock()
|
||||
channel._attach(mock_ws, "chat-1")
|
||||
await channel._maybe_push_active_goal_state("chat-1")
|
||||
await channel._maybe_push_persisted_goal_state("chat-1")
|
||||
mock_ws.send.assert_awaited_once()
|
||||
body = json.loads(mock_ws.send.await_args.args[0])
|
||||
assert body["event"] == "goal_state"
|
||||
@@ -3237,6 +3237,39 @@ async def test_maybe_push_active_goal_state_notifies_when_goal_active_on_disk()
|
||||
assert body["goal_state"]["ui_summary"] == "Docs"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maybe_push_goal_state_restores_blocked_attention_on_disk() -> None:
|
||||
bus = MagicMock()
|
||||
sm = MagicMock()
|
||||
sm.read_session_file.return_value = {
|
||||
"metadata": {
|
||||
"goal_state": {
|
||||
"status": "blocked",
|
||||
"objective": "deploy safely",
|
||||
"ui_summary": "Approval required",
|
||||
},
|
||||
},
|
||||
"messages": [],
|
||||
}
|
||||
channel = WebSocketChannel(
|
||||
{"enabled": True, "allowFrom": ["*"]},
|
||||
bus,
|
||||
gateway=_basic_handler(bus, session_manager=sm),
|
||||
)
|
||||
mock_ws = AsyncMock()
|
||||
channel._attach(mock_ws, "chat-1")
|
||||
|
||||
await channel._maybe_push_persisted_goal_state("chat-1")
|
||||
|
||||
body = json.loads(mock_ws.send.await_args.args[0])
|
||||
assert body["goal_state"] == {
|
||||
"active": False,
|
||||
"status": "blocked",
|
||||
"ui_summary": "Approval required",
|
||||
"objective": "deploy safely",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maybe_push_turn_run_wall_clock_skips_when_no_active_turn() -> None:
|
||||
bus = MagicMock()
|
||||
|
||||
Reference in New Issue
Block a user