ab2f4602de
Breaks the two import cycles that forced Protocol stand-ins in the F821 sweep, so the two sites now name the real types. gateway/platforms/event.py (new leaf): MessageType, ProcessingOutcome, MessageEvent moved out of base.py verbatim. Their only dependency is gateway.session.SessionSource; base.py imported helpers.py at module level, so helpers could not name MessageEvent. Now TextBatchAggregator is typed by the real MessageEvent. 249 importers repointed (`from gateway.platforms.base import` -> `.event`, preserving each import's layout); gateway.platforms.__init__ re-exports from .event. The three revert-scheduled PLUGIN-COMPAT pointers that named these symbols (gateway.slash_commands → MessageType, dingtalk → MessageType, photon → ProcessingOutcome) and their COMPAT_MANIFEST rows now target gateway.platforms.event. Docs updated: ADDING_A_PLATFORM.md, adding-platform-adapters.md (en + zh-Hans). tools/mcp_tool_sampling.py: ElicitationHandler no longer holds a back-reference to its MCPServerTask (mcp_tool imports sampling, so the task type cannot be named there). It only ever read owner._pending_call_context, so it takes `call_context: Callable[[], Context | None]` and MCPServerTask passes `lambda: self._pending_call_context`. The consent call is one `functools.partial`, run directly or inside the captured Context. ty on the 11 touched production files vs origin/main: 0 new diagnostics, 14 resolved. (The one `source: SessionSource = None` diagnostic moves with the class; typing it Optional exposes ~60 unguarded call sites — separate follow-up.) Tests: tests/gateway + tests/plugins + tests/tools + touched files, 18,235 passed; the 31 failures reproduce identically on origin/main (macOS /private/tmp, systemd socket, long-path fixtures, live-service tests).
98 lines
3.2 KiB
Python
98 lines
3.2 KiB
Python
"""Regression tests for #75164 — /steer fallback must not overwrite FIFO head.
|
|
|
|
Uses the real gateway test fixtures (tests/gateway/test_steer_command.py) to
|
|
exercise both /steer fallback paths through _handle_message, proving that a
|
|
pre-queued Q1/Q2 survives a /steer Q3 dispatch.
|
|
|
|
Covers:
|
|
- pending-sentinel path (agent not booted yet) -> _enqueue_fifo preserves Q1
|
|
- no-steer() path (agent lacks steer()) -> _enqueue_fifo preserves Q1
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from gateway.run import _AGENT_PENDING_SENTINEL
|
|
from tests.gateway.test_steer_command import (
|
|
_make_event,
|
|
_make_runner,
|
|
_session_entry,
|
|
_make_source,
|
|
)
|
|
from gateway.session import build_session_key
|
|
from unittest.mock import MagicMock
|
|
|
|
|
|
def _prequeue(runner, adapter, sk):
|
|
"""Pre-stage Q1 in the pending slot and Q2 in the overflow tail."""
|
|
from gateway.platforms.event import MessageEvent, MessageType
|
|
|
|
# Ensure _queued_events is initialized (mirrors GatewayRunner.__init__)
|
|
if not hasattr(runner, "_queued_events"):
|
|
runner._queued_events = {}
|
|
|
|
q1 = MessageEvent(
|
|
text="Q1",
|
|
source=_make_source(),
|
|
message_id="m1",
|
|
channel_context="ctx1",
|
|
message_type=MessageType.TEXT,
|
|
)
|
|
q2 = MessageEvent(
|
|
text="Q2",
|
|
source=_make_source(),
|
|
message_id="m2",
|
|
channel_context="ctx2",
|
|
message_type=MessageType.TEXT,
|
|
)
|
|
# Q1 -> pending slot (head), Q2 -> overflow tail
|
|
runner._enqueue_fifo(sk, q1, adapter)
|
|
runner._enqueue_fifo(sk, q2, adapter)
|
|
assert adapter._pending_messages[sk].text == "Q1"
|
|
assert runner._queued_events[sk][0].text == "Q2"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_steer_pending_sentinel_preserves_fifo_head():
|
|
"""Issue #75164: /steer Q3 must not overwrite pre-queued Q1."""
|
|
runner, adapter = _make_runner(_session_entry())
|
|
sk = build_session_key(_make_source())
|
|
runner._running_agents[sk] = _AGENT_PENDING_SENTINEL
|
|
|
|
_prequeue(runner, adapter, sk)
|
|
|
|
result = await runner._handle_message(
|
|
_make_event("/steer wait up", channel_context="ctx3")
|
|
)
|
|
assert result is not None
|
|
assert "queued" in result.lower()
|
|
|
|
# Q1 still in slot, Q2 in overflow, Q3 appended to overflow — order preserved
|
|
assert adapter._pending_messages[sk].text == "Q1"
|
|
overflow = runner._queued_events[sk]
|
|
texts = [msg.text for msg in overflow]
|
|
assert texts == ["Q2", "wait up"], f"FIFO order broken: {texts}"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_steer_no_steer_method_preserves_fifo_head():
|
|
"""Issue #75164: no-steer() fallback must not overwrite pre-queued Q1."""
|
|
runner, adapter = _make_runner(_session_entry())
|
|
sk = build_session_key(_make_source())
|
|
|
|
# Bare mock with NO steer() method
|
|
runner._running_agents[sk] = MagicMock(spec=[])
|
|
|
|
_prequeue(runner, adapter, sk)
|
|
|
|
result = await runner._handle_message(
|
|
_make_event("/steer fallback", channel_context="ctx3")
|
|
)
|
|
assert result is not None
|
|
assert "queued" in result.lower()
|
|
|
|
assert adapter._pending_messages[sk].text == "Q1"
|
|
overflow = runner._queued_events[sk]
|
|
texts = [msg.text for msg in overflow]
|
|
assert texts == ["Q2", "fallback"], f"FIFO order broken: {texts}"
|