mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
fix(gateway): prevent duplicate Photon sidecar storms
This commit is contained in:
parent
88ff722f94
commit
cf19ac8ff3
2 changed files with 43 additions and 12 deletions
|
|
@ -10342,12 +10342,15 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
if owner is not None:
|
||||
logger.error(
|
||||
"Profile '%s' and '%s' both configure %s with the same "
|
||||
"credential — refusing to start the duplicate (a single "
|
||||
"bot token cannot be polled twice). Give each profile its "
|
||||
"own %s credential.",
|
||||
"credential — refusing to start the duplicate (one "
|
||||
"credential cannot be consumed twice). Give each profile "
|
||||
"its own %s credential.",
|
||||
owner, profile_name, platform.value, platform.value,
|
||||
)
|
||||
await self._safe_adapter_disconnect(adapter, platform)
|
||||
# This adapter has not connected and therefore owns no
|
||||
# resources to clean up. Calling disconnect here can mutate
|
||||
# the shared platform state and, for a same-credential Photon
|
||||
# adapter, shut down the primary profile's live sidecar.
|
||||
continue
|
||||
claimed[(platform, fp)] = profile_name
|
||||
|
||||
|
|
@ -10591,13 +10594,23 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
def _adapter_credential_fingerprint(adapter: Any) -> Optional[str]:
|
||||
"""Return a stable, log-safe fingerprint of an adapter's credential.
|
||||
|
||||
Used only to detect two profiles claiming the same bot token. Returns a
|
||||
salted hash (never the token itself) of the adapter's primary
|
||||
credential, or None when no credential is discoverable (in which case
|
||||
we don't attempt conflict detection for it).
|
||||
Used only to detect two profiles claiming the same platform credential.
|
||||
Returns a salted hash (never the credential itself) of the adapter's
|
||||
primary credential, or None when no credential is discoverable (in
|
||||
which case we don't attempt conflict detection for it).
|
||||
"""
|
||||
token = None
|
||||
for attr in ("token", "bot_token", "_token", "api_token", "_bot_token"):
|
||||
for attr in (
|
||||
"token",
|
||||
"bot_token",
|
||||
"_token",
|
||||
"api_token",
|
||||
"_bot_token",
|
||||
# Photon/Spectrum authenticates with project credentials instead
|
||||
# of a bot token. Including its secret keeps multiplexed profiles
|
||||
# from spawning competing sidecars for the same account and port.
|
||||
"_project_secret",
|
||||
):
|
||||
val = getattr(adapter, attr, None)
|
||||
if isinstance(val, str) and val.strip():
|
||||
token = val.strip()
|
||||
|
|
|
|||
|
|
@ -41,6 +41,22 @@ class TestCredentialFingerprint:
|
|||
self.bot_token = "alt-token"
|
||||
assert GatewayRunner._adapter_credential_fingerprint(_AltAdapter()) is not None
|
||||
|
||||
def test_reads_photon_project_secret(self):
|
||||
class _PhotonAdapter:
|
||||
def __init__(self, secret):
|
||||
self._project_secret = secret
|
||||
|
||||
fp1 = GatewayRunner._adapter_credential_fingerprint(
|
||||
_PhotonAdapter("shared-project-secret")
|
||||
)
|
||||
fp2 = GatewayRunner._adapter_credential_fingerprint(
|
||||
_PhotonAdapter("shared-project-secret")
|
||||
)
|
||||
|
||||
assert fp1 == fp2
|
||||
assert fp1 is not None
|
||||
assert "shared-project-secret" not in fp1
|
||||
|
||||
def test_reads_platform_config_token(self):
|
||||
class _Config:
|
||||
token = "config-token"
|
||||
|
|
@ -718,8 +734,10 @@ class TestSecondaryProfileConfigHandling:
|
|||
assert connect_calls == [(relay, Platform.RELAY)]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_secondary_same_config_token_is_refused(self, monkeypatch):
|
||||
"""Adapters that keep their token on config still trip the mux guard."""
|
||||
async def test_secondary_same_config_token_is_refused_without_disconnect(
|
||||
self, monkeypatch
|
||||
):
|
||||
"""A never-connected duplicate must not disturb shared live state."""
|
||||
from gateway.config import GatewayConfig, Platform, PlatformConfig
|
||||
|
||||
class _ConfigTokenAdapter:
|
||||
|
|
@ -762,7 +780,7 @@ class TestSecondaryProfileConfigHandling:
|
|||
)
|
||||
|
||||
assert connected == 0
|
||||
assert duplicate.disconnected is True
|
||||
assert duplicate.disconnected is False
|
||||
assert runner._profile_adapters["reviewer"] == {}
|
||||
|
||||
def test_port_binding_set_covers_known_listeners(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue