diff --git a/CHANGELOG.md b/CHANGELOG.md index f83d2346..c9328df5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,6 @@ ### Fixed - Surface live tool activity when Hermes Agent reports tools through its dedicated `tool_start_callback` / `tool_complete_callback` path, so browser chat shows the existing running tool cards instead of appearing idle until the final answer. -- Keep the composer usable if a transient restart/proxy blip prevents `commands.js` from loading while `boot.js` still attaches slash-command listeners. ## [v0.51.93] — 2026-05-19 — Release BQ (stage-386 — 10-PR full sweep batch — RFC Slice 4 runner/sidecar gate + workspace tree toggle width CSS variable + settled file:// markdown link rendering + prompt-cache coverage percentage fix + terminal shell shutdown reap + configured model picker provider preservation + profile-aware assistant display names + state.db reconciliation slice 1 + queued-message cross-session drain fix + stale-stream writeback supersede) diff --git a/api/streaming.py b/api/streaming.py index 76b92b9e..e7ca4a82 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -3525,6 +3525,13 @@ def _run_agent_streaming( args_snap = _tool_args_snapshot(args) + # Modern Hermes Agent builds can call both tool_progress_callback + # and the structured tool_start/tool_complete callbacks for the + # same tool. Prefer the structured path when it is supported so + # the browser receives one tid-tagged tool card per real call. + if event_type in (None, 'tool.started') and 'tool_start_callback' in _agent_params: + return + if event_type in (None, 'tool.started'): _live_tool_calls.append({ 'name': name, @@ -3560,6 +3567,9 @@ def _run_agent_streaming( pass return + if event_type == 'tool.completed' and 'tool_complete_callback' in _agent_params: + return + if event_type == 'tool.completed': for live_tc in reversed(_live_tool_calls): if live_tc.get('done'): diff --git a/static/boot.js b/static/boot.js index eda383a4..9a30fe8e 100644 --- a/static/boot.js +++ b/static/boot.js @@ -1,20 +1,3 @@ -// Slash-command helpers normally come from commands.js, which is loaded before -// boot.js. If a restart/proxy blip makes that asset fail while boot.js loads, -// keep the composer usable instead of throwing ReferenceError on input/keydown. -(function(){ - function dropdown(){ return document.getElementById('cmdDropdown'); } - if(typeof window.hideCmdDropdown!=='function'){ - window.hideCmdDropdown=function(){ - const dd=dropdown(); - if(dd){ dd.classList.remove('open'); dd.style.display='none'; } - }; - } - if(typeof window.showCmdDropdown!=='function') window.showCmdDropdown=function(){}; - if(typeof window.getMatchingCommands!=='function') window.getMatchingCommands=function(){ return []; }; - if(typeof window.navigateCmdDropdown!=='function') window.navigateCmdDropdown=function(){}; - if(typeof window.selectCmdDropdownItem!=='function') window.selectCmdDropdownItem=function(){}; -})(); - async function cancelStream(){ const streamId = S.activeStreamId; if(!streamId) return; diff --git a/tests/test_command_asset_fallbacks.py b/tests/test_command_asset_fallbacks.py deleted file mode 100644 index 2d1bcefc..00000000 --- a/tests/test_command_asset_fallbacks.py +++ /dev/null @@ -1,30 +0,0 @@ -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[1] - - -def _read(relpath: str) -> str: - return (ROOT / relpath).read_text(encoding="utf-8") - - -def test_boot_installs_safe_slash_command_fallbacks_when_commands_asset_missing(): - boot = _read("static/boot.js") - - assert "If a restart/proxy blip makes that asset fail" in boot - for name in ( - "hideCmdDropdown", - "showCmdDropdown", - "getMatchingCommands", - "navigateCmdDropdown", - "selectCmdDropdownItem", - ): - assert f"typeof window.{name}!==\'function\'" in boot or f"typeof window.{name}!='function'" in boot - - -def test_commands_asset_still_owns_real_dropdown_implementation(): - commands = _read("static/commands.js") - - assert "function hideCmdDropdown()" in commands - assert "function showCmdDropdown(" in commands - assert "function getMatchingCommands(" in commands diff --git a/tests/test_live_tool_callback_events.py b/tests/test_live_tool_callback_events.py index 0e5dd3ee..74c8b343 100644 --- a/tests/test_live_tool_callback_events.py +++ b/tests/test_live_tool_callback_events.py @@ -52,6 +52,16 @@ def test_tool_complete_callback_emits_existing_tool_complete_sse_event_with_tool assert "_checkpoint_activity[0] += 1" in block +def test_legacy_progress_events_are_suppressed_when_structured_callbacks_are_wired(): + src = _read("api/streaming.py") + block = _function_block(src, "on_tool") + + assert "event_type in (None, 'tool.started') and 'tool_start_callback' in _agent_params" in block + assert "event_type == 'tool.completed' and 'tool_complete_callback' in _agent_params" in block + assert block.index("'tool_start_callback' in _agent_params") < block.index("put('tool'") + assert block.index("'tool_complete_callback' in _agent_params") < block.index("put('tool_complete'") + + def test_tool_callback_events_keep_existing_frontend_event_contract(): messages = _read("static/messages.js") ui = _read("static/ui.js")