fix(telegram): let an explicit TELEGRAM_REACTIONS beat the materialized YAML default

545e74d0ea made _reactions_enabled consult extra.reactions before the env
var, and _apply_yaml_config seeds extra["reactions"] whenever the YAML key
is present — including the stock reactions: false every install
materializes. The documented TELEGRAM_REACTIONS=true switch therefore
became a silent no-op after the 0.21.2 update (#109032), contradicting
yaml_env_setter's "explicit env wins over YAML" contract.

Read the scoped env first and fall back to the profile's own YAML: under
multiplex a scoped miss returns the default instead of another profile's
process-env value (#72348), so only a scoped/env hit counts as explicit
and per-profile isolation is unchanged.

Fixes #109032

(cherry picked from commit 2bd5a0a5c0a5f9630fd82f133def65f225f653a3)
This commit is contained in:
liuhao1024
2026-09-12 19:39:32 +08:00
committed by Teknium
parent abf4706384
commit d92490a4e4
2 changed files with 81 additions and 3 deletions

View File

@@ -6392,10 +6392,19 @@ class TelegramAdapter(BasePlatformAdapter):
# -- Message reactions (processing lifecycle) --
def _reactions_enabled(self) -> bool:
"""Reactions enabled via ``extra.reactions`` (YAML, per profile) or TELEGRAM_REACTIONS."""
configured = self.config.extra.get("reactions")
"""Reactions enabled via TELEGRAM_REACTIONS or ``extra.reactions`` (YAML, per profile).
An explicitly set env var wins over YAML — the same rule ``yaml_env_setter`` documents for
the YAML→env bridge — so the stock ``reactions: false`` every install materializes cannot
silently kill a documented ``TELEGRAM_REACTIONS=true`` (#109032). Under multiplex a scoped
miss returns the default instead of another profile's process-env value (#72348), so only
a scoped/env hit counts as explicit; otherwise the profile's own YAML decides.
"""
configured = _scoped_gate_env("TELEGRAM_REACTIONS", "")
if not configured:
configured = self.config.extra.get("reactions")
if configured is None:
configured = _scoped_gate_env("TELEGRAM_REACTIONS", "false")
return False
return str(configured).lower() not in {"false", "0", "no"}
async def _set_reaction(self, chat_id: str, message_id: str, emoji: Optional[str]) -> bool:

View File

@@ -53,6 +53,75 @@ def test_reactions_enabled_when_set_true(monkeypatch):
assert adapter._reactions_enabled() is True
def test_explicit_env_wins_over_materialized_yaml_default(monkeypatch):
"""TELEGRAM_REACTIONS=true must beat the stock ``reactions: false`` in config.yaml (#109032).
Fresh installs materialize the whole default config tree, so ``_apply_yaml_config`` seeds
``extra["reactions"] = False`` even when the user never chose a value; the reader must still
honour the explicitly set env var, like ``yaml_env_setter`` documents for the bridge.
"""
monkeypatch.setenv("TELEGRAM_REACTIONS", "true")
adapter = _make_adapter()
adapter.config.extra["reactions"] = False
assert adapter._reactions_enabled() is True
def test_bridged_yaml_false_without_explicit_env_still_disables(monkeypatch):
"""With no explicit env the YAML→env bridge writes 'false'; reactions stay off."""
monkeypatch.setenv("TELEGRAM_REACTIONS", "false")
adapter = _make_adapter()
adapter.config.extra["reactions"] = False
assert adapter._reactions_enabled() is False
def test_yaml_true_enables_when_env_unset(monkeypatch):
"""An explicit ``reactions: true`` in config.yaml enables reactions without any env var."""
monkeypatch.delenv("TELEGRAM_REACTIONS", raising=False)
adapter = _make_adapter()
adapter.config.extra["reactions"] = True
assert adapter._reactions_enabled() is True
def test_explicit_env_false_wins_over_yaml_true(monkeypatch):
"""An explicit TELEGRAM_REACTIONS=false also wins over a YAML ``reactions: true``."""
monkeypatch.setenv("TELEGRAM_REACTIONS", "false")
adapter = _make_adapter()
adapter.config.extra["reactions"] = True
assert adapter._reactions_enabled() is False
def test_scoped_miss_does_not_leak_default_profile_env(monkeypatch):
"""Under multiplex a scoped miss must not read another profile's process-env value (#72348)."""
from agent.secret_scope import reset_secret_scope, set_multiplex_active, set_secret_scope
monkeypatch.setenv("TELEGRAM_REACTIONS", "true") # default profile's bridged value
adapter = _make_adapter()
adapter.config.extra["reactions"] = False # this profile's own YAML
set_multiplex_active(True)
token = set_secret_scope({"TELEGRAM_BOT_TOKEN": "222:b2"})
try:
assert adapter._reactions_enabled() is False
finally:
reset_secret_scope(token)
set_multiplex_active(False)
def test_scoped_env_hit_wins_over_own_yaml(monkeypatch):
"""A secondary profile's own scoped TELEGRAM_REACTIONS=true beats its YAML ``reactions: false``."""
from agent.secret_scope import reset_secret_scope, set_multiplex_active, set_secret_scope
monkeypatch.setenv("TELEGRAM_REACTIONS", "false") # default profile's value
adapter = _make_adapter()
adapter.config.extra["reactions"] = False
set_multiplex_active(True)
token = set_secret_scope({"TELEGRAM_BOT_TOKEN": "222:b2", "TELEGRAM_REACTIONS": "true"})
try:
assert adapter._reactions_enabled() is True
finally:
reset_secret_scope(token)
set_multiplex_active(False)
# ── _set_reaction ────────────────────────────────────────────────────