mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
fix(gateway): seed plugin extras before is_connected gate (#31703)
Follow-up to 54e61f933. The plugin enablement gate calls
``entry.is_connected(probe_cfg)`` BEFORE ``env_enablement_fn`` runs,
and the probe is built as ``existing_cfg or PlatformConfig()`` — empty
extras, ``enabled=False``.
For plugins whose ``is_connected`` reads ``config.extra`` instead
of env vars directly, that probe is a misrepresentation of what the
platform will look like after enablement. Google Chat's
``_is_connected`` short-circuits on ``config.enabled`` and inspects
``config.extra["project_id"]`` / ``config.extra["subscription_name"]``
— both False on the default probe even when the user has set
``GOOGLE_CHAT_PROJECT_ID`` and ``GOOGLE_CHAT_SUBSCRIPTION_NAME``. Result:
Google Chat silently fails the gate on every env-var-only setup.
Build a candidate probe that mirrors what the platform will look like
post-enablement:
- pre-call ``env_enablement_fn`` and layer its result into the probe's
``extra`` (without mutating any existing platform config)
- pass ``enabled=True`` on the probe — we're asking "would this BE
configured if we let it in?" not "is it currently enabled?"
- reuse the same seeded extras when we commit the platform to
``config.platforms`` (avoids calling ``env_enablement_fn`` twice)
Discord/IRC/Teams/LINE/ntfy/Simplex ``_is_connected`` hooks read env
vars directly, so they are unaffected. This change only restores
Google Chat on env-var-only setups while keeping the original #31116
Discord-no-token block intact.
All 6 shipped ``env_enablement_fn`` implementations were audited and
are pure reads (no ``os.environ`` writes), so running them earlier in
the loop has no observable side effects.
Tests: 2 new in tests/gateway/test_platform_registry.py covering
extras-seeded-before-is_connected and don't-leak-extras-on-gate-fail.
693 tests across 11 adjacent suites pass (platform_registry, config,
google_chat, matrix, discord_connect, ntfy_plugin, simplex_plugin,
line_plugin, irc_adapter, teams, gateway_platform_gating).
Refs #31116.
This commit is contained in:
parent
514f5020c7
commit
396ee69032
2 changed files with 157 additions and 26 deletions
|
|
@ -1837,6 +1837,25 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
continue
|
||||
platform = Platform(entry.name)
|
||||
existing_cfg = config.platforms.get(platform)
|
||||
# Seed candidate extras from ``env_enablement_fn`` so plugins
|
||||
# whose ``is_connected`` reads ``config.extra`` (e.g. Google
|
||||
# Chat's ``_is_connected`` checks ``config.extra["project_id"]``)
|
||||
# see the same state they will after enablement. Without this,
|
||||
# Google-Chat-on-env-vars-only setups silently fail the gate
|
||||
# below even though the user is configured. Plugins whose
|
||||
# ``is_connected`` reads env vars directly (Discord, IRC,
|
||||
# Teams, LINE, ntfy, Simplex) are unaffected; this only
|
||||
# restores Google Chat.
|
||||
seed_for_probe = None
|
||||
if entry.env_enablement_fn is not None:
|
||||
try:
|
||||
seed_for_probe = entry.env_enablement_fn()
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
"env_enablement_fn for %s raised: %s", entry.name, e
|
||||
)
|
||||
seed_for_probe = None
|
||||
|
||||
# Only consult is_connected for platforms that are NOT already
|
||||
# explicitly configured in YAML / env (existing_cfg with
|
||||
# enabled=True means the user wrote it themselves or another
|
||||
|
|
@ -1844,7 +1863,35 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
if existing_cfg is None or not existing_cfg.enabled:
|
||||
if entry.is_connected is not None:
|
||||
try:
|
||||
probe_cfg = existing_cfg or PlatformConfig()
|
||||
# Probe with ``enabled=True`` since we're asking
|
||||
# "would this plugin BE configured if we enabled
|
||||
# it?" not "is it currently enabled?". Google
|
||||
# Chat's ``_is_connected`` short-circuits on
|
||||
# ``config.enabled`` being False, which on the
|
||||
# default ``PlatformConfig()`` would fail the
|
||||
# gate even with proper env vars set.
|
||||
if existing_cfg is not None:
|
||||
probe_cfg = existing_cfg
|
||||
if not probe_cfg.enabled:
|
||||
probe_cfg = PlatformConfig(
|
||||
enabled=True,
|
||||
extra=dict(probe_cfg.extra or {}),
|
||||
)
|
||||
else:
|
||||
probe_cfg = PlatformConfig(enabled=True)
|
||||
if isinstance(seed_for_probe, dict) and seed_for_probe:
|
||||
# Don't mutate ``existing_cfg``; the probe gets
|
||||
# a transient view with env-seeded extras layered
|
||||
# on top of whatever's already there.
|
||||
probe_extra = dict(getattr(probe_cfg, "extra", {}) or {})
|
||||
for k, v in seed_for_probe.items():
|
||||
if k == "home_channel":
|
||||
continue
|
||||
probe_extra.setdefault(k, v)
|
||||
probe_cfg = PlatformConfig(
|
||||
enabled=True,
|
||||
extra=probe_extra,
|
||||
)
|
||||
configured = bool(entry.is_connected(probe_cfg))
|
||||
except Exception as exc:
|
||||
logger.debug(
|
||||
|
|
@ -1862,31 +1909,26 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
if platform not in config.platforms:
|
||||
config.platforms[platform] = PlatformConfig()
|
||||
config.platforms[platform].enabled = True
|
||||
# Seed extras from env if the plugin opted in.
|
||||
if entry.env_enablement_fn is not None:
|
||||
try:
|
||||
seed = entry.env_enablement_fn()
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
"env_enablement_fn for %s raised: %s", entry.name, e
|
||||
# Commit env-seeded extras onto the now-enabled platform.
|
||||
# We've already called ``env_enablement_fn`` above (for the
|
||||
# probe); reuse that result instead of calling it twice.
|
||||
if isinstance(seed_for_probe, dict) and seed_for_probe:
|
||||
seed = dict(seed_for_probe)
|
||||
# Extract the home_channel dict (if provided) so we wire it
|
||||
# up as a proper HomeChannel dataclass. Everything else is
|
||||
# merged into ``extra``.
|
||||
home = seed.pop("home_channel", None)
|
||||
config.platforms[platform].extra.update(seed)
|
||||
if isinstance(home, dict) and home.get("chat_id"):
|
||||
config.platforms[platform].home_channel = HomeChannel(
|
||||
platform=platform,
|
||||
chat_id=str(home["chat_id"]),
|
||||
name=str(home.get("name") or "Home"),
|
||||
thread_id=(
|
||||
str(home["thread_id"])
|
||||
if home.get("thread_id")
|
||||
else None
|
||||
),
|
||||
)
|
||||
seed = None
|
||||
if isinstance(seed, dict) and seed:
|
||||
# Extract the home_channel dict (if provided) so we wire it
|
||||
# up as a proper HomeChannel dataclass. Everything else is
|
||||
# merged into ``extra``.
|
||||
home = seed.pop("home_channel", None)
|
||||
config.platforms[platform].extra.update(seed)
|
||||
if isinstance(home, dict) and home.get("chat_id"):
|
||||
config.platforms[platform].home_channel = HomeChannel(
|
||||
platform=platform,
|
||||
chat_id=str(home["chat_id"]),
|
||||
name=str(home.get("name") or "Home"),
|
||||
thread_id=(
|
||||
str(home["thread_id"])
|
||||
if home.get("thread_id")
|
||||
else None
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug("Plugin platform enable pass failed: %s", e)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue