From 1e591aef13d97268510269a4c153efae6c2286cb Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Fri, 24 Jul 2026 13:29:52 -0400 Subject: [PATCH 1/2] fix(#6467): show final assistant report with tool-limit status card --- .../pr-sweep/webui-PR-TARGET-6467-PROOF.md | 7 + static/ui.js | 3 +- tests/test_status_command_card.py | 228 ++++++++++++++++++ 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 .claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md diff --git a/.claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md b/.claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md new file mode 100644 index 00000000..e867dd74 --- /dev/null +++ b/.claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md @@ -0,0 +1,7 @@ +| surface | command run | observed result | base/head | +| --- | --- | --- | --- | +| ordinary assistant `_statusCard` plus non-empty final report | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | `tests/test_status_command_card.py::test_render_messages_keeps_final_report_when_status_card_present PASSED` and `12 passed in 4.50s` | base: simulated legacy ordinary-branch probe emitted only `limit` and dropped `

Final report

`; head: extracted current ordinary branch emitted the status card before `

Final report

` in the same assistant segment | +| status-only assistant `_statusCard` | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | boundary `tests/test_status_command_card.py::test_render_messages_keeps_status_only_card_boundary PASSED` | base: boundary unchanged by the bug contract; head: extracted current branch returned only `limit` | +| assistant files plus `_statusCard` with empty text body | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | boundary `tests/test_status_command_card.py::test_render_messages_keeps_files_when_status_card_present PASSED` | base: the legacy ordinary branch returned only `limit` when the assistant segment carried files without text. head: extracted current branch kept `
artifact.txt
` after the status card in the same assistant segment | +| special status-first branch | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | boundary `tests/test_status_command_card.py::test_ordered_transparent_branch_keeps_single_status_card_boundary PASSED` | base: boundary unchanged by this ordinary-branch fix; head: extracted ordered-transparent branch emitted one status card and one body block | +| ordinary assistant without `_statusCard` | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | boundary `tests/test_status_command_card.py::test_render_messages_keeps_body_only_boundary_without_status_card PASSED` | base: boundary unchanged by the bug contract; head: extracted current branch returned only `

Final report

` | diff --git a/static/ui.js b/static/ui.js index 71de6a9a..191b8002 100644 --- a/static/ui.js +++ b/static/ui.js @@ -16408,9 +16408,10 @@ function renderMessages(options){ if((isCompactWorklogMode()||isTransparentStream())&&_assistantThinkingBelongsInWorklog(m, rawIdx, toolCallAssistantIdxs)) assistantThinking.set(rawIdx, thinkingText); else if(window._showThinking!==false) seg.insertAdjacentHTML('beforeend', _thinkingCardHtml(thinkingText)); } - const hasVisibleBody=!!(String(content||'').trim()||filesHtml||statusHtml||recoveryHtml); + const hasVisibleBody=!!(String(content||'').trim()||filesHtml||recoveryHtml); if(statusHtml){ seg.insertAdjacentHTML('beforeend', statusHtml); + if(hasVisibleBody) seg.insertAdjacentHTML('beforeend', `${filesHtml}
${bodyHtml}
${footHtml}`); }else if(hasVisibleBody){ seg.insertAdjacentHTML('beforeend', `${filesHtml}
${bodyHtml}
${footHtml}`); }else if(!(thinkingText&&window._showThinking!==false&&!isSimplifiedToolCalling())){ diff --git a/tests/test_status_command_card.py b/tests/test_status_command_card.py index ca3fa44d..5d5a616f 100644 --- a/tests/test_status_command_card.py +++ b/tests/test_status_command_card.py @@ -5,7 +5,13 @@ ephemeral assistant-style card from already-loaded session/profile/model data. It must not round-trip through the agent or a status endpoint just to draw the card. """ +import json import pathlib +import shutil +import subprocess +import tempfile + +import pytest REPO_ROOT = pathlib.Path(__file__).parent.parent @@ -14,6 +20,7 @@ UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8") STYLE_CSS = (REPO_ROOT / "static" / "style.css").read_text(encoding="utf-8") I18N_JS = (REPO_ROOT / "static" / "i18n.js").read_text(encoding="utf-8") MESSAGES_JS = (REPO_ROOT / "static" / "messages.js").read_text(encoding="utf-8") +NODE = shutil.which("node") def _function_body(src: str, name: str) -> str: @@ -31,6 +38,190 @@ def _function_body(src: str, name: str) -> str: raise AssertionError(f"Could not extract {name}()") +def _run_node(source: str) -> str: + if NODE is None: + pytest.skip("node not on PATH") + with tempfile.NamedTemporaryFile( + "w", suffix=".cjs", encoding="utf-8", dir=REPO_ROOT, delete=False + ) as script: + script.write(source) + script_path = pathlib.Path(script.name) + try: + result = subprocess.run( + [NODE, str(script_path)], + cwd=str(REPO_ROOT), + capture_output=True, + text=True, + timeout=30, + ) + finally: + script_path.unlink(missing_ok=True) + if result.returncode != 0: + raise RuntimeError(result.stderr) + return result.stdout.strip() + + +def _status_render_probe() -> dict: + render_body = _function_body(UI_JS, "renderMessages") + return json.loads( + _run_node( + f""" +const renderBody = {render_body!r}; + +function extractBlock(startMarker, endMarker) {{ + const start = renderBody.indexOf(startMarker); + if (start === -1) throw new Error(`missing start marker: ${{startMarker}}`); + const end = renderBody.indexOf(endMarker, start); + if (end === -1) throw new Error(`missing end marker: ${{endMarker}}`); + return renderBody.slice(start, end); +}} + +function makeSeg() {{ + return {{ + html: '', + classList: {{ + added: [], + add(value) {{ this.added.push(value); }}, + }}, + insertAdjacentHTML(_where, html) {{ this.html += String(html || ''); }}, + }}; +}} + +const ordinaryBlock = extractBlock( + "const hasVisibleBody=!!(String(content||'').trim()||filesHtml||recoveryHtml);", + "_assistantTurnBlocks(currentAssistantTurn).appendChild(seg);" +); +const orderedBlock = extractBlock( + "if(isLastTextPart&&statusHtml){{", + "blocks.appendChild(orderedSeg);" +); + +function runBaseOrdinary(opts) {{ + const seg = makeSeg(); + const content = opts.content || ''; + const filesHtml = opts.filesHtml || ''; + const statusHtml = opts.statusHtml || ''; + const recoveryHtml = opts.recoveryHtml || ''; + const bodyHtml = opts.bodyHtml || ''; + const footHtml = opts.footHtml || ''; + const thinkingText = opts.thinkingText || ''; + const window = {{ _showThinking: opts.showThinking !== false }}; + function isSimplifiedToolCalling() {{ return !!opts.simplified; }} + const hasVisibleBody = !!(String(content || '').trim() || filesHtml || statusHtml || recoveryHtml); + if (statusHtml) {{ + seg.insertAdjacentHTML('beforeend', statusHtml); + }} else if (hasVisibleBody) {{ + seg.insertAdjacentHTML('beforeend', `${{filesHtml}}
${{bodyHtml}}
${{footHtml}}`); + }} else if (!(thinkingText && window._showThinking !== false && !isSimplifiedToolCalling())) {{ + seg.classList.add('assistant-segment-anchor'); + }} + return seg; +}} + +function runHeadOrdinary(opts) {{ + const seg = makeSeg(); + const content = opts.content || ''; + const filesHtml = opts.filesHtml || ''; + const statusHtml = opts.statusHtml || ''; + const recoveryHtml = opts.recoveryHtml || ''; + const bodyHtml = opts.bodyHtml || ''; + const footHtml = opts.footHtml || ''; + const thinkingText = opts.thinkingText || ''; + const window = {{ _showThinking: opts.showThinking !== false }}; + function isSimplifiedToolCalling() {{ return !!opts.simplified; }} + eval(ordinaryBlock); + return seg; +}} + +function runOrderedSpecial(opts) {{ + const orderedSeg = makeSeg(); + const isLastTextPart = opts.isLastTextPart ?? true; + const statusHtml = opts.statusHtml || ''; + const filesHtml = opts.filesHtml || ''; + const footHtml = opts.footHtml || ''; + const partBodyHtml = opts.partBodyHtml || ''; + eval(orderedBlock); + return orderedSeg; +}} + +const ordinaryInput = {{ + content: 'Final report', + bodyHtml: '

Final report

', + statusHtml: 'limit', + filesHtml: '', + footHtml: '', + recoveryHtml: '', +}}; +const ordinaryBase = runBaseOrdinary(ordinaryInput); +const ordinaryHead = runHeadOrdinary(ordinaryInput); +const statusOnly = runHeadOrdinary({{ + statusHtml: 'limit', + content: '', + bodyHtml: '', + filesHtml: '', + footHtml: '', + recoveryHtml: '', +}}); +const ordinaryNoStatus = runHeadOrdinary({{ + statusHtml: '', + content: 'Final report', + bodyHtml: '

Final report

', + filesHtml: '', + footHtml: '', + recoveryHtml: '', +}}); +const filesOnlyWithStatus = runHeadOrdinary({{ + statusHtml: 'limit', + content: '', + bodyHtml: '', + filesHtml: '
artifact.txt
', + footHtml: '', + recoveryHtml: '', +}}); +const orderedSpecial = runOrderedSpecial({{ + statusHtml: 'limit', + filesHtml: '', + footHtml: '', + partBodyHtml: '

Final report

', +}}); + +console.log(JSON.stringify({{ + ordinary: {{ + baseHtml: ordinaryBase.html, + headHtml: ordinaryHead.html, + headHasBody: ordinaryHead.html.includes('

Final report

'), + headStatusBeforeBody: + ordinaryHead.html.indexOf('limit') < + ordinaryHead.html.indexOf('

Final report

'), + baseDropsBody: !ordinaryBase.html.includes('

Final report

'), + }}, + statusOnly: {{ + html: statusOnly.html, + isCardOnly: statusOnly.html === 'limit', + }}, + ordinaryNoStatus: {{ + html: ordinaryNoStatus.html, + isBodyOnly: + ordinaryNoStatus.html === '

Final report

', + }}, + filesOnlyWithStatus: {{ + html: filesOnlyWithStatus.html, + hasFiles: filesOnlyWithStatus.html.includes('
artifact.txt
'), + statusBeforeFiles: + filesOnlyWithStatus.html.indexOf('limit') < + filesOnlyWithStatus.html.indexOf('
artifact.txt
'), + }}, + orderedSpecial: {{ + html: orderedSpecial.html, + statusCount: (orderedSpecial.html.match(//g) || []).length, + bodyCount: (orderedSpecial.html.match(/
/g) || []).length, + }}, +}})); +""" + ) + ) + + def test_status_command_is_registered_with_help_text(): assert "{name:'status'" in COMMANDS_JS assert "desc:t('cmd_status')" in COMMANDS_JS @@ -81,6 +272,43 @@ def test_render_messages_treats_status_card_as_visible_assistant_content(): assert "statusHtml" in render_body +def test_render_messages_keeps_final_report_when_status_card_present(): + probe = _status_render_probe() + ordinary = probe["ordinary"] + assert ordinary["baseDropsBody"] is True + assert ordinary["headHasBody"] is True + assert ordinary["headStatusBeforeBody"] is True + + +def test_render_messages_keeps_status_only_card_boundary(): + probe = _status_render_probe() + status_only = probe["statusOnly"] + assert status_only["isCardOnly"] is True + assert status_only["html"] == "limit" + + +def test_render_messages_keeps_body_only_boundary_without_status_card(): + probe = _status_render_probe() + ordinary_no_status = probe["ordinaryNoStatus"] + assert ordinary_no_status["isBodyOnly"] is True + assert ordinary_no_status["html"] == "

Final report

meta
" + + +def test_render_messages_keeps_files_when_status_card_present(): + probe = _status_render_probe() + files_only = probe["filesOnlyWithStatus"] + assert files_only["hasFiles"] is True + assert files_only["statusBeforeFiles"] is True + + +def test_ordered_transparent_branch_keeps_single_status_card_boundary(): + probe = _status_render_probe() + ordered = probe["orderedSpecial"] + assert ordered["statusCount"] == 1 + assert ordered["bodyCount"] == 1 + assert ordered["html"] == "limit

Final report

meta
" + + def test_status_card_styles_exist(): assert ".status-card" in STYLE_CSS assert ".status-card-grid" in STYLE_CSS From 3c53afeb1286796113baafca57286b04dc54a5bf Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Wed, 29 Jul 2026 20:54:51 +0000 Subject: [PATCH 2/2] chore: drop stray .claude pr-sweep proof artifact (not for repo) --- .claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md diff --git a/.claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md b/.claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md deleted file mode 100644 index e867dd74..00000000 --- a/.claude/pr-sweep/webui-PR-TARGET-6467-PROOF.md +++ /dev/null @@ -1,7 +0,0 @@ -| surface | command run | observed result | base/head | -| --- | --- | --- | --- | -| ordinary assistant `_statusCard` plus non-empty final report | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | `tests/test_status_command_card.py::test_render_messages_keeps_final_report_when_status_card_present PASSED` and `12 passed in 4.50s` | base: simulated legacy ordinary-branch probe emitted only `limit` and dropped `

Final report

`; head: extracted current ordinary branch emitted the status card before `

Final report

` in the same assistant segment | -| status-only assistant `_statusCard` | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | boundary `tests/test_status_command_card.py::test_render_messages_keeps_status_only_card_boundary PASSED` | base: boundary unchanged by the bug contract; head: extracted current branch returned only `limit` | -| assistant files plus `_statusCard` with empty text body | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | boundary `tests/test_status_command_card.py::test_render_messages_keeps_files_when_status_card_present PASSED` | base: the legacy ordinary branch returned only `limit` when the assistant segment carried files without text. head: extracted current branch kept `
artifact.txt
` after the status card in the same assistant segment | -| special status-first branch | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | boundary `tests/test_status_command_card.py::test_ordered_transparent_branch_keeps_single_status_card_boundary PASSED` | base: boundary unchanged by this ordinary-branch fix; head: extracted ordered-transparent branch emitted one status card and one body block | -| ordinary assistant without `_statusCard` | `C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_status_command_card.py -v --timeout=60` | boundary `tests/test_status_command_card.py::test_render_messages_keeps_body_only_boundary_without_status_card PASSED` | base: boundary unchanged by the bug contract; head: extracted current branch returned only `

Final report

` |