"""Rendering what a tool did. The block is written from four places and read from stored rows written by earlier versions, so it has to render anything shaped roughly like an event -- and everything in it is third-party text. """ from __future__ import annotations import re from pathlib import Path from lembas.services import tool_labels from lembas.services import tools as tools_service from lembas.services.agent import tools as agent_tools from lembas.web.templating import templates def _render(*events, live: bool = False) -> str: return templates.get_template("chat/_tool_activity.html").render( {"tool_events": list(events), "live": live} ) def test_a_search_still_says_it_searched_the_web(): html = _render( { "name": "web_search", "kind": "search", "query": "mallorn", "status": "ok", "results": [ { "title": "Mallorn", "url": "https://a.test/m", "host": "a.test", "snippet": "A tree.", } ], } ) assert "Searched the web for “mallorn”" in html assert '".""" html = _render({"name": "notes_search", "query": "shopping", "status": "ok", "results": []}) assert "Searched the web" not in html assert "Notes searched" in html def test_a_custom_tool_is_named_and_its_host_shown(): html = _render( { "name": "weather", "kind": "custom", "label": "Weather", "query": "city='Minas Tirith'", "detail": "GET api.test", "status": "ok", "results": [], "text": "Sunny.", } ) assert "Weather" in html assert "GET api.test" in html assert "Sunny." in html def test_a_tools_own_text_is_escaped_and_never_rendered_as_markdown(): """Hard rule 6. A tool's reply is exactly as untrusted as a search result, and markdown is the one path allowed to emit HTML.""" html = _render( { "name": "weather", "kind": "custom", "label": "Weather", "status": "ok", "results": [], "text": " [click](javascript:alert(1))", } ) assert "Bad' in html def test_a_result_with_no_url_at_all_does_not_explode(): html = _render( { "name": "notes_search", "status": "ok", "results": [{"title": "A note", "id": "abc"}], } ) assert "A note" in html def test_a_failure_shows_its_reason(): html = _render( { "name": "weather", "kind": "custom", "label": "Weather", "status": "error", "error": "HTTP 503", "results": [], } ) assert "tool-activity--error" in html assert "Weather failed" in html assert "HTTP 503" in html # --- What a tool is called ----------------------------------------------------- def test_a_stored_profile_name_no_longer_becomes_the_label(): """The whole point of the inversion. Every agent event written before today carries `label` set to the SSH profile's name, so the transcript said "homeserver · ls -la" and named the machine rather than the thing that was done. Those rows are on disk and are re-rendered on every page load, so the fix has to reach them -- which means the static table wins over the stored value, not the other way round. """ html = _render( { "name": "shell_run", "kind": "agent", "label": "homeserver", "query": "ls -la", "detail": "homeserver:/srv/app", "status": "ok", "results": [], } ) summary = html.split("")[0] assert "Bash" in summary assert "homeserver" not in summary # It is still shown, in the body, where "where this ran" belongs. assert "homeserver:/srv/app" in html def test_a_custom_tools_own_label_still_wins(): """The other half of the same rule. A row-backed tool's name is per row and cannot be tabulated, so nothing in the table shadows it.""" html = _render({"name": "weather", "kind": "custom", "label": "Weather", "results": []}) assert "Weather" in html def test_every_builtin_and_agent_tool_has_a_label_and_an_icon(): """A property, not markup. A tool added without an entry renders its own function name at somebody, which is the state this replaced.""" names = [tool.name for tool in tools_service.REGISTRY.values()] names += [tool.name for tool in agent_tools.tool_defs()] # plan_submit is filtered out of tool_defs() outside Plan mode. names.append("plan_submit") missing = [name for name in names if name not in tool_labels.LABELS] assert not missing, f"no label for {missing}" missing = [name for name in names if name not in tool_labels.ICONS] assert not missing, f"no icon for {missing}" def test_every_icon_named_exists_in_the_sprite(): """A typo'd symbol id renders an empty box and says nothing. This is the only thing that catches it.""" sprite = Path(tools_service.__file__).parents[1] / "web/templates/partials/icons.html" available = set(re.findall(r'id="i-([a-z-]+)"', sprite.read_text())) wanted = set(tool_labels.ICONS.values()) | set(tool_labels.KIND_ICONS.values()) wanted.add(tool_labels.FALLBACK_ICON) assert wanted <= available, f"not in the sprite: {sorted(wanted - available)}" def test_an_unknown_tool_falls_back_to_its_name(): assert tool_labels.label_for({"name": "mcp_thing"}) == "mcp_thing" assert tool_labels.icon_for({"name": "mcp_thing", "kind": "mcp"}) == "server" assert tool_labels.icon_for({"name": "whatever"}) == tool_labels.FALLBACK_ICON # --- Diffs ----------------------------------------------------------------------- def test_a_diff_renders_added_and_removed_lines(): html = _render( { "name": "file_edit", "kind": "agent", "query": "src/app.py", "status": "ok", "results": [], "diff": "--- a/src/app.py\n+++ b/src/app.py\n@@ -1,2 +1,2 @@\n alpha\n-beta\n+BETA", } ) assert 'diff__line--del">-beta' in html assert 'diff__line--add">+BETA' in html assert 'diff__line--ctx"> alpha' in html assert 'diff__line--meta">@@ -1,2 +1,2 @@' in html def test_a_diff_header_is_not_an_addition(): """`+++ b/x` at the top of every diff would otherwise render green, and `--- a/x` red, which reads as the file being replaced by itself.""" html = _render( { "name": "file_edit", "results": [], "diff": "--- a/x.py\n+++ b/x.py\n@@ -1 +1 @@\n-a\n+b", } ) assert 'diff__line--meta">--- a/x.py' in html assert 'diff__line--meta">+++ b/x.py' in html def test_a_removed_line_of_dashes_is_still_a_removal(): """A removed line whose own text begins with `--` produces exactly three dashes, which is why the header test is against the a/ and b/ prefixes.""" html = _render({"name": "file_edit", "results": [], "diff": "@@ -1 +1 @@\n--- a dashed line"}) assert 'diff__line--del">--- a dashed line' in html def test_a_diff_line_is_escaped(): """Hard rule 6. It is a file off somebody else's machine.""" html = _render( { "name": "file_edit", "results": [], "diff": "@@ -1 +1 @@\n+", } ) assert "