fix(gateway): allow Feishu websocket mode in multiplex profiles

Feishu was unconditionally listed in _PORT_BINDING_PLATFORM_VALUES,
causing the multiplexer to reject ALL Feishu secondary profiles. But
Feishu in websocket mode (the default) uses an outbound WebSocket
connection and does NOT bind an HTTP port — only webhook/callback mode
needs a listener.

Add _platform_binds_port() helper that checks connection-dependent
platforms (currently only Feishu) against their actual config before
raising MultiplexConfigError. Feishu websocket profiles are now allowed;
Feishu webhook profiles still raise as before.

Fixes #52563
This commit is contained in:
liuhao1024 2026-06-26 01:00:12 +08:00 committed by Teknium
parent 6b1267c2e4
commit 9cb3569e97
3 changed files with 95 additions and 1 deletions

View file

@ -333,6 +333,29 @@ PORT_BINDING_PLATFORM_VALUES = frozenset({
"line",
})
# Platforms whose port-binding status depends on connection mode. Feishu in
# websocket mode (its default) uses an outbound long connection — no listener.
# Only webhook/callback mode binds a port. Maps platform value → the mode
# value that actually binds (#52563).
PORT_BINDING_CONDITIONAL_MODES: dict[str, str] = {
"feishu": "webhook",
}
def platform_binds_port(platform_value: str, extra: Optional[dict] = None) -> bool:
"""Return True when *platform_value* actually binds a port for *extra* config.
Mode-conditional platforms (Feishu) only bind in their listener mode;
everything else in ``PORT_BINDING_PLATFORM_VALUES`` always binds.
"""
if platform_value not in PORT_BINDING_PLATFORM_VALUES:
return False
expected_mode = PORT_BINDING_CONDITIONAL_MODES.get(platform_value)
if expected_mode is not None:
actual = str((extra or {}).get("connection_mode", "websocket")).strip().lower()
return actual == expected_mode
return True
@dataclass
class HomeChannel:

View file

@ -1417,6 +1417,7 @@ from contextlib import contextmanager as _contextmanager
# dashboard's pre-write validation enforces the same policy.
from gateway.config import (
PORT_BINDING_PLATFORM_VALUES as _PORT_BINDING_PLATFORM_VALUES,
platform_binds_port as _platform_binds_port,
)
@ -8802,7 +8803,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
port_binding_platforms = sorted(
platform.value
for platform, platform_config in profile_cfg.platforms.items()
if platform_config.enabled and platform.value in _PORT_BINDING_PLATFORM_VALUES
if platform_config.enabled
and _platform_binds_port(platform.value, platform_config.extra)
)
if port_binding_platforms:
joined = ", ".join(port_binding_platforms)

View file

@ -530,3 +530,72 @@ class TestSecondaryProfileConfigHandling:
"line",
):
assert p in _PORT_BINDING_PLATFORM_VALUES
class TestFeishuPortBindingConditional:
"""Feishu websocket mode does NOT bind a port; only webhook mode does (#52563)."""
@pytest.mark.asyncio
async def test_feishu_websocket_mode_not_rejected(self, monkeypatch):
"""Feishu in websocket mode (the default) should NOT raise MultiplexConfigError."""
from gateway.run import MultiplexConfigError
from gateway.config import GatewayConfig, Platform, PlatformConfig
runner = GatewayRunner.__new__(GatewayRunner)
runner.config = GatewayConfig(multiplex_profiles=True)
runner._profile_adapters = {}
reviewer_cfg = GatewayConfig(multiplex_profiles=True)
reviewer_cfg.platforms = {
Platform.FEISHU: PlatformConfig(
enabled=True,
extra={"app_id": "cli_xxx", "app_secret": "sec", "connection_mode": "websocket"},
),
}
monkeypatch.setattr("gateway.config.load_gateway_config", lambda: reviewer_cfg)
monkeypatch.setattr(runner, "_create_adapter", lambda p, c: None)
connected = await runner._start_one_profile_adapters("reviewer", "/tmp/x", {})
assert connected == 0 # no error, just nothing connected
@pytest.mark.asyncio
async def test_feishu_webhook_mode_raises(self, monkeypatch):
"""Feishu in webhook mode binds a port and should raise MultiplexConfigError."""
from gateway.run import MultiplexConfigError
from gateway.config import GatewayConfig, Platform, PlatformConfig
runner = GatewayRunner.__new__(GatewayRunner)
runner.config = GatewayConfig(multiplex_profiles=True)
runner._profile_adapters = {}
reviewer_cfg = GatewayConfig(multiplex_profiles=True)
reviewer_cfg.platforms = {
Platform.FEISHU: PlatformConfig(
enabled=True,
extra={"app_id": "cli_xxx", "app_secret": "sec", "connection_mode": "webhook"},
),
}
monkeypatch.setattr("gateway.config.load_gateway_config", lambda: reviewer_cfg)
with pytest.raises(MultiplexConfigError) as ei:
await runner._start_one_profile_adapters("reviewer", "/tmp/x", {})
assert "feishu" in str(ei.value)
def test_platform_binds_port_helper(self):
"""Unit test for _platform_binds_port helper."""
from gateway.run import _platform_binds_port
# Non-port-binding platform
assert _platform_binds_port("telegram", {}) is False
# Unconditional port-binding platform
assert _platform_binds_port("webhook", {}) is True
assert _platform_binds_port("api_server", {}) is True
# Feishu: websocket = no port binding
assert _platform_binds_port("feishu", {"connection_mode": "websocket"}) is False
assert _platform_binds_port("feishu", {}) is False # default is websocket
# Feishu: webhook = port binding
assert _platform_binds_port("feishu", {"connection_mode": "webhook"}) is True