fix(whatsapp): default-profile UnscopedSecretError fallback + full bridge env set

Follow-ups on the #75382 salvage (review findings):
- _wenv/_get_wsecret now catch UnscopedSecretError and fall back to
  os.getenv for the DEFAULT profile's adapter, which constructs and sends
  outside any _profile_runtime_scope under multiplexing — a bare
  get_secret would crash its WhatsApp path (fixing one profile by
  breaking another). Same pattern as Slack SLACK_APP_TOKEN (#59739) and
  the Matrix recovery key. Scoped misses still return the default — no
  cross-profile borrow.
- bridge_env overlay extended to the full WHATSAPP_* set bridge.js
  consumes (DEBUG, FORWARD_OWNER_MESSAGES, REPLY_PREFIX,
  MAX_MESSAGE_LENGTH, CHUNK_DELAY_MS, SEND_TIMEOUT_MS).
- Removed the always-true conditional on WHATSAPP_MODE injection.
This commit is contained in:
Teknium 2026-08-01 21:15:58 -07:00
parent 4f4ea9a6de
commit 5438e9c629
2 changed files with 37 additions and 4 deletions

View File

@ -37,7 +37,26 @@ import os
import re
from typing import Any, Dict, Optional
from agent.secret_scope import get_secret as _get_wsecret
from agent.secret_scope import UnscopedSecretError as _UnscopedSecretError
from agent.secret_scope import get_secret as _scoped_get_secret
def _get_wsecret(name, default=None):
"""Scope-aware WHATSAPP_* read with the default-profile startup fallback.
Secondary profiles run under ``_profile_runtime_scope`` -- the scope is
authoritative and a scoped miss returns ``default`` (no cross-profile
borrow). The DEFAULT profile's adapter constructs and sends *unscoped*
under multiplexing, where a bare ``get_secret`` would raise
``UnscopedSecretError`` and crash its WhatsApp path; there ``os.environ``
is that profile's own value, so fall back to it. Same pattern as the
Slack ``SLACK_APP_TOKEN`` read (#59739).
"""
try:
val = _scoped_get_secret(name, default)
except _UnscopedSecretError:
val = os.getenv(name)
return val if val is not None else default
logger = logging.getLogger(__name__)

View File

@ -43,8 +43,16 @@ def _wenv(name: str, default: str = "") -> str:
``get_secret`` honors the active ``_profile_runtime_scope`` so secondary
profiles see their own credentials.
"""
from agent.secret_scope import get_secret
val = get_secret(name)
from agent.secret_scope import UnscopedSecretError, get_secret
try:
val = get_secret(name)
except UnscopedSecretError:
# DEFAULT profile's adapter constructs/connects outside any
# _profile_runtime_scope under multiplexing; os.environ is that
# profile's own value there. Same pattern as Slack SLACK_APP_TOKEN
# (#59739) and the Matrix recovery key. A *scoped* miss still
# returns the default (no cross-profile borrow).
val = os.getenv(name)
return val if val is not None else default
logger = logging.getLogger(__name__)
@ -683,7 +691,7 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter):
# (which reads process.env.WHATSAPP_MODE etc.) sees the profile's
# own configuration instead of falling back to self-chat defaults.
_profile_wa_mode = _wenv("WHATSAPP_MODE", "self-chat")
if _profile_wa_mode != "self-chat" or _profile_wa_mode:
if _profile_wa_mode:
bridge_env["WHATSAPP_MODE"] = _profile_wa_mode
for _key in (
"WHATSAPP_ALLOWED_USERS", "WHATSAPP_ALLOW_FROM",
@ -691,6 +699,12 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter):
"WHATSAPP_GROUP_ALLOWED_USERS", "WHATSAPP_GROUP_ALLOW_FROM",
"WHATSAPP_REQUIRE_MENTION", "WHATSAPP_MENTION_PATTERNS",
"WHATSAPP_FREE_RESPONSE_CHATS",
# Full set bridge.js consumes -- without these a secondary
# profile's bridge silently reverts to defaults for debug,
# forwarding, prefixes, and send pacing.
"WHATSAPP_DEBUG", "WHATSAPP_FORWARD_OWNER_MESSAGES",
"WHATSAPP_REPLY_PREFIX", "WHATSAPP_MAX_MESSAGE_LENGTH",
"WHATSAPP_CHUNK_DELAY_MS", "WHATSAPP_SEND_TIMEOUT_MS",
):
_v = _wenv(_key)
if _v: