feat(gateway): plugin-platform-safe deserialization via resolve_platform_id
Without this, plugin-registered platforms (like molecule-a2a) survive the runtime hot path but die on daemon restart: SessionSource.from_dict calls Platform(value) directly, which raises ValueError for any name not in the closed enum. Restored sessions with plugin-platform origins either crash the restore loop or silently drop the platform field. Add resolve_platform_id() — Platform-first, falls back to PluginPlatformIdentifier ONLY when a currently-loaded plugin actually claims the name. Bare typos and corrupted state still raise so silent state-corruption can't slip past restore. Wire it into the three known from_dict call sites: - gateway/session.py:SessionSource.from_dict - gateway/session.py:SessionEntry.from_dict (drops the silent debug-log swallow that left platform=None on unknown values) - gateway/config.py:HomeChannel.from_dict All existing gateway/test_session.py round-trip tests still pass, including test_invalid_platform_raises (the narrow fallback preserves loud-fail semantics for genuinely unknown names).
This commit is contained in:
parent
17451dc77a
commit
047de4a668
@ -90,8 +90,9 @@ class HomeChannel:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "HomeChannel":
|
||||
from hermes_cli.plugins import resolve_platform_id
|
||||
return cls(
|
||||
platform=Platform(data["platform"]),
|
||||
platform=resolve_platform_id(data["platform"]),
|
||||
chat_id=str(data["chat_id"]),
|
||||
name=data.get("name", "Home"),
|
||||
)
|
||||
|
||||
@ -123,8 +123,9 @@ class SessionSource:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "SessionSource":
|
||||
from hermes_cli.plugins import resolve_platform_id
|
||||
return cls(
|
||||
platform=Platform(data["platform"]),
|
||||
platform=resolve_platform_id(data["platform"]),
|
||||
chat_id=str(data["chat_id"]),
|
||||
chat_name=data.get("chat_name"),
|
||||
chat_type=data.get("chat_type", "dm"),
|
||||
@ -409,10 +410,8 @@ class SessionEntry:
|
||||
|
||||
platform = None
|
||||
if data.get("platform"):
|
||||
try:
|
||||
platform = Platform(data["platform"])
|
||||
except ValueError as e:
|
||||
logger.debug("Unknown platform value %r: %s", data["platform"], e)
|
||||
from hermes_cli.plugins import resolve_platform_id
|
||||
platform = resolve_platform_id(data["platform"])
|
||||
|
||||
return cls(
|
||||
session_key=data["session_key"],
|
||||
|
||||
@ -883,6 +883,32 @@ class PluginPlatformIdentifier:
|
||||
return f"PluginPlatformIdentifier({self.value!r})"
|
||||
|
||||
|
||||
def resolve_platform_id(value: str):
|
||||
"""Resolve a platform-name string to a ``Platform`` enum member or
|
||||
a ``PluginPlatformIdentifier``.
|
||||
|
||||
Used by ``from_dict`` deserializers for ``SessionSource``,
|
||||
``SessionEntry``, ``HomeChannel`` etc. Without this fallback,
|
||||
daemon restart would lose every session whose platform was
|
||||
contributed by a plugin (e.g., ``molecule-a2a``) because the
|
||||
bare ``Platform(value)`` call raises ``ValueError`` for unknown
|
||||
enum members.
|
||||
|
||||
The fallback is intentionally narrow: an unknown name is only
|
||||
accepted as a plugin identifier if a currently-loaded plugin has
|
||||
actually claimed that name via ``register_platform_adapter``. Bare
|
||||
typos and corrupted state still raise ``ValueError`` as before, so
|
||||
silent state-corruption doesn't slip past restore.
|
||||
"""
|
||||
from gateway.config import Platform # late import to avoid cycle
|
||||
try:
|
||||
return Platform(value)
|
||||
except ValueError:
|
||||
if get_plugin_platform_adapter(value) is not None:
|
||||
return PluginPlatformIdentifier(value)
|
||||
raise
|
||||
|
||||
|
||||
def get_plugin_commands() -> Dict[str, dict]:
|
||||
"""Return the full plugin commands dict (name → {handler, description, plugin}).
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user