140 lines
4.8 KiB
Python
140 lines
4.8 KiB
Python
"""HUD mode reaches the model as a per-turn note, not as a platform hint.
|
|
|
|
The floating HUD sits over whatever the user is really working in, so a request
|
|
typed there is usually about that other app — and usually wants to be carried
|
|
out IN that app. Without the note the model answers from its own browser and
|
|
panes, which is the wrong half of the screen.
|
|
|
|
The same desktop session can be driven from the app window on one turn and the
|
|
HUD on the next, so this cannot live in the system prompt — that has to stay
|
|
byte-stable for the life of a conversation. It rides the model-bound message
|
|
instead, beside the reaction and speech-interrupted notes.
|
|
"""
|
|
|
|
import threading
|
|
import types
|
|
|
|
import pytest
|
|
|
|
from agent.prompt_builder import hud_surface_note
|
|
from tui_gateway import server
|
|
|
|
FULL_KIT = {"read_window_below", "computer_use", "browser_navigate"}
|
|
|
|
|
|
def _session(*, tools=FULL_KIT, **extra):
|
|
return {
|
|
"agent": types.SimpleNamespace(valid_tool_names=set(tools)),
|
|
"session_key": "session-key",
|
|
"history": [],
|
|
"history_lock": threading.Lock(),
|
|
"history_version": 0,
|
|
"running": False,
|
|
"transport": None,
|
|
"attached_images": [],
|
|
**extra,
|
|
}
|
|
|
|
|
|
class TestNoteContents:
|
|
"""Every tool the note names has to be one this agent actually has."""
|
|
|
|
def test_points_at_the_window_below_and_at_working_in_it(self):
|
|
note = hud_surface_note(FULL_KIT)
|
|
|
|
assert "read_window_below" in note
|
|
assert "computer_use" in note
|
|
assert "browser_navigate" in note
|
|
|
|
def test_no_note_at_all_without_the_tool_it_rests_on(self):
|
|
assert hud_surface_note({"computer_use", "browser_navigate"}) == ""
|
|
|
|
def test_identifies_the_app_even_when_it_cannot_drive_it(self):
|
|
note = hud_surface_note({"read_window_below"})
|
|
|
|
assert "read_window_below" in note
|
|
assert "computer_use" not in note
|
|
|
|
def test_browser_preference_needs_a_browser_to_prefer_over(self):
|
|
note = hud_surface_note({"read_window_below", "computer_use"})
|
|
|
|
assert "computer_use" in note
|
|
assert "browser_navigate" not in note
|
|
|
|
def test_no_tools_at_all(self):
|
|
assert hud_surface_note(None) == ""
|
|
|
|
|
|
class TestTurnRouting:
|
|
def test_hud_turn_gets_the_note(self):
|
|
assert server._hud_surface_note(_session(client_surface="hud")) == hud_surface_note(FULL_KIT)
|
|
|
|
def test_app_window_turn_gets_nothing(self):
|
|
assert server._hud_surface_note(_session(client_surface="")) == ""
|
|
|
|
def test_session_that_never_reported_a_surface_gets_nothing(self):
|
|
"""Every other client (TUI, dashboard, messaging) omits the field."""
|
|
assert server._hud_surface_note(_session()) == ""
|
|
|
|
def test_survives_an_agent_with_no_toolset_at_all(self):
|
|
session = _session(client_surface="hud")
|
|
session["agent"] = types.SimpleNamespace()
|
|
|
|
assert server._hud_surface_note(session) == ""
|
|
|
|
|
|
class TestPrepending:
|
|
"""Notes ride the model input; the persisted prompt stays what was typed."""
|
|
|
|
def test_plain_text_turn(self):
|
|
assert server._prepend_note("go to x", "[Note: hi]") == "[Note: hi]\n\ngo to x"
|
|
|
|
def test_multimodal_turn_keeps_its_parts(self):
|
|
parts = [{"type": "text", "text": "go to x"}, {"type": "image_url", "image_url": {}}]
|
|
|
|
assert server._prepend_note(parts, "[Note: hi]") == [
|
|
{"type": "text", "text": "[Note: hi]"},
|
|
*parts,
|
|
]
|
|
|
|
def test_nothing_to_say_leaves_the_message_untouched(self):
|
|
parts = [{"type": "text", "text": "go to x"}]
|
|
|
|
assert server._prepend_note("go to x", "") == "go to x"
|
|
assert server._prepend_note(parts, "") is parts
|
|
|
|
|
|
class TestSurfaceRecording:
|
|
"""``prompt.submit`` stamps the window each message was typed into."""
|
|
|
|
@pytest.fixture
|
|
def busy_session(self):
|
|
# A running session takes the busy path, which returns before any of
|
|
# the agent/DB machinery — enough to observe what submit recorded.
|
|
session = _session(running=True)
|
|
server._sessions["sid"] = session
|
|
yield session
|
|
server._sessions.pop("sid", None)
|
|
|
|
def _submit(self, **params):
|
|
return server._methods["prompt.submit"](
|
|
"r1", {"session_id": "sid", "text": "what is this?", "queued": True, **params}
|
|
)
|
|
|
|
def test_hud_submit_is_recorded(self, busy_session):
|
|
self._submit(surface="hud")
|
|
|
|
assert busy_session["client_surface"] == "hud"
|
|
|
|
def test_the_next_app_window_submit_clears_it(self, busy_session):
|
|
"""A stale 'hud' would tell the model the user is still floating."""
|
|
self._submit(surface="hud")
|
|
self._submit()
|
|
|
|
assert busy_session["client_surface"] == ""
|
|
|
|
def test_an_unknown_surface_is_not_hud(self, busy_session):
|
|
self._submit(surface="pet-overlay")
|
|
|
|
assert busy_session["client_surface"] == ""
|