fix(gateway): show lock-hold reason when /compress no-ops

This commit is contained in:
Ethan 2026-07-03 17:03:57 +10:00 committed by Teknium
parent 65145d9c5a
commit eed6bb14bc
2 changed files with 51 additions and 0 deletions

View file

@ -3563,6 +3563,19 @@ class GatewaySlashCommandsMixin:
defer_context_engine_notification=True,
)
)
# If _compress_context returned unchanged because a
# concurrent compression lock is held, tell the user
# clearly instead of showing the misleading
# "No changes from compression" no-op text.
_lock_skipped = getattr(tmp_agent, "_compression_skipped_due_to_lock", None)
if _lock_skipped:
holder = _lock_skipped if isinstance(_lock_skipped, str) else "unknown"
return (
f"⏳ Compression already in progress for this session "
f"(holder: {holder}). Please wait for it to finish."
)
if partial and tail:
compressed = rejoin_compressed_head_and_tail(compressed, tail)

View file

@ -70,6 +70,7 @@ async def test_compress_command_reports_noop_without_success_banner():
agent_instance.context_compressor.has_content_to_compress.return_value = True
agent_instance.session_id = "sess-1"
agent_instance._compress_context.return_value = (list(history), "")
agent_instance._compression_skipped_due_to_lock = False
def _estimate(messages, **_kwargs):
assert messages == history
@ -149,6 +150,7 @@ async def test_compress_command_explains_when_token_estimate_rises():
agent_instance.context_compressor.has_content_to_compress.return_value = True
agent_instance.session_id = "sess-1"
agent_instance._compress_context.return_value = (compressed, "")
agent_instance._compression_skipped_due_to_lock = False
def _estimate(messages, **_kwargs):
if messages == history:
@ -199,6 +201,7 @@ async def test_compress_command_appends_warning_when_compression_aborts():
)
agent_instance.session_id = "sess-1"
agent_instance._compress_context.return_value = (compressed, "")
agent_instance._compression_skipped_due_to_lock = False
def _estimate(messages, **_kwargs):
if messages == history:
@ -261,6 +264,7 @@ async def test_compress_command_surfaces_aux_model_failure_even_when_recovered()
)
agent_instance.session_id = "sess-1"
agent_instance._compress_context.return_value = (compressed, "")
agent_instance._compression_skipped_due_to_lock = False
def _estimate(messages, **_kwargs):
if messages == history:
@ -319,6 +323,7 @@ async def test_compress_command_passes_session_db_and_persists_rotated_session()
return compressed, ""
agent_instance._compress_context.side_effect = _compress
agent_instance._compression_skipped_due_to_lock = False
def _estimate(messages, **_kwargs):
if messages == history:
@ -385,6 +390,7 @@ async def test_compress_command_does_not_repoint_session_when_transcript_write_f
return compressed, ""
agent_instance._compress_context.side_effect = _compress
agent_instance._compression_skipped_due_to_lock = False
def _estimate(messages, **_kwargs):
return 100
@ -440,6 +446,7 @@ async def test_compress_command_in_place_skips_destructive_rewrite():
agent_instance._last_compaction_in_place = True
agent_instance.session_id = "sess-1"
agent_instance._compress_context.return_value = (compressed, "")
agent_instance._compression_skipped_due_to_lock = False
def _estimate(messages, **_kwargs):
if messages == history:
@ -482,6 +489,7 @@ async def test_compress_command_preserves_platform_and_gateway_session_key():
agent_instance.context_compressor.has_content_to_compress.return_value = True
agent_instance.session_id = "sess-1"
agent_instance._compress_context.return_value = (list(history), "")
agent_instance._compression_skipped_due_to_lock = False
with (
patch("gateway.run._resolve_runtime_agent_kwargs", return_value={"api_key": "test-key"}),
@ -517,6 +525,7 @@ async def test_compress_command_overrides_stale_resolver_identity():
agent_instance.context_compressor.has_content_to_compress.return_value = True
agent_instance.session_id = "sess-1"
agent_instance._compress_context.return_value = (list(history), "")
agent_instance._compression_skipped_due_to_lock = False
# Resolver injects a WRONG platform and a stale session key.
runtime = {"api_key": "test-key", "platform": "discord", "gateway_session_key": "stale-key"}
@ -581,3 +590,32 @@ async def test_compress_command_passes_tool_messages_to_compressor():
# Assistant tool_calls stubs (content=None) must survive too, or the
# tool message would dangle without its call.
assert any(m.get("tool_calls") for m in passed), "assistant tool_calls stub dropped"
@pytest.mark.asyncio
async def test_compress_command_surfaces_lock_skip():
"""When _compress_context skips due to a concurrent lock, the gateway
handler must surface a clear message, not the misleading no-op text."""
history = _make_history()
runner = _make_runner(history)
agent_instance = MagicMock()
agent_instance.shutdown_memory_provider = MagicMock()
agent_instance.close = MagicMock()
agent_instance._cached_system_prompt = ""
agent_instance.tools = None
agent_instance.context_compressor.has_content_to_compress.return_value = True
agent_instance.session_id = "sess-1"
agent_instance._compress_context.return_value = (list(history), "")
agent_instance._compression_skipped_due_to_lock = "pid=99999"
with (
patch("gateway.run._resolve_runtime_agent_kwargs", return_value={"api_key": "***"}),
patch("gateway.run._resolve_gateway_model", return_value="test-model"),
patch("run_agent.AIAgent", return_value=agent_instance),
patch("agent.model_metadata.estimate_request_tokens_rough", return_value=100),
):
result = await runner._handle_compress_command(_make_event())
assert "Compression already in progress" in result
assert "pid=99999" in result
assert "No changes from compression" not in result