refactor: decouple webui runtime state via events
This commit is contained in:
@@ -7,6 +7,7 @@ from nanobot.agent.loop import AgentLoop
|
||||
from nanobot.bus.events import OutboundMessage
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.providers.base import GenerationSettings, LLMResponse
|
||||
from nanobot.session.webui_turns import WebuiTurnCoordinator
|
||||
|
||||
|
||||
def _make_loop(tmp_path):
|
||||
@@ -25,6 +26,11 @@ def _make_loop(tmp_path):
|
||||
workspace=tmp_path,
|
||||
model="test-model",
|
||||
)
|
||||
WebuiTurnCoordinator(
|
||||
bus=bus,
|
||||
sessions=loop.sessions,
|
||||
schedule_background=lambda coro: loop._schedule_background(coro),
|
||||
).subscribe(loop.runtime_events)
|
||||
loop.tools.get_definitions = MagicMock(return_value=[])
|
||||
return loop
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from nanobot.agent.loop import AgentLoop
|
||||
from nanobot.bus.events import InboundMessage
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.providers.base import LLMResponse, ToolCallRequest
|
||||
from nanobot.session.webui_turns import WebuiTurnCoordinator
|
||||
from nanobot.utils.progress_events import (
|
||||
invoke_file_edit_progress,
|
||||
on_progress_accepts_file_edit_events,
|
||||
@@ -24,6 +25,15 @@ def _make_loop(tmp_path: Path) -> AgentLoop:
|
||||
return AgentLoop(bus=bus, provider=provider, workspace=tmp_path, model="test-model")
|
||||
|
||||
|
||||
def _attach_webui_runtime_events(loop: AgentLoop, bus: MessageBus) -> None:
|
||||
coordinator = WebuiTurnCoordinator(
|
||||
bus=bus,
|
||||
sessions=loop.sessions,
|
||||
schedule_background=lambda coro: loop._schedule_background(coro),
|
||||
)
|
||||
coordinator.subscribe(loop.runtime_events)
|
||||
|
||||
|
||||
class TestToolEventProgress:
|
||||
"""_run_agent_loop emits structured tool_events via on_progress."""
|
||||
|
||||
@@ -456,6 +466,7 @@ class TestToolEventProgress:
|
||||
provider.chat_stream_with_retry = chat_stream_with_retry
|
||||
provider.chat_with_retry = AsyncMock()
|
||||
loop = AgentLoop(bus=bus, provider=provider, workspace=tmp_path, model="openai-codex/gpt-5.5")
|
||||
_attach_webui_runtime_events(loop, bus)
|
||||
loop.tools.get_definitions = MagicMock(return_value=[])
|
||||
loop.consolidator.maybe_consolidate_by_tokens = AsyncMock(return_value=False) # type: ignore[method-assign]
|
||||
|
||||
@@ -549,6 +560,7 @@ class TestToolEventProgress:
|
||||
provider.get_default_model.return_value = "test-model"
|
||||
provider.chat_with_retry = AsyncMock(return_value=LLMResponse(content="Done", tool_calls=[]))
|
||||
loop = AgentLoop(bus=bus, provider=provider, workspace=tmp_path, model="test-model")
|
||||
_attach_webui_runtime_events(loop, bus)
|
||||
loop.tools.get_definitions = MagicMock(return_value=[])
|
||||
loop.consolidator.maybe_consolidate_by_tokens = AsyncMock(return_value=False) # type: ignore[method-assign]
|
||||
|
||||
@@ -593,6 +605,7 @@ class TestToolEventProgress:
|
||||
|
||||
provider.chat_with_retry = AsyncMock(side_effect=chat_with_retry)
|
||||
loop = AgentLoop(bus=bus, provider=provider, workspace=tmp_path, model="test-model")
|
||||
_attach_webui_runtime_events(loop, bus)
|
||||
loop.tools.get_definitions = MagicMock(return_value=[])
|
||||
loop.consolidator.maybe_consolidate_by_tokens = AsyncMock(return_value=False) # type: ignore[method-assign]
|
||||
|
||||
@@ -641,6 +654,7 @@ class TestToolEventProgress:
|
||||
provider.get_default_model.return_value = "test-model"
|
||||
provider.chat_with_retry = AsyncMock(return_value=LLMResponse(content="Done", tool_calls=[]))
|
||||
loop = AgentLoop(bus=bus, provider=provider, workspace=tmp_path, model="test-model")
|
||||
_attach_webui_runtime_events(loop, bus)
|
||||
loop.tools.get_definitions = MagicMock(return_value=[])
|
||||
loop.consolidator.maybe_consolidate_by_tokens = AsyncMock(return_value=False) # type: ignore[method-assign]
|
||||
|
||||
@@ -693,6 +707,7 @@ class TestToolEventProgress:
|
||||
provider.get_default_model.return_value = "test-model"
|
||||
provider.chat_with_retry = AsyncMock(return_value=LLMResponse(content="Done", tool_calls=[]))
|
||||
loop = AgentLoop(bus=bus, provider=provider, workspace=tmp_path, model="test-model")
|
||||
_attach_webui_runtime_events(loop, bus)
|
||||
|
||||
async def fake_title_after_turn(**_kwargs: object) -> bool:
|
||||
raise AssertionError("command-only turns should not generate titles")
|
||||
|
||||
@@ -39,7 +39,13 @@ def _make_full_loop(tmp_path: Path) -> AgentLoop:
|
||||
provider = MagicMock()
|
||||
provider.get_default_model.return_value = "test-model"
|
||||
provider.chat_with_retry = AsyncMock(return_value=LLMResponse(content="Test title"))
|
||||
return AgentLoop(bus=MessageBus(), provider=provider, workspace=tmp_path, model="test-model")
|
||||
loop = AgentLoop(bus=MessageBus(), provider=provider, workspace=tmp_path, model="test-model")
|
||||
WebuiTurnCoordinator(
|
||||
bus=loop.bus,
|
||||
sessions=loop.sessions,
|
||||
schedule_background=lambda coro: loop._schedule_background(coro),
|
||||
).subscribe(loop.runtime_events)
|
||||
return loop
|
||||
|
||||
|
||||
def test_agent_loop_llm_runtime_reflects_current_provider_and_model(tmp_path: Path) -> None:
|
||||
|
||||
@@ -14,8 +14,10 @@ from nanobot.agent.tools.long_task import (
|
||||
LongTaskTool,
|
||||
)
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.bus.runtime_events import RuntimeEventBus
|
||||
from nanobot.session.goal_state import GOAL_STATE_KEY
|
||||
from nanobot.session.manager import SessionManager
|
||||
from nanobot.session.webui_turns import WebuiTurnCoordinator
|
||||
|
||||
|
||||
def _tools(sm: SessionManager) -> tuple[LongTaskTool, CompleteGoalTool]:
|
||||
@@ -120,8 +122,14 @@ async def test_goal_tools_context_isolated_across_tool_types(tmp_path):
|
||||
async def test_long_task_publishes_goal_state_ws_after_save(tmp_path):
|
||||
bus = MagicMock()
|
||||
bus.publish_outbound = AsyncMock()
|
||||
runtime_events = RuntimeEventBus()
|
||||
sm = SessionManager(tmp_path)
|
||||
lt = LongTaskTool(sessions=sm, bus=bus)
|
||||
WebuiTurnCoordinator(
|
||||
bus=bus,
|
||||
sessions=sm,
|
||||
schedule_background=lambda _coro: None,
|
||||
).subscribe(runtime_events)
|
||||
lt = LongTaskTool(sessions=sm, runtime_events=runtime_events)
|
||||
rc = RequestContext(
|
||||
channel="websocket",
|
||||
chat_id="chat-99",
|
||||
@@ -148,9 +156,15 @@ async def test_long_task_publishes_goal_state_ws_after_save(tmp_path):
|
||||
async def test_complete_goal_publishes_inactive_goal_state_ws(tmp_path):
|
||||
bus = MagicMock()
|
||||
bus.publish_outbound = AsyncMock()
|
||||
runtime_events = RuntimeEventBus()
|
||||
sm = SessionManager(tmp_path)
|
||||
lt = LongTaskTool(sessions=sm, bus=bus)
|
||||
cg = CompleteGoalTool(sessions=sm, bus=bus)
|
||||
WebuiTurnCoordinator(
|
||||
bus=bus,
|
||||
sessions=sm,
|
||||
schedule_background=lambda _coro: None,
|
||||
).subscribe(runtime_events)
|
||||
lt = LongTaskTool(sessions=sm, runtime_events=runtime_events)
|
||||
cg = CompleteGoalTool(sessions=sm, runtime_events=runtime_events)
|
||||
rc = RequestContext(
|
||||
channel="websocket",
|
||||
chat_id="chat-z",
|
||||
|
||||
Reference in New Issue
Block a user