fix: show auto-compression running state
This commit is contained in:
committed by
nesquena-hermes
parent
ac8a41bc1f
commit
e31b7e72d6
@@ -1886,6 +1886,29 @@ def _run_agent_streaming(
|
||||
except Exception:
|
||||
logger.debug("Failed to put event to queue")
|
||||
|
||||
def _agent_status_callback(kind, message):
|
||||
"""Bridge Agent lifecycle compression status into WebUI SSE."""
|
||||
_message = str(message or '').strip()
|
||||
_kind = str(kind or '').strip().lower()
|
||||
if not _message:
|
||||
return
|
||||
_lower = _message.lower()
|
||||
_is_compression_start = (
|
||||
_kind == 'lifecycle'
|
||||
and (
|
||||
'preflight compression' in _lower
|
||||
or 'compressing' in _lower
|
||||
or 'compacting context' in _lower
|
||||
or 'context too large' in _lower
|
||||
)
|
||||
)
|
||||
if not _is_compression_start:
|
||||
return
|
||||
put('compressing', {
|
||||
'session_id': session_id,
|
||||
'message': 'Auto-compressing context to continue...',
|
||||
})
|
||||
|
||||
# Initialised here (before any code that may raise) so the outer `finally`
|
||||
# block can safely check `if _checkpoint_stop is not None` even when an
|
||||
# exception fires before the checkpoint thread is created (Issue #765).
|
||||
@@ -2330,6 +2353,8 @@ def _run_agent_streaming(
|
||||
# but guard defensively to avoid TypeError on an older agent build.
|
||||
if 'reasoning_config' in _agent_params and _reasoning_config is not None:
|
||||
_agent_kwargs['reasoning_config'] = _reasoning_config
|
||||
if 'status_callback' in _agent_params:
|
||||
_agent_kwargs['status_callback'] = _agent_status_callback
|
||||
if 'max_tokens' in _agent_params and _max_tokens_cfg is not None:
|
||||
_agent_kwargs['max_tokens'] = _max_tokens_cfg
|
||||
# Params added in newer hermes-agent — skip if not supported
|
||||
@@ -2383,6 +2408,8 @@ def _run_agent_streaming(
|
||||
# objects (put queue, cancel_event) that are new each request.
|
||||
agent.stream_delta_callback = _agent_kwargs.get('stream_delta_callback')
|
||||
agent.tool_progress_callback = _agent_kwargs.get('tool_progress_callback')
|
||||
if hasattr(agent, 'status_callback'):
|
||||
agent.status_callback = _agent_kwargs.get('status_callback')
|
||||
if hasattr(agent, 'reasoning_callback'):
|
||||
agent.reasoning_callback = _agent_kwargs.get('reasoning_callback')
|
||||
if hasattr(agent, 'clarify_callback'):
|
||||
|
||||
BIN
docs/pr-media/1832/auto-compression-running-card.png
Normal file
BIN
docs/pr-media/1832/auto-compression-running-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -1011,6 +1011,24 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
}catch(_){}
|
||||
});
|
||||
|
||||
source.addEventListener('compressing',e=>{
|
||||
// Context auto-compression is starting. Surface the same calm running
|
||||
// compression card as manual /compress while the summarizer LLM call runs.
|
||||
if(!S.session||S.session.session_id!==activeSid) return;
|
||||
let d={};
|
||||
try{ d=JSON.parse(e.data||'{}')||{}; }catch(_){ d={}; }
|
||||
if(d.session_id&&d.session_id!==activeSid) return;
|
||||
if(typeof setCompressionUi==='function'){
|
||||
setCompressionUi({
|
||||
sessionId:activeSid,
|
||||
phase:'running',
|
||||
automatic:true,
|
||||
message:d.message||'Auto-compressing context...',
|
||||
});
|
||||
}
|
||||
if(typeof renderMessages==='function') renderMessages({preserveScroll:true});
|
||||
});
|
||||
|
||||
source.addEventListener('compressed',e=>{
|
||||
// Context was auto-compressed during this turn. Render it through the
|
||||
// same transient compression-card path as manual /compress, without
|
||||
|
||||
@@ -17,6 +17,56 @@ def _compressed_listener_block() -> str:
|
||||
return src[start:end]
|
||||
|
||||
|
||||
def _compressing_listener_block() -> str:
|
||||
src = _read("static/messages.js")
|
||||
start = src.find("source.addEventListener('compressing'")
|
||||
assert start != -1, "compressing SSE listener not found"
|
||||
end = src.find("source.addEventListener('compressed'", start)
|
||||
assert end != -1, "compressed listener after compressing SSE listener not found"
|
||||
return src[start:end]
|
||||
|
||||
|
||||
def test_auto_compression_running_sse_uses_active_session_running_card():
|
||||
block = _compressing_listener_block()
|
||||
|
||||
assert "if(!S.session||S.session.session_id!==activeSid) return;" in block
|
||||
assert "if(d.session_id&&d.session_id!==activeSid) return;" in block
|
||||
assert "try{ d=JSON.parse(e.data||'{}')||{}; }catch(_){ d={}; }" in block
|
||||
assert "setCompressionUi" in block
|
||||
assert "phase:'running'" in block
|
||||
assert "automatic:true" in block
|
||||
assert "message:d.message||'Auto-compressing context...'" in block
|
||||
|
||||
|
||||
def test_auto_compression_running_sse_is_emitted_from_agent_lifecycle_status():
|
||||
src = _read("api/streaming.py")
|
||||
start = src.find("def _agent_status_callback")
|
||||
assert start != -1, "agent status callback bridge not found"
|
||||
end = src.find("# Initialised here", start)
|
||||
assert end != -1, "status callback block end marker not found"
|
||||
block = src[start:end]
|
||||
|
||||
assert "put('compressing'" in block
|
||||
assert "'session_id': session_id" in block
|
||||
assert "'message': 'Auto-compressing context to continue...'" in block
|
||||
assert "'preflight compression'" in block
|
||||
assert "'compressing'" in block
|
||||
assert "'compacting context'" in block
|
||||
assert "'context too large'" in block
|
||||
assert "'status_callback' in _agent_params" in src
|
||||
assert "_agent_kwargs['status_callback'] = _agent_status_callback" in src
|
||||
assert "agent.status_callback = _agent_kwargs.get('status_callback')" in src
|
||||
|
||||
|
||||
def test_auto_compression_completion_transition_is_preserved_after_running_listener():
|
||||
src = _read("static/messages.js")
|
||||
compressing_idx = src.find("source.addEventListener('compressing'")
|
||||
compressed_idx = src.find("source.addEventListener('compressed'")
|
||||
assert compressing_idx != -1 and compressed_idx != -1
|
||||
assert compressing_idx < compressed_idx
|
||||
assert "phase:'done'" in _compressed_listener_block()
|
||||
|
||||
|
||||
def test_auto_compression_sse_uses_transient_card_not_fake_message():
|
||||
"""Auto compression must not inject display-only text into S.messages."""
|
||||
src = _read("static/messages.js")
|
||||
|
||||
Reference in New Issue
Block a user