mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-16 09:31:37 +00:00
fix(whatsapp-cloud): review follow-ups for #43921
- nous_subscription: gate the STT managed-default flip on openai-audio
entitlement and skip when a local backend (faster-whisper or custom
command) works; new _local_stt_backend_available() helper + tests
- whatsapp_cloud: WHATSAPP_CLOUD_{DM_POLICY,ALLOW_FROM,GROUP_POLICY,
GROUP_ALLOW_FROM} env overrides so both adapters can run in parallel;
normalize allowlist entries (JID/punctuation) to bare wa_id
- whatsapp_cloud: wrap per-message event build in try/except (dedup-marked
wamids would be silently dropped on Meta's batch retry otherwise)
- whatsapp_cloud: validate media_id before URL/filename interpolation,
delete transient .ogg after voice upload, FIFO-cap interactive-button
state dicts and per-chat wamid cache
- whatsapp_common: '# **Title**' headers no longer double-wrap asterisks
- setup wizard: read access token / app secret via getpass on TTYs
- docs: new WHATSAPP_CLOUD_* gating env vars
This commit is contained in:
parent
2ecb4e62bb
commit
52c7976f40
8 changed files with 319 additions and 35 deletions
|
|
@ -226,6 +226,24 @@ def _stt_label(current_provider: str) -> str:
|
|||
return mapping.get(current_provider or "local", current_provider or "Local faster-whisper")
|
||||
|
||||
|
||||
def _local_stt_backend_available() -> bool:
|
||||
"""Whether a local STT backend could serve transcription right now.
|
||||
|
||||
True when faster-whisper is importable or a custom local STT command
|
||||
is configured. Used both for feature detection and to stop
|
||||
``apply_nous_managed_defaults`` from flipping a working local setup
|
||||
to the managed gateway.
|
||||
"""
|
||||
if get_env_value("HERMES_LOCAL_STT_COMMAND"):
|
||||
return True
|
||||
try:
|
||||
from tools.transcription_tools import _HAS_FASTER_WHISPER
|
||||
|
||||
return bool(_HAS_FASTER_WHISPER)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _resolve_browser_feature_state(
|
||||
*,
|
||||
browser_tool_enabled: bool,
|
||||
|
|
@ -772,10 +790,22 @@ def apply_nous_managed_defaults(
|
|||
# (requires `pip install faster-whisper`); for Nous subscribers we
|
||||
# flip it to "openai" so the managed audio gateway handles transcription
|
||||
# via the same auth as TTS. Skipped when the user has explicitly
|
||||
# configured STT or has direct credentials for a non-managed provider.
|
||||
if not features.stt.explicit_configured and not (
|
||||
get_env_value("GROQ_API_KEY")
|
||||
or get_env_value("MISTRAL_API_KEY")
|
||||
# configured STT, has direct credentials for a non-managed provider,
|
||||
# has a working local backend (faster-whisper installed or a custom
|
||||
# local command — strong intent signal that "local" was a choice, not
|
||||
# just the DEFAULT_CONFIG seed), or isn't entitled to the managed
|
||||
# "openai-audio" category (flipping would point at a gateway that
|
||||
# refuses them, silently breaking voice transcription).
|
||||
if (
|
||||
not features.stt.explicit_configured
|
||||
and not _local_stt_backend_available()
|
||||
and not (
|
||||
resolve_openai_audio_api_key()
|
||||
or get_env_value("GROQ_API_KEY")
|
||||
or get_env_value("MISTRAL_API_KEY")
|
||||
)
|
||||
and features.account_info is not None
|
||||
and features.account_info.tool_gateway_entitled_for("openai-audio")
|
||||
):
|
||||
stt_cfg["provider"] = "openai"
|
||||
changed.add("stt")
|
||||
|
|
|
|||
|
|
@ -162,17 +162,25 @@ def _validate_access_token(value: str) -> tuple[bool, Optional[str]]:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _prompt(message: str, default: Optional[str] = None) -> str:
|
||||
def _prompt(message: str, default: Optional[str] = None, secret: bool = False) -> str:
|
||||
"""Read one line of input. Returns "" on EOF / Ctrl+C / empty input.
|
||||
|
||||
The ``default`` parameter is shown to the user but NOT auto-applied
|
||||
on empty input — callers handle the "user kept existing" case
|
||||
explicitly so they can distinguish between a real value and a
|
||||
display preview (e.g. ``"abc12345..."`` for masked secrets).
|
||||
|
||||
``secret=True`` reads via ``getpass`` so credentials are not echoed
|
||||
to the terminal (or left in scrollback).
|
||||
"""
|
||||
try:
|
||||
suffix = f" [{default}]" if default else ""
|
||||
raw = input(f"{message}{suffix}: ").strip()
|
||||
if secret and sys.stdin.isatty():
|
||||
import getpass
|
||||
|
||||
raw = getpass.getpass(f"{message}{suffix} (input hidden): ").strip()
|
||||
else:
|
||||
raw = input(f"{message}{suffix}: ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print()
|
||||
return ""
|
||||
|
|
@ -185,6 +193,7 @@ def _prompt_validated(
|
|||
*,
|
||||
current: Optional[str] = None,
|
||||
help_text: Optional[str] = None,
|
||||
secret: bool = False,
|
||||
) -> Optional[str]:
|
||||
"""Repeat the prompt until the user enters a valid value or aborts.
|
||||
|
||||
|
|
@ -198,7 +207,7 @@ def _prompt_validated(
|
|||
attempts = 0
|
||||
while True:
|
||||
attempts += 1
|
||||
value = _prompt(f" → {message}", default=current)
|
||||
value = _prompt(f" → {message}", default=current, secret=secret)
|
||||
if not value:
|
||||
return None
|
||||
ok, reason = validator(value)
|
||||
|
|
@ -295,6 +304,7 @@ def run_whatsapp_cloud_setup() -> int:
|
|||
"Access Token",
|
||||
_validate_access_token,
|
||||
current=current_display,
|
||||
secret=True,
|
||||
help_text=(
|
||||
"Two options for getting one:\n\n"
|
||||
" (a) TEMP — App Dashboard → WhatsApp → API Setup →\n"
|
||||
|
|
@ -335,6 +345,7 @@ def run_whatsapp_cloud_setup() -> int:
|
|||
"App Secret",
|
||||
_validate_app_secret,
|
||||
current=current_secret_display,
|
||||
secret=True,
|
||||
help_text=(
|
||||
"Found in: App Dashboard → Settings → Basic →\n"
|
||||
"'App secret' field (click 'Show', enter your Facebook password).\n\n"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue