{% from "_macros.html" import icon %} {# What the model did before answering. Rendered both live (streamed as a whole block, like the reasoning and the answer) and from the stored message afterwards, so the sources behind an answer stay in the transcript rather than vanishing when the stream ends. EVERYTHING in here is third-party text and is untrusted, exactly as much as model output is -- a search provider's results, an administrator's HTTP tool relaying whatever it was pointed at, an MCP server's reply. Jinja autoescaping covers it. Two things are handled separately: the URL, because `is_linkable` is the only thing standing between a result carrying a javascript: URL and an anchor pointing at it, and `event.text`, which is rendered as preformatted text and deliberately NOT through services/markdown.py -- markdown is the one path allowed to emit HTML, and this is the last content that should be given it. `kind` says how to label the event. Rows written before it existed have none, so web_search reads as a search and everything else falls to the generic branch: an old notes_search event used to claim it had searched the web. What a tool is *called* is not decided here. `tool_label` and `tool_icon` are Jinja globals over services/tool_labels.py, which is the one table the status line and the approval card read too. It deliberately ignores an `event.label` it recognises the name of: rows already on disk carry the SSH profile's name, so a resolver that preferred the stored value would leave every existing transcript saying "homeserver" where it means "Bash". #} {# `message_id` and `step_index` place this block in the reply, and an id is emitted only when the caller gave one. That is not defensiveness: an id is what carries an *opened* block across a swap, and a caller with no message to key on would emit the same id in every bubble on the page, which is worse than emitting none. `_step.html` always passes one. `steps.js` records which of these are open before a swap and puts them back after. It works because the id is stable -- the step index comes from a mark, the marks are append-only, and the finished bubble emits exactly what the live one did, so a block opened at round three is still open after the `done` frame replaces the whole article. #} {# Each kind of call drawn the way it is read (services/tool_blocks.py, from the harness spec's `block`): a command with its output behind the click and no host line; an edit with its most-changed lines shown and the whole change one click further; a read as just the file; a search with its hits. #} {% macro diff_lines(lines) -%} {%- for cls, line in lines -%}{{ line }}{%- endfor -%} {%- endmacro %} {% macro whole_diff(diff) -%}

  {%- for line in diff.split("\n") -%}
    {%- if line.startswith('@@') or line.startswith('--- a/')
           or line.startswith('+++ b/') or line.startswith('diff ') -%}
      {%- set cls = 'meta' -%}
    {%- elif line.startswith('+') -%}
      {%- set cls = 'add' -%}
    {%- elif line.startswith('-') -%}
      {%- set cls = 'del' -%}
    {%- else -%}
      {%- set cls = 'ctx' -%}
    {%- endif -%}
    {{ line }}
  {%- endfor -%}
{%- endmacro %} {% for event in tool_events %} {% set block = tool_block(event) %} {% set block_id = ("tool-" ~ message_id ~ "-" ~ (step_index | default(0)) ~ "-" ~ loop.index0) if message_id | default('') else "" %} {% set failed = event.status == "error" %} {% if event.show and not failed %} {#- `show`: a card that opens the file in the viewer. The card names the file and nothing of its contents; the viewer fetches it through a route that rebuilds the chat's view and checks the path again (api/tree.py `chat_show`), or -- a device's file -- reads the copy the device sent (`shown_id`). The words in it are a path a model chose: escaped like everything here. js/files.js opens it, and opens a card that arrives while the page is open by itself, beside the conversation, on a screen wide enough to have room. -#} {% set shown = event.show %} {% set chat_ref = message.chat_id if message is defined and message else "" %} {% set query = ("shown=" ~ (shown.shown_id|urlencode)) if shown.shown_id else ("node=" ~ (shown.node_id|urlencode) ~ "&version=" ~ ((shown.version_id or "")|urlencode)) %}
{{ icon("eye", "icon--sm") }} {{ shown.name }} {{ shown.path }}{% if shown.line %}:{{ shown.line }}{% endif %} {% if chat_ref %} {{ t("Open") }} {% endif %}
{% elif block.kind == "hidden" %} {% elif block.kind == "read" %} {# Just the file: what was in it is the model's business, and one click to see a file's contents nobody asked for is clutter. #}
{{ icon(tool_icon(event), "icon--sm tool-activity__icon") }} {{ tool_label(event) }} {{ block.title }} {% if failed %}{{ event.error or t("failed") }}{% endif %}
{% elif block.kind == "edit" and event.diff and not failed %} {# The change, shown: its most-changed lines open, the rest one click away. #}
{{ icon(tool_icon(event), "icon--sm tool-activity__icon") }} {{ tool_label(event) }} {{ block.title }} +{{ block.added }} −{{ block.removed }} {% if event.edited %}{{ t("edited by you") }}{% endif %}
{% if block.preview %}
{{ diff_lines(block.preview) }}
{% endif %} {% if block.more %}
{{ t("Show the whole change") }} {{ icon("chevron-down", "icon--sm reasoning__chevron") }} {{ whole_diff(event.diff) }}
{% endif %}
{% elif block.kind in ("shell", "search", "task", "edit") %}
{{ icon(tool_icon(event), "icon--sm tool-activity__icon") }} {% if block.kind == "shell" %} {{ block.title or tool_label(event) }} {% else %} {{ tool_label(event) }}{% if failed %} {{ t("failed") }}{% endif %} {% if block.title %}{{ block.title }}{% endif %} {% endif %} {% for chip in block.chips %}{{ chip }}{% endfor %} {% if event.status == "stopped" %}{{ t("stopped") }}{% endif %} {% if event.edited %}{{ t("edited by you") }}{% endif %} {% if event.purpose %}{{ event.purpose }}{% endif %} {% if event.why %}{{ event.why }}{% endif %} {{ icon("chevron-down", "icon--sm reasoning__chevron") }}
{% if event.error %}

{{ event.error }}

{% endif %} {# A helper's account of itself: which job, which agent, how many calls, how long -- and whether it was stopped short. The task block's alone: a shell row written before 1.3.0 carries a host line in `detail`, and the chat says where it works. #} {% if block.kind == "task" and event.detail %}

{{ event.detail }}

{% endif %} {# A subagent's tool calls, drawn under the task line as the CLI draws them. The names and titles came from another model, or off a device. #} {% if event.children %}
{% for child in event.children %}
{{ icon(tool_icon(child.name), "icon--sm tool-activity__icon") }} {{ tool_label(child.name) }} {% if child.title %}{{ child.title }}{% endif %} {% if child.isError %}{{ t("failed") }}{% endif %}
{% endfor %}
{% endif %} {% if event.text and event.text != event.error %}
{{ event.text }}
{% elif not event.error and not event.results %}

{{ t("No output.") }}

{% endif %} {% if event.diff %}{{ whole_diff(event.diff) }}{% endif %} {% for result in event.results %}
{% set scheme = (result.url or "").split(":")[0] | lower %} {% if scheme in ("http", "https") %} {{ result.title }} {% else %} {{ result.title }} {% endif %} {% if result.host %}{{ result.host }}{% endif %} {% if result.snippet %}

{{ result.snippet }}

{% endif %}
{% endfor %}
{% else %} {% set kind = event.kind or ('search' if event.name == 'web_search' else 'tool') %}
{{ icon(tool_icon(event), "icon--sm tool-activity__icon") }} {% if kind == 'search' %} {# Prose, not "Web search · mallorn". A search is the one thing here common enough to be worth a sentence. #} {% if event.status == "error" %} {{ t("Web search failed") }} {% elif event.query %} {{ t("Searched the web for “%(query)s”", query=event.query) }} {% else %} {{ t("Searched the web") }} {% endif %} {% else %} {% set label = tool_label(event) %} {% if event.status == "error" %} {{ t("%(tool)s failed", tool=label) }} {% else %} {{ label }} {% endif %} {% if event.query %} · {{ event.query }} {% endif %} {% endif %} {% if event.results %} · {{ t("%(n)s result", n=event.results | length) if event.results | length == 1 else t("%(n)s results", n=event.results | length) }} {% endif %} {% if event.edited %} {# Corrected on the approval card before it ran, so what is shown above is the reader's command and not the model's. Said out loud rather than left to be inferred: attributing somebody's own typing to a model is the same misattribution as the other way round, and a transcript read back a week later has nothing else to go on. #} {{ t("edited by you") }} {% endif %} {% if event.purpose %} {# The spec's `purpose`: one sentence from the model for the person, shown beside the call as the approval card would have shown it. #} {{ event.purpose }} {% endif %} {% if event.why %} {# What the model said this call was for. In the summary rather than the body because the body is collapsed: in Auto mode nothing stops for approval, so without this a reader watches a list of commands with no account of what any of them is for until the reply ends. Model text, escaped like everything else here, and styled as a quieter second line so it reads as the model's account rather than ours. #} {{ event.why }} {% endif %} {{ icon("chevron-down", "icon--sm reasoning__chevron") }}
{#- Where a custom or MCP tool went (its request); an agent's own host line is gone -- the chat says where it works. -#} {% if event.detail and kind != 'search' and event.kind != 'agent' %}

{{ event.detail }}

{% endif %} {% if event.error %}

{{ event.error }}

{% elif not event.results and not event.text and not event.image %}

{{ t("Nothing was found.") }}

{% endif %} {% if event.image and event.image.id %} {# What was drawn, in the block that drew it. The picture is also an attachment on this bubble, so the reader sees it without opening anything; this copy is what makes the block make sense on its own once it is expanded, beside the seed and the attempts. The address is built here from an id and is never taken from the event. That is the same rule the result links a few lines down follow, and it matters more here: an event is a dict a runner wrote, and a `src` taken from one would be a model-supplied URL fetched by the reader's browser. `/api/files/{id}/content` is owner-checked on the way out. #} {{ t('The generated image') }} {% endif %} {% if event.text %}
{{ event.text }}
{% endif %} {% if event.diff %}{{ whole_diff(event.diff) }}{% endif %} {% for result in event.results %}
{% set scheme = (result.url or "").split(":")[0] | lower %} {% if scheme in ("http", "https") %} {{ result.title }} {% else %} {# Not a link. A result is third-party text and its URL is not trusted to be safe to click. #} {{ result.title }} {% endif %} {{ result.host }} {% if result.snippet %}

{{ result.snippet }}

{% endif %}
{% endfor %}
{% endif %} {% endfor %}