fix(agent): manual /compress "nothing to compress" gate stays gateway-only
The shared core applied `has_content_to_compress(head) is False -> nothing_to_do` on every surface, where origin/main only had it in the gateway handler. That predicate only knows the local summarizer's window: on CLI/TUI/ACP, `_compress_context(force=True)` still routes codex_app_server sessions to native compaction before any local-compressor check, and `ContextCompressor.compress` commits the phase-1 tool-result prune / blank-echo drop even when no summary window exists -- so the gate wrongly skipped real work there. It is now an opt-in `skip_without_window` that only the gateway passes, restoring each surface's prior behavior. Review follow-up on #109610.
This commit is contained in:
@@ -68,7 +68,7 @@ def estimate_request_tokens(agent: Any, messages: Sequence[Dict[str, Any]]) -> i
|
||||
|
||||
def compress_now(
|
||||
agent: Any, history: Sequence[Dict[str, Any]], request: CompressRequest, *,
|
||||
system_message: Any = None, task_id: str = "default",
|
||||
system_message: Any = None, task_id: str = "default", skip_without_window: bool = False,
|
||||
) -> CompressResult:
|
||||
"""Run one manual compression of ``history`` on ``agent`` and return the outcome; the caller installs
|
||||
``after_messages`` (and re-anchors session ids) — history is never mutated here.
|
||||
@@ -78,7 +78,11 @@ def compress_now(
|
||||
discarded; otherwise the caller must call ``finalize_context_engine_compression_notification(agent,
|
||||
committed=True)`` once its own history transaction commits (``committed=False`` on failure).
|
||||
``system_message=None`` makes ``_compress_context`` rebuild the prompt; passing the cached prompt
|
||||
duplicated the identity block (#15281)."""
|
||||
duplicated the identity block (#15281). ``skip_without_window`` (gateway) answers ``nothing_to_do``
|
||||
when the local compressor sees no summarizable middle; the in-process surfaces leave it off because
|
||||
``_compress_context`` still does useful work there — codex_app_server native compaction, and the
|
||||
phase-1 tool-result prune / blank-echo drop that ``ContextCompressor.compress`` commits even when no
|
||||
summary window exists."""
|
||||
from agent.conversation_compression import finalize_context_engine_compression_notification
|
||||
from agent.manual_compression_feedback import summarize_manual_compression
|
||||
from hermes_cli.partial_compress import (
|
||||
@@ -97,7 +101,7 @@ def compress_now(
|
||||
|
||||
compressor = getattr(agent, "context_compressor", None)
|
||||
has_content = getattr(compressor, "has_content_to_compress", None)
|
||||
if callable(has_content) and has_content(head) is False:
|
||||
if skip_without_window and callable(has_content) and has_content(head) is False:
|
||||
return CompressResult("nothing_to_do", before, before, before_tokens, before_tokens, request)
|
||||
try:
|
||||
compressed, _ = agent._compress_context(
|
||||
|
||||
@@ -550,7 +550,7 @@ class GatewaySessionCommandsMixin:
|
||||
# Not a bare run_in_executor: the profile secret scope is a contextvar the default
|
||||
# executor hop would drop, failing aux-client credential resolution closed.
|
||||
result = await self._run_in_executor_with_context(
|
||||
lambda: compress_now(tmp_agent, msgs, request, system_message=""))
|
||||
lambda: compress_now(tmp_agent, msgs, request, system_message="", skip_without_window=True))
|
||||
if result.status == "nothing_to_do":
|
||||
return t("gateway.compress.nothing_to_do")
|
||||
if result.status != "compressed":
|
||||
|
||||
@@ -87,6 +87,25 @@ def test_every_surface_honours_preview_without_compressing(surface, monkeypatch)
|
||||
agent._compress_context.assert_not_called()
|
||||
|
||||
|
||||
def test_windowless_gate_is_gateway_only_so_in_process_surfaces_still_reach_compress_context():
|
||||
"""``has_content_to_compress`` only knows the local summary window; codex_app_server native compaction
|
||||
and the phase-1 tool-result prune inside ``_compress_context`` do useful work without one, so CLI/TUI/
|
||||
ACP (the default) must not short-circuit on it. The gateway keeps its historical early answer."""
|
||||
agent, history = _agent(), _history()
|
||||
agent.context_compressor = MagicMock()
|
||||
agent.context_compressor.has_content_to_compress.return_value = False
|
||||
agent._compress_context.return_value = (history[:-1], "") # e.g. a pruned tool result, no summary
|
||||
|
||||
default = compress_now(agent, history, parse_compress_args(""))
|
||||
assert default.status == "compressed" and default.removed == 1
|
||||
agent._compress_context.assert_called_once()
|
||||
|
||||
agent._compress_context.reset_mock()
|
||||
gateway = compress_now(agent, history, parse_compress_args(""), system_message="", skip_without_window=True)
|
||||
assert gateway.status == "nothing_to_do" and gateway.after_messages == history
|
||||
agent._compress_context.assert_not_called()
|
||||
|
||||
|
||||
def _coro(value):
|
||||
async def _inner(*_a, **_k):
|
||||
return value
|
||||
|
||||
Reference in New Issue
Block a user