fix(gateway): preserve platform + gateway_session_key on /compress temp agent
Manual /compress built a temporary AIAgent without the originating platform / stable gateway session key, so an external context engine ingested the retained transcript tail as source=cli during /compress and again as the real platform on resume (duplicate cli,telegram rows). Pass platform=_platform_config_key(source.platform) + the in-scope gateway_session_key, mirroring the normal gateway turn. Assigned into runtime_kwargs (single-valued, authoritative) so they neither collide into a duplicate-kwarg TypeError nor lose to a stale resolver value. Fixes #50422.
This commit is contained in:
parent
00ec3b1884
commit
2a04137322
2 changed files with 96 additions and 0 deletions
|
|
@ -3056,6 +3056,17 @@ class GatewaySlashCommandsMixin:
|
|||
from agent.model_metadata import estimate_request_tokens_rough
|
||||
|
||||
session_key = self._session_key_for_source(source)
|
||||
# Preserve the same platform + stable gateway session identity that a
|
||||
# normal gateway turn passes (gateway/run.py main turn), so external
|
||||
# context engines bind this temporary compression agent to the
|
||||
# original platform conversation instead of falling back to an
|
||||
# unbound/default "cli" host source — see #50422. _platform_config_key
|
||||
# maps LOCAL->"cli" exactly like the live turn, avoiding a new
|
||||
# "local" vs "cli" mismatch.
|
||||
from gateway.run import _platform_config_key
|
||||
platform_key = (
|
||||
_platform_config_key(source.platform) if source.platform else None
|
||||
)
|
||||
model, runtime_kwargs = self._resolve_session_agent_runtime(
|
||||
source=source,
|
||||
session_key=session_key,
|
||||
|
|
@ -3082,6 +3093,21 @@ class GatewaySlashCommandsMixin:
|
|||
partial = False
|
||||
head = msgs
|
||||
|
||||
# Bind the temporary compression agent to the originating source's
|
||||
# platform + stable gateway session key. These are *authoritative*
|
||||
# identity invariants (derived from `source`), so assign them into
|
||||
# runtime_kwargs directly rather than via setdefault: a value already
|
||||
# present there from the resolver would be a placeholder/stale
|
||||
# identity and must not win. Assigning (vs passing a second explicit
|
||||
# kwarg) also keeps each key single-valued, avoiding a "got multiple
|
||||
# values for keyword argument" TypeError. platform is only set when
|
||||
# known: for a source without platform metadata we leave it unset so
|
||||
# AIAgent's default (platform=None -> source "cli") applies, exactly
|
||||
# the prior behavior. _resolve_session_agent_runtime does not set
|
||||
# either key today, so in practice this just adds them.
|
||||
if platform_key is not None:
|
||||
runtime_kwargs["platform"] = platform_key
|
||||
runtime_kwargs["gateway_session_key"] = session_key
|
||||
tmp_agent = AIAgent(
|
||||
**runtime_kwargs,
|
||||
model=model,
|
||||
|
|
|
|||
|
|
@ -413,3 +413,73 @@ async def test_compress_command_in_place_write_failure_reports_error():
|
|||
runner.session_store._save.assert_not_called()
|
||||
agent_instance.shutdown_memory_provider.assert_called_once()
|
||||
agent_instance.close.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_compress_command_preserves_platform_and_gateway_session_key():
|
||||
"""The temporary compression agent must carry the originating source's
|
||||
platform and stable gateway session key, matching a normal gateway turn.
|
||||
Without them ``_session_source_for_agent`` falls back to a default "cli"
|
||||
host source, so an external context engine misattributes the retained
|
||||
transcript tail and later duplicates it on resume (#50422)."""
|
||||
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), "")
|
||||
|
||||
with (
|
||||
patch("gateway.run._resolve_runtime_agent_kwargs", return_value={"api_key": "test-key"}),
|
||||
patch("gateway.run._resolve_gateway_model", return_value="test-model"),
|
||||
patch("run_agent.AIAgent", return_value=agent_instance) as mock_agent,
|
||||
patch("agent.model_metadata.estimate_request_tokens_rough", return_value=100),
|
||||
):
|
||||
await runner._handle_compress_command(_make_event())
|
||||
|
||||
assert mock_agent.call_count == 1
|
||||
_, kwargs = mock_agent.call_args
|
||||
# Platform preserved as the live turn's config key (TELEGRAM -> "telegram"),
|
||||
# not the unbound "cli"/"local" fallback.
|
||||
assert kwargs.get("platform") == "telegram"
|
||||
# Stable gateway session key preserved, identical to a normal gateway turn.
|
||||
assert kwargs.get("gateway_session_key") == runner._session_key_for_source(_make_source())
|
||||
assert kwargs["gateway_session_key"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_compress_command_overrides_stale_resolver_identity():
|
||||
"""If the resolver already supplies platform/gateway_session_key, the
|
||||
construction must (a) not raise "got multiple values for keyword argument",
|
||||
and (b) let the originating-source identity win — a stale/placeholder
|
||||
resolver value must not defeat the attribution fix."""
|
||||
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), "")
|
||||
|
||||
# Resolver injects a WRONG platform and a stale session key.
|
||||
runtime = {"api_key": "test-key", "platform": "discord", "gateway_session_key": "stale-key"}
|
||||
with (
|
||||
patch("gateway.run._resolve_runtime_agent_kwargs", return_value=runtime),
|
||||
patch("gateway.run._resolve_gateway_model", return_value="test-model"),
|
||||
patch("run_agent.AIAgent", return_value=agent_instance) as mock_agent,
|
||||
patch("agent.model_metadata.estimate_request_tokens_rough", return_value=100),
|
||||
):
|
||||
await runner._handle_compress_command(_make_event()) # must not raise
|
||||
|
||||
assert mock_agent.call_count == 1
|
||||
_, kwargs = mock_agent.call_args
|
||||
# Source-derived identity overrides the stale resolver values, passed once.
|
||||
assert kwargs["platform"] == "telegram"
|
||||
assert kwargs["gateway_session_key"] == runner._session_key_for_source(_make_source())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue