Under gateway.multiplex_profiles every secondary profile's config loads inside _profile_runtime_scope, yet every apply_yaml_config_fn hook (feishu, matrix, whatsapp, slack, dingtalk, discord non-gate keys, telegram non-gate keys) and gateway/config_loader.py::bridge_core_env_settings still wrote os.environ there. First-writer-wins: the first secondary with a require_mention / allowlist / allow_bots / reactions block made that policy the DEFAULT profile's (live: TELEGRAM_REQUIRE_MENTION written from a secondary load), and secondaries read the default's env for the same keys. - gateway/platforms/_shared.py::yaml_env_setter: the one env-write shape for YAML->env bridges — env wins, skipped under an active secondary scope. - Every hook now seeds its values into the profile's PlatformConfig.extra and uses yaml_env_setter; bridge_core_env_settings seeds telegram/signal require_mention into extra and skips the env write under scope. - Readers that bypassed extra/scope now consult extra first (matrix flags + session_scope, slack reactions, telegram reactions/mention_patterns/_extra_bool, discord reactions/auto_thread/history_backfill/approval_mentions/allow_mentions, feishu allow_bots, dingtalk mention_patterns, signal require_mention). Salvages the shape of PR #100604 (whatsapp, earliest report #80099), #100435 (discord) and #100448 (telegram) by @nftpoetrist on top of current main. Fixes #80099 Co-authored-by: nftpoetrist <264138787+nftpoetrist@users.noreply.github.com>
75 lines
3.0 KiB
Python
75 lines
3.0 KiB
Python
"""Per-profile isolation for the remaining process-global sinks under gateway.multiplex_profiles:
|
|
Yuanbao auto-sethome env write, the config ``terminal.env_passthrough`` allowlist, and the runner-level
|
|
Slack ignored-channel fail-safe (which only had the DEFAULT profile's GatewayConfig)."""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from agent.secret_scope import reset_secret_scope, set_multiplex_active, set_secret_scope
|
|
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
|
|
|
|
|
|
def _under_secondary(home, fn):
|
|
set_multiplex_active(True)
|
|
home_token = set_hermes_home_override(str(home))
|
|
secret_token = set_secret_scope({})
|
|
try:
|
|
return fn()
|
|
finally:
|
|
reset_secret_scope(secret_token)
|
|
reset_hermes_home_override(home_token)
|
|
set_multiplex_active(False)
|
|
|
|
|
|
def test_yuanbao_auto_sethome_from_secondary_stays_in_its_config(tmp_path, monkeypatch):
|
|
from gateway.platforms.yuanbao import AutoSetHomeMiddleware
|
|
|
|
monkeypatch.delenv("YUANBAO_HOME_CHANNEL", raising=False)
|
|
secondary = tmp_path / "profiles" / "b2"
|
|
secondary.mkdir(parents=True)
|
|
(secondary / "config.yaml").write_text("{}\n")
|
|
|
|
class Adapter:
|
|
name = "yuanbao-b2"
|
|
|
|
class Ctx:
|
|
chat_id = "dm:tenant-b2"
|
|
chat_name = "b2"
|
|
|
|
_under_secondary(secondary, lambda: AutoSetHomeMiddleware._persist_home(Adapter(), Ctx()))
|
|
|
|
assert "YUANBAO_HOME_CHANNEL" not in os.environ
|
|
assert "dm:tenant-b2" in (secondary / "config.yaml").read_text()
|
|
|
|
|
|
def test_env_passthrough_allowlist_follows_the_active_profile(tmp_path, monkeypatch):
|
|
import tools.env_passthrough as ep
|
|
|
|
default_home = tmp_path / "hermes"
|
|
secondary = default_home / "profiles" / "b2"
|
|
secondary.mkdir(parents=True)
|
|
(default_home / "config.yaml").write_text("terminal:\n env_passthrough: [FOO_DEFAULT]\n")
|
|
(secondary / "config.yaml").write_text("terminal:\n env_passthrough: [FOO_B2]\n")
|
|
monkeypatch.setenv("HERMES_HOME", str(default_home))
|
|
ep._config_passthrough.clear()
|
|
|
|
assert ep._load_config_passthrough() == {"FOO_DEFAULT"}
|
|
assert _under_secondary(secondary, ep._load_config_passthrough) == {"FOO_B2"}
|
|
assert ep._load_config_passthrough() == {"FOO_DEFAULT"}
|
|
|
|
|
|
def test_slack_ignored_channels_use_the_routed_adapters_list(monkeypatch):
|
|
from gateway.config import GatewayConfig, Platform, PlatformConfig
|
|
from gateway.run import _is_slack_ignored_channel
|
|
|
|
monkeypatch.delenv("SLACK_IGNORED_CHANNELS", raising=False)
|
|
default_cfg = GatewayConfig(platforms={Platform.SLACK: PlatformConfig(enabled=True, extra={"ignored_channels": ["C_DEFAULT"]})})
|
|
|
|
class SecondaryAdapter:
|
|
config = PlatformConfig(enabled=True, extra={"ignored_channels": ["C_B2"]})
|
|
|
|
assert _is_slack_ignored_channel(default_cfg, "C_B2", SecondaryAdapter())
|
|
assert not _is_slack_ignored_channel(default_cfg, "C_DEFAULT", SecondaryAdapter())
|
|
# No routed adapter (legacy callers): the process-level config still rules.
|
|
assert _is_slack_ignored_channel(default_cfg, "C_DEFAULT")
|