mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-23 05:31:23 +00:00
chore: ruff auto-fix PLR6201 — tuple → set in membership tests (#23937)
Replace with for all literal-tuple membership tests. Set lookup is O(1) vs O(n) for tuple — consistent micro-optimization across the codebase. 608 instances fixed via `ruff --fix --unsafe-fixes`, 0 remaining. 133 files, +626/-626 (net zero).
This commit is contained in:
parent
8c11710314
commit
2ec8d2b42f
133 changed files with 626 additions and 626 deletions
|
|
@ -28,9 +28,9 @@ def _coerce_bool(value: Any, default: bool = True) -> bool:
|
|||
return default
|
||||
if isinstance(value, str):
|
||||
lowered = value.strip().lower()
|
||||
if lowered in ("true", "1", "yes", "on"):
|
||||
if lowered in {"true", "1", "yes", "on"}:
|
||||
return True
|
||||
if lowered in ("false", "0", "no", "off"):
|
||||
if lowered in {"false", "0", "no", "off"}:
|
||||
return False
|
||||
return default
|
||||
return is_truthy_value(value, default=default)
|
||||
|
|
@ -799,7 +799,7 @@ def load_gateway_config() -> GatewayConfig:
|
|||
bridged["group_allow_admin_from"] = platform_cfg["group_allow_admin_from"]
|
||||
if "group_user_allowed_commands" in platform_cfg:
|
||||
bridged["group_user_allowed_commands"] = platform_cfg["group_user_allowed_commands"]
|
||||
if plat in (Platform.DISCORD, Platform.SLACK) and "channel_skill_bindings" in platform_cfg:
|
||||
if plat in {Platform.DISCORD, Platform.SLACK} and "channel_skill_bindings" in platform_cfg:
|
||||
bridged["channel_skill_bindings"] = platform_cfg["channel_skill_bindings"]
|
||||
if "channel_prompts" in platform_cfg:
|
||||
channel_prompts = platform_cfg["channel_prompts"]
|
||||
|
|
@ -1179,7 +1179,7 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
|
||||
# Reply threading mode for Telegram (off/first/all)
|
||||
telegram_reply_mode = os.getenv("TELEGRAM_REPLY_TO_MODE", "").lower()
|
||||
if telegram_reply_mode in ("off", "first", "all"):
|
||||
if telegram_reply_mode in {"off", "first", "all"}:
|
||||
if Platform.TELEGRAM not in config.platforms:
|
||||
config.platforms[Platform.TELEGRAM] = PlatformConfig()
|
||||
config.platforms[Platform.TELEGRAM].reply_to_mode = telegram_reply_mode
|
||||
|
|
@ -1220,14 +1220,14 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
|
||||
# Reply threading mode for Discord (off/first/all)
|
||||
discord_reply_mode = os.getenv("DISCORD_REPLY_TO_MODE", "").lower()
|
||||
if discord_reply_mode in ("off", "first", "all"):
|
||||
if discord_reply_mode in {"off", "first", "all"}:
|
||||
if Platform.DISCORD not in config.platforms:
|
||||
config.platforms[Platform.DISCORD] = PlatformConfig()
|
||||
config.platforms[Platform.DISCORD].reply_to_mode = discord_reply_mode
|
||||
|
||||
# WhatsApp (typically uses different auth mechanism)
|
||||
whatsapp_enabled = os.getenv("WHATSAPP_ENABLED", "").lower() in ("true", "1", "yes")
|
||||
whatsapp_disabled_explicitly = os.getenv("WHATSAPP_ENABLED", "").lower() in ("false", "0", "no")
|
||||
whatsapp_enabled = os.getenv("WHATSAPP_ENABLED", "").lower() in {"true", "1", "yes"}
|
||||
whatsapp_disabled_explicitly = os.getenv("WHATSAPP_ENABLED", "").lower() in {"false", "0", "no"}
|
||||
if Platform.WHATSAPP in config.platforms:
|
||||
# YAML config exists — respect explicit disable
|
||||
wa_cfg = config.platforms[Platform.WHATSAPP]
|
||||
|
|
@ -1285,7 +1285,7 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
config.platforms[Platform.SIGNAL].extra.update({
|
||||
"http_url": signal_url,
|
||||
"account": signal_account,
|
||||
"ignore_stories": os.getenv("SIGNAL_IGNORE_STORIES", "true").lower() in ("true", "1", "yes"),
|
||||
"ignore_stories": os.getenv("SIGNAL_IGNORE_STORIES", "true").lower() in {"true", "1", "yes"},
|
||||
})
|
||||
signal_home = os.getenv("SIGNAL_HOME_CHANNEL")
|
||||
if signal_home and Platform.SIGNAL in config.platforms:
|
||||
|
|
@ -1334,7 +1334,7 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
matrix_password = os.getenv("MATRIX_PASSWORD", "")
|
||||
if matrix_password:
|
||||
config.platforms[Platform.MATRIX].extra["password"] = matrix_password
|
||||
matrix_e2ee = os.getenv("MATRIX_ENCRYPTION", "").lower() in ("true", "1", "yes")
|
||||
matrix_e2ee = os.getenv("MATRIX_ENCRYPTION", "").lower() in {"true", "1", "yes"}
|
||||
config.platforms[Platform.MATRIX].extra["encryption"] = matrix_e2ee
|
||||
matrix_device_id = os.getenv("MATRIX_DEVICE_ID", "")
|
||||
if matrix_device_id:
|
||||
|
|
@ -1399,7 +1399,7 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
)
|
||||
|
||||
# API Server
|
||||
api_server_enabled = os.getenv("API_SERVER_ENABLED", "").lower() in ("true", "1", "yes")
|
||||
api_server_enabled = os.getenv("API_SERVER_ENABLED", "").lower() in {"true", "1", "yes"}
|
||||
api_server_key = os.getenv("API_SERVER_KEY", "")
|
||||
api_server_cors_origins = os.getenv("API_SERVER_CORS_ORIGINS", "")
|
||||
api_server_port = os.getenv("API_SERVER_PORT")
|
||||
|
|
@ -1426,7 +1426,7 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
config.platforms[Platform.API_SERVER].extra["model_name"] = api_server_model_name
|
||||
|
||||
# Webhook platform
|
||||
webhook_enabled = os.getenv("WEBHOOK_ENABLED", "").lower() in ("true", "1", "yes")
|
||||
webhook_enabled = os.getenv("WEBHOOK_ENABLED", "").lower() in {"true", "1", "yes"}
|
||||
webhook_port = os.getenv("WEBHOOK_PORT")
|
||||
webhook_secret = os.getenv("WEBHOOK_SECRET", "")
|
||||
if webhook_enabled:
|
||||
|
|
@ -1442,11 +1442,11 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
config.platforms[Platform.WEBHOOK].extra["secret"] = webhook_secret
|
||||
|
||||
# Microsoft Graph webhook platform
|
||||
msgraph_webhook_enabled = os.getenv("MSGRAPH_WEBHOOK_ENABLED", "").lower() in (
|
||||
msgraph_webhook_enabled = os.getenv("MSGRAPH_WEBHOOK_ENABLED", "").lower() in {
|
||||
"true",
|
||||
"1",
|
||||
"yes",
|
||||
)
|
||||
}
|
||||
msgraph_webhook_port = os.getenv("MSGRAPH_WEBHOOK_PORT")
|
||||
msgraph_webhook_client_state = os.getenv("MSGRAPH_WEBHOOK_CLIENT_STATE", "")
|
||||
msgraph_webhook_resources = os.getenv("MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES", "")
|
||||
|
|
@ -1640,7 +1640,7 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
|
|||
"webhook_host": os.getenv("BLUEBUBBLES_WEBHOOK_HOST", "127.0.0.1"),
|
||||
"webhook_port": int(os.getenv("BLUEBUBBLES_WEBHOOK_PORT", "8645")),
|
||||
"webhook_path": os.getenv("BLUEBUBBLES_WEBHOOK_PATH", "/bluebubbles-webhook"),
|
||||
"send_read_receipts": os.getenv("BLUEBUBBLES_SEND_READ_RECEIPTS", "true").lower() in ("true", "1", "yes"),
|
||||
"send_read_receipts": os.getenv("BLUEBUBBLES_SEND_READ_RECEIPTS", "true").lower() in {"true", "1", "yes"},
|
||||
})
|
||||
bluebubbles_home = os.getenv("BLUEBUBBLES_HOME_CHANNEL")
|
||||
if bluebubbles_home and Platform.BLUEBUBBLES in config.platforms:
|
||||
|
|
|
|||
|
|
@ -190,13 +190,13 @@ def _normalise(setting: str, value: Any) -> Any:
|
|||
if value is True:
|
||||
return "all"
|
||||
return str(value).lower()
|
||||
if setting in ("show_reasoning", "streaming"):
|
||||
if setting in {"show_reasoning", "streaming"}:
|
||||
if isinstance(value, str):
|
||||
return value.lower() in ("true", "1", "yes", "on")
|
||||
return value.lower() in {"true", "1", "yes", "on"}
|
||||
return bool(value)
|
||||
if setting == "cleanup_progress":
|
||||
if isinstance(value, str):
|
||||
return value.lower() in ("true", "1", "yes", "on")
|
||||
return value.lower() in {"true", "1", "yes", "on"}
|
||||
return bool(value)
|
||||
if setting == "tool_preview_length":
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -449,7 +449,7 @@ if AIOHTTP_AVAILABLE:
|
|||
@web.middleware
|
||||
async def body_limit_middleware(request, handler):
|
||||
"""Reject overly large request bodies early based on Content-Length."""
|
||||
if request.method in ("POST", "PUT", "PATCH"):
|
||||
if request.method in {"POST", "PUT", "PATCH"}:
|
||||
cl = request.headers.get("Content-Length")
|
||||
if cl is not None:
|
||||
try:
|
||||
|
|
@ -646,7 +646,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
try:
|
||||
from hermes_cli.profiles import get_active_profile_name
|
||||
profile = get_active_profile_name()
|
||||
if profile and profile not in ("default", "custom"):
|
||||
if profile and profile not in {"default", "custom"}:
|
||||
return profile
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -1003,7 +1003,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
system_prompt = content
|
||||
else:
|
||||
system_prompt = system_prompt + "\n" + content
|
||||
elif role in ("user", "assistant"):
|
||||
elif role in {"user", "assistant"}:
|
||||
try:
|
||||
content = _normalize_multimodal_content(raw_content)
|
||||
except ValueError as exc:
|
||||
|
|
@ -2381,7 +2381,7 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
if cron_err:
|
||||
return cron_err
|
||||
try:
|
||||
include_disabled = request.query.get("include_disabled", "").lower() in ("true", "1")
|
||||
include_disabled = request.query.get("include_disabled", "").lower() in {"true", "1"}
|
||||
jobs = _cron_list(include_disabled=include_disabled)
|
||||
return web.json_response({"jobs": jobs})
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -560,7 +560,7 @@ def _looks_like_image(data: bytes) -> bool:
|
|||
return True
|
||||
if data[:3] == b"\xff\xd8\xff":
|
||||
return True
|
||||
if data[:6] in (b"GIF87a", b"GIF89a"):
|
||||
if data[:6] in {b"GIF87a", b"GIF89a"}:
|
||||
return True
|
||||
if data[:2] == b"BM":
|
||||
return True
|
||||
|
|
@ -859,7 +859,7 @@ def cache_document_from_bytes(data: bytes, filename: str) -> str:
|
|||
# Sanitize: strip directory components, null bytes, and control characters
|
||||
safe_name = Path(filename).name if filename else "document"
|
||||
safe_name = safe_name.replace("\x00", "").strip()
|
||||
if not safe_name or safe_name in (".", ".."):
|
||||
if not safe_name or safe_name in {".", ".."}:
|
||||
safe_name = "document"
|
||||
cached_name = f"doc_{uuid.uuid4().hex[:12]}_{safe_name}"
|
||||
filepath = cache_dir / cached_name
|
||||
|
|
@ -2793,7 +2793,7 @@ class BasePlatformAdapter(ABC):
|
|||
# and preserve ordering of queued follow-ups. Route those
|
||||
# through the dedicated handoff path that serializes
|
||||
# cancellation + runner response + pending drain.
|
||||
if cmd in ("stop", "new", "reset"):
|
||||
if cmd in {"stop", "new", "reset"}:
|
||||
try:
|
||||
await self._dispatch_active_session_command(event, session_key, cmd)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ class BlueBubblesAdapter(BasePlatformAdapter):
|
|||
def _webhook_url(self) -> str:
|
||||
"""Compute the external webhook URL for BlueBubbles registration."""
|
||||
host = self.webhook_host
|
||||
if host in ("0.0.0.0", "127.0.0.1", "localhost", "::"):
|
||||
if host in {"0.0.0.0", "127.0.0.1", "localhost", "::"}:
|
||||
host = "localhost"
|
||||
return f"http://{host}:{self.webhook_port}{self.webhook_path}"
|
||||
|
||||
|
|
|
|||
|
|
@ -353,9 +353,9 @@ class DingTalkAdapter(BasePlatformAdapter):
|
|||
configured = self.config.extra.get("require_mention")
|
||||
if configured is not None:
|
||||
if isinstance(configured, str):
|
||||
return configured.lower() in ("true", "1", "yes", "on")
|
||||
return configured.lower() in {"true", "1", "yes", "on"}
|
||||
return bool(configured)
|
||||
return os.getenv("DINGTALK_REQUIRE_MENTION", "false").lower() in ("true", "1", "yes", "on")
|
||||
return os.getenv("DINGTALK_REQUIRE_MENTION", "false").lower() in {"true", "1", "yes", "on"}
|
||||
|
||||
def _dingtalk_free_response_chats(self) -> Set[str]:
|
||||
raw = self.config.extra.get("free_response_chats")
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ def _build_allowed_mentions():
|
|||
raw = os.getenv(name, "").strip().lower()
|
||||
if not raw:
|
||||
return default
|
||||
return raw in ("true", "1", "yes", "on")
|
||||
return raw in {"true", "1", "yes", "on"}
|
||||
|
||||
return discord.AllowedMentions(
|
||||
everyone=_b("DISCORD_ALLOW_MENTION_EVERYONE", False),
|
||||
|
|
@ -708,7 +708,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
|
||||
# Ignore Discord system messages (thread renames, pins, member joins, etc.)
|
||||
# Allow both default and reply types — replies have a distinct MessageType.
|
||||
if message.type not in (discord.MessageType.default, discord.MessageType.reply):
|
||||
if message.type not in {discord.MessageType.default, discord.MessageType.reply}:
|
||||
return
|
||||
|
||||
# Bot message filtering (DISCORD_ALLOW_BOTS):
|
||||
|
|
@ -769,7 +769,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
# answer regardless of who is mentioned.
|
||||
_ignore_no_mention = os.getenv(
|
||||
"DISCORD_IGNORE_NO_MENTION", "true"
|
||||
).lower() in ("true", "1", "yes")
|
||||
).lower() in {"true", "1", "yes"}
|
||||
if _ignore_no_mention and not _self_mentioned and not _other_bots_mentioned:
|
||||
_channel_id = str(message.channel.id)
|
||||
_parent_id = None
|
||||
|
|
@ -1317,7 +1317,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
|
||||
def _reactions_enabled(self) -> bool:
|
||||
"""Check if message reactions are enabled via config/env."""
|
||||
return os.getenv("DISCORD_REACTIONS", "true").lower() not in ("false", "0", "no")
|
||||
return os.getenv("DISCORD_REACTIONS", "true").lower() not in {"false", "0", "no"}
|
||||
|
||||
async def on_processing_start(self, event: MessageEvent) -> None:
|
||||
"""Add an in-progress reaction for normal Discord message events."""
|
||||
|
|
@ -3137,9 +3137,9 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
# UX so users don't see commands they can't invoke. Off by default
|
||||
# to preserve the slash UX for deployments that intentionally allow
|
||||
# everyone in the guild.
|
||||
if os.getenv("DISCORD_HIDE_SLASH_COMMANDS", "false").strip().lower() in (
|
||||
if os.getenv("DISCORD_HIDE_SLASH_COMMANDS", "false").strip().lower() in {
|
||||
"true", "1", "yes", "on",
|
||||
):
|
||||
}:
|
||||
self._apply_owner_only_visibility(tree)
|
||||
|
||||
def _apply_owner_only_visibility(self, tree) -> None:
|
||||
|
|
@ -3526,9 +3526,9 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
configured = self.config.extra.get("require_mention")
|
||||
if configured is not None:
|
||||
if isinstance(configured, str):
|
||||
return configured.lower() not in ("false", "0", "no", "off")
|
||||
return configured.lower() not in {"false", "0", "no", "off"}
|
||||
return bool(configured)
|
||||
return os.getenv("DISCORD_REQUIRE_MENTION", "true").lower() not in ("false", "0", "no", "off")
|
||||
return os.getenv("DISCORD_REQUIRE_MENTION", "true").lower() not in {"false", "0", "no", "off"}
|
||||
|
||||
def _discord_free_response_channels(self) -> set:
|
||||
"""Return Discord channel IDs where no bot mention is required.
|
||||
|
|
@ -4200,7 +4200,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
no_thread_channels_raw = os.getenv("DISCORD_NO_THREAD_CHANNELS", "")
|
||||
no_thread_channels = {ch.strip() for ch in no_thread_channels_raw.split(",") if ch.strip()}
|
||||
skip_thread = bool(channel_ids & no_thread_channels)
|
||||
auto_thread = os.getenv("DISCORD_AUTO_THREAD", "true").lower() in ("true", "1", "yes")
|
||||
auto_thread = os.getenv("DISCORD_AUTO_THREAD", "true").lower() in {"true", "1", "yes"}
|
||||
is_reply_message = getattr(message, "type", None) == discord.MessageType.reply
|
||||
if auto_thread and not skip_thread and not is_voice_linked_channel and not is_reply_message:
|
||||
thread = await self._auto_create_thread(message)
|
||||
|
|
@ -4282,7 +4282,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
try:
|
||||
# Determine extension from content type (image/png -> .png)
|
||||
ext = "." + content_type.split("/")[-1].split(";")[0]
|
||||
if ext not in (".jpg", ".jpeg", ".png", ".gif", ".webp"):
|
||||
if ext not in {".jpg", ".jpeg", ".png", ".gif", ".webp"}:
|
||||
ext = ".jpg"
|
||||
cached_path = await self._cache_discord_image(att, ext)
|
||||
media_urls.append(cached_path)
|
||||
|
|
@ -4296,7 +4296,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
elif content_type.startswith("audio/"):
|
||||
try:
|
||||
ext = "." + content_type.split("/")[-1].split(";")[0]
|
||||
if ext not in (".ogg", ".mp3", ".wav", ".webm", ".m4a"):
|
||||
if ext not in {".ogg", ".mp3", ".wav", ".webm", ".m4a"}:
|
||||
ext = ".ogg"
|
||||
cached_path = await self._cache_discord_audio(att, ext)
|
||||
media_urls.append(cached_path)
|
||||
|
|
@ -4339,7 +4339,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||
logger.info("[Discord] Cached user document: %s", cached_path)
|
||||
# Inject text content for plain-text documents (capped at 100 KB)
|
||||
MAX_TEXT_INJECT_BYTES = 100 * 1024
|
||||
if ext in (".md", ".txt", ".log") and len(raw_bytes) <= MAX_TEXT_INJECT_BYTES:
|
||||
if ext in {".md", ".txt", ".log"} and len(raw_bytes) <= MAX_TEXT_INJECT_BYTES:
|
||||
try:
|
||||
text_content = raw_bytes.decode("utf-8")
|
||||
display_name = att.filename or f"document{ext}"
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ _NOREPLY_PATTERNS = (
|
|||
# RFC headers that indicate bulk/automated mail
|
||||
_AUTOMATED_HEADERS = {
|
||||
"Auto-Submitted": lambda v: v.lower() != "no",
|
||||
"Precedence": lambda v: v.lower() in ("bulk", "list", "junk"),
|
||||
"Precedence": lambda v: v.lower() in {"bulk", "list", "junk"},
|
||||
"X-Auto-Response-Suppress": lambda v: bool(v),
|
||||
"List-Unsubscribe": lambda v: bool(v),
|
||||
}
|
||||
|
|
@ -203,7 +203,7 @@ def _extract_attachments(
|
|||
continue
|
||||
# Skip text/plain and text/html body parts
|
||||
content_type = part.get_content_type()
|
||||
if content_type in ("text/plain", "text/html") and "attachment" not in disposition:
|
||||
if content_type in {"text/plain", "text/html"} and "attachment" not in disposition:
|
||||
continue
|
||||
|
||||
filename = part.get_filename()
|
||||
|
|
|
|||
|
|
@ -428,7 +428,7 @@ RejectReason = Literal[
|
|||
|
||||
def _is_bot_sender(sender: Any) -> bool:
|
||||
# receive_v1 docs say {user, bot}; accept "app" defensively.
|
||||
return getattr(sender, "sender_type", "") in ("bot", "app")
|
||||
return getattr(sender, "sender_type", "") in {"bot", "app"}
|
||||
|
||||
|
||||
def _sender_identity(sender: Any) -> frozenset:
|
||||
|
|
@ -1443,7 +1443,7 @@ class FeishuAdapter(BasePlatformAdapter):
|
|||
# Env-only so adapter and gateway auth bypass share one source; yaml
|
||||
# feishu.allow_bots is bridged to this env var at config load.
|
||||
allow_bots = os.getenv("FEISHU_ALLOW_BOTS", "none").strip().lower()
|
||||
if allow_bots not in ("none", "mentions", "all"):
|
||||
if allow_bots not in {"none", "mentions", "all"}:
|
||||
logger.warning(
|
||||
"[Feishu] Unknown allow_bots=%r, falling back to 'none'. Valid: none, mentions, all.",
|
||||
allow_bots,
|
||||
|
|
@ -2752,7 +2752,7 @@ class FeishuAdapter(BasePlatformAdapter):
|
|||
# =========================================================================
|
||||
|
||||
def _reactions_enabled(self) -> bool:
|
||||
return os.getenv("FEISHU_REACTIONS", "true").strip().lower() not in ("false", "0", "no")
|
||||
return os.getenv("FEISHU_REACTIONS", "true").strip().lower() not in {"false", "0", "no"}
|
||||
|
||||
async def _add_reaction(self, message_id: str, emoji_type: str) -> Optional[str]:
|
||||
"""Return the reaction_id on success, else None. The id is needed later for deletion."""
|
||||
|
|
@ -3219,7 +3219,7 @@ class FeishuAdapter(BasePlatformAdapter):
|
|||
self._on_bot_added_to_chat(data)
|
||||
elif event_type == "im.chat.member.bot.deleted_v1":
|
||||
self._on_bot_removed_from_chat(data)
|
||||
elif event_type in ("im.message.reaction.created_v1", "im.message.reaction.deleted_v1"):
|
||||
elif event_type in {"im.message.reaction.created_v1", "im.message.reaction.deleted_v1"}:
|
||||
self._on_reaction_event(event_type, data)
|
||||
elif event_type == "card.action.trigger":
|
||||
self._on_card_action_trigger(data)
|
||||
|
|
@ -4815,7 +4815,7 @@ def _poll_registration(
|
|||
|
||||
# Terminal errors
|
||||
error = res.get("error", "")
|
||||
if error in ("access_denied", "expired_token"):
|
||||
if error in {"access_denied", "expired_token"}:
|
||||
if poll_count > 0:
|
||||
print()
|
||||
logger.warning("[Feishu onboard] Registration %s", error)
|
||||
|
|
|
|||
|
|
@ -690,7 +690,7 @@ def _extract_docs_links(replies: List[Dict[str, Any]]) -> List[Dict[str, str]]:
|
|||
except (json.JSONDecodeError, TypeError):
|
||||
continue
|
||||
for elem in content.get("elements", []):
|
||||
if elem.get("type") not in ("docs_link", "link"):
|
||||
if elem.get("type") not in {"docs_link", "link"}:
|
||||
continue
|
||||
link_data = elem.get("docs_link") or elem.get("link") or {}
|
||||
url = link_data.get("url", "")
|
||||
|
|
@ -1031,7 +1031,7 @@ def _save_session_history(key: str, messages: List[Dict[str, Any]]) -> None:
|
|||
# Only keep user/assistant messages (strip system messages and tool internals)
|
||||
cleaned = [
|
||||
m for m in messages
|
||||
if m.get("role") in ("user", "assistant") and m.get("content")
|
||||
if m.get("role") in {"user", "assistant"} and m.get("content")
|
||||
]
|
||||
# Keep last N
|
||||
if len(cleaned) > _SESSION_MAX_MESSAGES:
|
||||
|
|
@ -1170,7 +1170,7 @@ async def handle_drive_comment_event(
|
|||
rule = resolve_rule(comments_cfg, file_type, file_token)
|
||||
|
||||
# If no exact match and config has wiki keys, try reverse-lookup
|
||||
if rule.match_source in ("wildcard", "top") and has_wiki_keys(comments_cfg):
|
||||
if rule.match_source in {"wildcard", "top"} and has_wiki_keys(comments_cfg):
|
||||
wiki_token = await _reverse_lookup_wiki_token(client, file_type, file_token)
|
||||
if wiki_token:
|
||||
rule = resolve_rule(comments_cfg, file_type, file_token, wiki_token=wiki_token)
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ class HomeAssistantAdapter(BasePlatformAdapter):
|
|||
await self._handle_ha_event(data.get("event", {}))
|
||||
except json.JSONDecodeError:
|
||||
logger.debug("Invalid JSON from HA WS: %s", ws_msg.data[:200])
|
||||
elif ws_msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR):
|
||||
elif ws_msg.type in {aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR}:
|
||||
break
|
||||
|
||||
async def _handle_ha_event(self, event: Dict[str, Any]) -> None:
|
||||
|
|
@ -361,7 +361,7 @@ class HomeAssistantAdapter(BasePlatformAdapter):
|
|||
f"(was {'triggered' if old_val == 'on' else 'cleared'})"
|
||||
)
|
||||
|
||||
if domain in ("light", "switch", "fan"):
|
||||
if domain in {"light", "switch", "fan"}:
|
||||
return (
|
||||
f"[Home Assistant] {friendly_name}: turned "
|
||||
f"{'on' if new_val == 'on' else 'off'}"
|
||||
|
|
|
|||
|
|
@ -245,11 +245,11 @@ def check_matrix_requirements() -> bool:
|
|||
|
||||
# If encryption is requested, verify E2EE deps are available at startup
|
||||
# rather than silently degrading to plaintext-only at connect time.
|
||||
encryption_requested = os.getenv("MATRIX_ENCRYPTION", "").lower() in (
|
||||
encryption_requested = os.getenv("MATRIX_ENCRYPTION", "").lower() in {
|
||||
"true",
|
||||
"1",
|
||||
"yes",
|
||||
)
|
||||
}
|
||||
if encryption_requested and not _check_e2ee_deps():
|
||||
logger.error(
|
||||
"Matrix: MATRIX_ENCRYPTION=true but E2EE dependencies are missing. %s. "
|
||||
|
|
@ -312,7 +312,7 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
)
|
||||
self._encryption: bool = config.extra.get(
|
||||
"encryption",
|
||||
os.getenv("MATRIX_ENCRYPTION", "").lower() in ("true", "1", "yes"),
|
||||
os.getenv("MATRIX_ENCRYPTION", "").lower() in {"true", "1", "yes"},
|
||||
)
|
||||
self._device_id: str = config.extra.get("device_id", "") or os.getenv(
|
||||
"MATRIX_DEVICE_ID", ""
|
||||
|
|
@ -343,7 +343,7 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
# Mention/thread gating — parsed once from env vars.
|
||||
self._require_mention: bool = os.getenv(
|
||||
"MATRIX_REQUIRE_MENTION", "true"
|
||||
).lower() not in ("false", "0", "no")
|
||||
).lower() not in {"false", "0", "no"}
|
||||
free_rooms_raw = config.extra.get("free_response_rooms")
|
||||
if free_rooms_raw is None:
|
||||
free_rooms_raw = os.getenv("MATRIX_FREE_RESPONSE_ROOMS", "")
|
||||
|
|
@ -367,22 +367,22 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
self._allowed_rooms: Set[str] = {
|
||||
r.strip() for r in str(allowed_rooms_raw).split(",") if r.strip()
|
||||
}
|
||||
self._auto_thread: bool = os.getenv("MATRIX_AUTO_THREAD", "true").lower() in (
|
||||
self._auto_thread: bool = os.getenv("MATRIX_AUTO_THREAD", "true").lower() in {
|
||||
"true",
|
||||
"1",
|
||||
"yes",
|
||||
)
|
||||
}
|
||||
self._dm_auto_thread: bool = os.getenv(
|
||||
"MATRIX_DM_AUTO_THREAD", "false"
|
||||
).lower() in ("true", "1", "yes")
|
||||
).lower() in {"true", "1", "yes"}
|
||||
self._dm_mention_threads: bool = os.getenv(
|
||||
"MATRIX_DM_MENTION_THREADS", "false"
|
||||
).lower() in ("true", "1", "yes")
|
||||
).lower() in {"true", "1", "yes"}
|
||||
|
||||
# Reactions: configurable via MATRIX_REACTIONS (default: true).
|
||||
self._reactions_enabled: bool = os.getenv(
|
||||
"MATRIX_REACTIONS", "true"
|
||||
).lower() not in ("false", "0", "no")
|
||||
).lower() not in {"false", "0", "no"}
|
||||
self._pending_reactions: dict[tuple[str, str], str] = {}
|
||||
# Delay before redacting reactions so Matrix homeservers have time to
|
||||
# deliver the final message event without tripping "missing event"
|
||||
|
|
@ -1771,9 +1771,9 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
|
||||
# Cache media locally when downstream tools need a real file path.
|
||||
cached_path = None
|
||||
should_cache_locally = msg_type in (
|
||||
should_cache_locally = msg_type in {
|
||||
MessageType.PHOTO, MessageType.AUDIO, MessageType.VIDEO, MessageType.DOCUMENT,
|
||||
) or is_voice_message or is_encrypted_media
|
||||
} or is_voice_message or is_encrypted_media
|
||||
if should_cache_locally and url:
|
||||
try:
|
||||
file_bytes = await self._client.download_media(ContentURI(url))
|
||||
|
|
@ -1834,7 +1834,7 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
ext = ext_map.get(media_type, ".jpg")
|
||||
cached_path = cache_image_from_bytes(file_bytes, ext=ext)
|
||||
logger.info("[Matrix] Cached user image at %s", cached_path)
|
||||
elif msg_type in (MessageType.AUDIO, MessageType.VOICE):
|
||||
elif msg_type in {MessageType.AUDIO, MessageType.VOICE}:
|
||||
ext = (
|
||||
Path(
|
||||
body
|
||||
|
|
@ -2602,7 +2602,7 @@ class MatrixAdapter(BasePlatformAdapter):
|
|||
"""Sanitize a URL for use in an href attribute."""
|
||||
stripped = url.strip()
|
||||
scheme = stripped.split(":", 1)[0].lower().strip() if ":" in stripped else ""
|
||||
if scheme in ("javascript", "data", "vbscript"):
|
||||
if scheme in {"javascript", "data", "vbscript"}:
|
||||
return ""
|
||||
return stripped.replace('"', """)
|
||||
|
||||
|
|
|
|||
|
|
@ -611,7 +611,7 @@ class MattermostAdapter(BasePlatformAdapter):
|
|||
# succeed on retry — stop reconnecting instead of looping forever.
|
||||
import aiohttp
|
||||
err_str = str(exc).lower()
|
||||
if isinstance(exc, aiohttp.WSServerHandshakeError) and exc.status in (401, 403):
|
||||
if isinstance(exc, aiohttp.WSServerHandshakeError) and exc.status in {401, 403}:
|
||||
logger.error("Mattermost WS auth failed (HTTP %d) — stopping reconnect", exc.status)
|
||||
return
|
||||
if "401" in err_str or "403" in err_str or "unauthorized" in err_str:
|
||||
|
|
@ -649,21 +649,21 @@ class MattermostAdapter(BasePlatformAdapter):
|
|||
if self._closing:
|
||||
return
|
||||
|
||||
if raw_msg.type in (
|
||||
if raw_msg.type in {
|
||||
raw_msg.type.TEXT,
|
||||
raw_msg.type.BINARY,
|
||||
):
|
||||
}:
|
||||
try:
|
||||
event = json.loads(raw_msg.data)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
continue
|
||||
await self._handle_ws_event(event)
|
||||
elif raw_msg.type in (
|
||||
elif raw_msg.type in {
|
||||
raw_msg.type.ERROR,
|
||||
raw_msg.type.CLOSE,
|
||||
raw_msg.type.CLOSING,
|
||||
raw_msg.type.CLOSED,
|
||||
):
|
||||
}:
|
||||
logger.info("Mattermost: WebSocket closed (%s)", raw_msg.type)
|
||||
break
|
||||
|
||||
|
|
@ -732,7 +732,7 @@ class MattermostAdapter(BasePlatformAdapter):
|
|||
|
||||
require_mention = os.getenv(
|
||||
"MATTERMOST_REQUIRE_MENTION", "true"
|
||||
).lower() not in ("false", "0", "no")
|
||||
).lower() not in {"false", "0", "no"}
|
||||
|
||||
free_channels_raw = os.getenv("MATTERMOST_FREE_RESPONSE_CHANNELS", "")
|
||||
free_channels = {ch.strip() for ch in free_channels_raw.split(",") if ch.strip()}
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
self._fail_pending("Connection closed")
|
||||
|
||||
# Stop reconnecting for fatal codes
|
||||
if code in (4914, 4915):
|
||||
if code in {4914, 4915}:
|
||||
desc = "offline/sandbox-only" if code == 4914 else "banned"
|
||||
logger.error(
|
||||
"[%s] Bot is %s. Check QQ Open Platform.", self._log_tag, desc
|
||||
|
|
@ -550,7 +550,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
self._token_expires_at = 0.0
|
||||
|
||||
# Session invalid → clear session, will re-identify on next Hello
|
||||
if code in (
|
||||
if code in {
|
||||
4006,
|
||||
4007,
|
||||
4009,
|
||||
|
|
@ -568,7 +568,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
4911,
|
||||
4912,
|
||||
4913,
|
||||
):
|
||||
}:
|
||||
logger.info(
|
||||
"[%s] Session error (%d), clearing session for re-identify",
|
||||
self._log_tag,
|
||||
|
|
@ -637,12 +637,12 @@ class QQAdapter(BasePlatformAdapter):
|
|||
payload = self._parse_json(msg.data)
|
||||
if payload:
|
||||
self._dispatch_payload(payload)
|
||||
elif msg.type in (aiohttp.WSMsgType.PING,):
|
||||
elif msg.type in {aiohttp.WSMsgType.PING,}:
|
||||
# aiohttp auto-replies with PONG
|
||||
pass
|
||||
elif msg.type == aiohttp.WSMsgType.CLOSE:
|
||||
raise QQCloseError(msg.data, msg.extra)
|
||||
elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR):
|
||||
elif msg.type in {aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR}:
|
||||
raise RuntimeError("WebSocket closed")
|
||||
|
||||
async def _heartbeat_loop(self) -> None:
|
||||
|
|
@ -783,13 +783,13 @@ class QQAdapter(BasePlatformAdapter):
|
|||
self._handle_ready(d)
|
||||
elif t == "RESUMED":
|
||||
logger.info("[%s] Session resumed", self._log_tag)
|
||||
elif t in (
|
||||
elif t in {
|
||||
"C2C_MESSAGE_CREATE",
|
||||
"GROUP_AT_MESSAGE_CREATE",
|
||||
"DIRECT_MESSAGE_CREATE",
|
||||
"GUILD_MESSAGE_CREATE",
|
||||
"GUILD_AT_MESSAGE_CREATE",
|
||||
):
|
||||
}:
|
||||
asyncio.create_task(self._on_message(t, d))
|
||||
elif t == "INTERACTION_CREATE":
|
||||
self._create_task(self._on_interaction(d))
|
||||
|
|
@ -859,9 +859,9 @@ class QQAdapter(BasePlatformAdapter):
|
|||
# Route by event type
|
||||
if event_type == "C2C_MESSAGE_CREATE":
|
||||
await self._handle_c2c_message(d, msg_id, content, author, timestamp)
|
||||
elif event_type in ("GROUP_AT_MESSAGE_CREATE",):
|
||||
elif event_type in {"GROUP_AT_MESSAGE_CREATE",}:
|
||||
await self._handle_group_message(d, msg_id, content, author, timestamp)
|
||||
elif event_type in ("GUILD_MESSAGE_CREATE", "GUILD_AT_MESSAGE_CREATE"):
|
||||
elif event_type in {"GUILD_MESSAGE_CREATE", "GUILD_AT_MESSAGE_CREATE"}:
|
||||
await self._handle_guild_message(d, msg_id, content, author, timestamp)
|
||||
elif event_type == "DIRECT_MESSAGE_CREATE":
|
||||
await self._handle_dm_message(d, msg_id, content, author, timestamp)
|
||||
|
|
@ -1864,7 +1864,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
return ".wav"
|
||||
if data[:4] == b"fLaC":
|
||||
return ".flac"
|
||||
if data[:2] in (b"\xff\xfb", b"\xff\xf3", b"\xff\xf2"):
|
||||
if data[:2] in {b"\xff\xfb", b"\xff\xf3", b"\xff\xf2"}:
|
||||
return ".mp3"
|
||||
if data[:4] == b"\x30\x26\xb2\x75" or data[:4] == b"\x4f\x67\x67\x53":
|
||||
return ".ogg"
|
||||
|
|
@ -2033,7 +2033,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
"base_url": base_url,
|
||||
"api_key": api_key,
|
||||
"model": model
|
||||
or ("glm-asr" if provider in ("zai", "glm") else "whisper-1"),
|
||||
or ("glm-asr" if provider in {"zai", "glm"} else "whisper-1"),
|
||||
}
|
||||
|
||||
# 2. QQ-specific env vars (set by `hermes setup gateway` / `hermes gateway`)
|
||||
|
|
@ -2115,7 +2115,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
if urlparse(source_url).path
|
||||
else ""
|
||||
)
|
||||
if not ext or ext not in (
|
||||
if not ext or ext not in {
|
||||
".silk",
|
||||
".amr",
|
||||
".mp3",
|
||||
|
|
@ -2124,7 +2124,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
".m4a",
|
||||
".aac",
|
||||
".flac",
|
||||
):
|
||||
}:
|
||||
ext = self._guess_ext_from_data(audio_data)
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as tmp_src:
|
||||
|
|
@ -2870,7 +2870,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
raise ValueError("Media source is required")
|
||||
|
||||
parsed = urlparse(source)
|
||||
if parsed.scheme in ("http", "https"):
|
||||
if parsed.scheme in {"http", "https"}:
|
||||
# For URLs, pass through directly to the upload API
|
||||
content_type = mimetypes.guess_type(source)[0] or "application/octet-stream"
|
||||
resolved_name = file_name or Path(parsed.path).name or "media"
|
||||
|
|
@ -2966,7 +2966,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
chat_type = self._guess_chat_type(chat_id)
|
||||
return {
|
||||
"name": chat_id,
|
||||
"type": "group" if chat_type in ("group", "guild") else "dm",
|
||||
"type": "group" if chat_type in {"group", "guild"} else "dm",
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
|
@ -2975,7 +2975,7 @@ class QQAdapter(BasePlatformAdapter):
|
|||
|
||||
@staticmethod
|
||||
def _is_url(source: str) -> bool:
|
||||
return urlparse(str(source)).scheme in ("http", "https")
|
||||
return urlparse(str(source)).scheme in {"http", "https"}
|
||||
|
||||
def _guess_chat_type(self, chat_id: str) -> str:
|
||||
"""Determine chat type from stored inbound metadata, fallback to 'c2c'."""
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ class ChunkedUploader:
|
|||
:raises UploadFileTooLargeError: When the file exceeds the platform limit.
|
||||
:raises RuntimeError: On other API or I/O failures.
|
||||
"""
|
||||
if chat_type not in ("c2c", "group"):
|
||||
if chat_type not in {"c2c", "group"}:
|
||||
raise ValueError(
|
||||
f"ChunkedUploader: unsupported chat_type {chat_type!r}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -99,11 +99,11 @@ def _guess_extension(data: bytes) -> str:
|
|||
|
||||
|
||||
def _is_image_ext(ext: str) -> bool:
|
||||
return ext.lower() in (".jpg", ".jpeg", ".png", ".gif", ".webp")
|
||||
return ext.lower() in {".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||||
|
||||
|
||||
def _is_audio_ext(ext: str) -> bool:
|
||||
return ext.lower() in (".mp3", ".wav", ".ogg", ".m4a", ".aac")
|
||||
return ext.lower() in {".mp3", ".wav", ".ogg", ".m4a", ".aac"}
|
||||
|
||||
|
||||
_EXT_TO_MIME = {
|
||||
|
|
@ -1449,7 +1449,7 @@ class SignalAdapter(BasePlatformAdapter):
|
|||
contacts from seeing the 👀 reaction (which fires before run.py's
|
||||
auth gate and would otherwise reveal that a bot is listening).
|
||||
"""
|
||||
if os.getenv("SIGNAL_REACTIONS", "true").lower() in ("false", "0", "no"):
|
||||
if os.getenv("SIGNAL_REACTIONS", "true").lower() in {"false", "0", "no"}:
|
||||
return False
|
||||
if event is not None:
|
||||
sender = getattr(getattr(event, "source", None), "user_id", None)
|
||||
|
|
|
|||
|
|
@ -935,7 +935,7 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
raw = self.config.extra.get("dm_top_level_threads_as_sessions")
|
||||
if raw is None:
|
||||
return True # default: each DM thread is its own session
|
||||
return str(raw).strip().lower() in ("1", "true", "yes", "on")
|
||||
return str(raw).strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
def _resolve_thread_ts(
|
||||
self,
|
||||
|
|
@ -1300,7 +1300,7 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
|
||||
def _reactions_enabled(self) -> bool:
|
||||
"""Check if message reactions are enabled via config/env."""
|
||||
return os.getenv("SLACK_REACTIONS", "true").lower() not in ("false", "0", "no")
|
||||
return os.getenv("SLACK_REACTIONS", "true").lower() not in {"false", "0", "no"}
|
||||
|
||||
async def on_processing_start(self, event: MessageEvent) -> None:
|
||||
"""Add an in-progress reaction when message processing begins."""
|
||||
|
|
@ -1773,7 +1773,7 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
|
||||
# Ignore message edits and deletions
|
||||
subtype = event.get("subtype")
|
||||
if subtype in ("message_changed", "message_deleted"):
|
||||
if subtype in {"message_changed", "message_deleted"}:
|
||||
return
|
||||
|
||||
original_text = event.get("text", "")
|
||||
|
|
@ -1892,7 +1892,7 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
channel_type = event.get("channel_type", "")
|
||||
if not channel_type and channel_id.startswith("D"):
|
||||
channel_type = "im"
|
||||
is_dm = channel_type in ("im", "mpim") # Both 1:1 and group DMs
|
||||
is_dm = channel_type in {"im", "mpim"} # Both 1:1 and group DMs
|
||||
|
||||
# Build thread_ts for session keying.
|
||||
# In channels: fall back to ts so each top-level @mention starts a
|
||||
|
|
@ -2033,7 +2033,7 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
if mimetype.startswith("image/") and url:
|
||||
try:
|
||||
ext = "." + mimetype.split("/")[-1].split(";")[0]
|
||||
if ext not in (".jpg", ".jpeg", ".png", ".gif", ".webp"):
|
||||
if ext not in {".jpg", ".jpeg", ".png", ".gif", ".webp"}:
|
||||
ext = ".jpg"
|
||||
# Slack private URLs require the bot token as auth header
|
||||
cached = await self._download_slack_file(url, ext, team_id=team_id)
|
||||
|
|
@ -2049,7 +2049,7 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
elif mimetype.startswith("audio/") and url:
|
||||
try:
|
||||
ext = "." + mimetype.split("/")[-1].split(";")[0]
|
||||
if ext not in (".ogg", ".mp3", ".wav", ".webm", ".m4a"):
|
||||
if ext not in {".ogg", ".mp3", ".wav", ".webm", ".m4a"}:
|
||||
ext = ".ogg"
|
||||
cached = await self._download_slack_file(url, ext, audio=True, team_id=team_id)
|
||||
media_urls.append(cached)
|
||||
|
|
@ -2737,7 +2737,7 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
if team_id and channel_id:
|
||||
self._channel_team[channel_id] = team_id
|
||||
|
||||
if slash_name in ("hermes", ""):
|
||||
if slash_name in {"hermes", ""}:
|
||||
# Legacy /hermes <subcommand> [args] routing + free-form questions.
|
||||
# Empty slash_name falls into this branch for backward compat
|
||||
# with any caller that didn't populate command["command"].
|
||||
|
|
@ -2932,9 +2932,9 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
configured = self.config.extra.get("require_mention")
|
||||
if configured is not None:
|
||||
if isinstance(configured, str):
|
||||
return configured.lower() not in ("false", "0", "no", "off")
|
||||
return configured.lower() not in {"false", "0", "no", "off"}
|
||||
return bool(configured)
|
||||
return os.getenv("SLACK_REQUIRE_MENTION", "true").lower() not in ("false", "0", "no", "off")
|
||||
return os.getenv("SLACK_REQUIRE_MENTION", "true").lower() not in {"false", "0", "no", "off"}
|
||||
|
||||
def _slack_strict_mention(self) -> bool:
|
||||
"""When true, channel threads require an explicit @-mention on every
|
||||
|
|
@ -2944,9 +2944,9 @@ class SlackAdapter(BasePlatformAdapter):
|
|||
configured = self.config.extra.get("strict_mention")
|
||||
if configured is not None:
|
||||
if isinstance(configured, str):
|
||||
return configured.lower() in ("true", "1", "yes", "on")
|
||||
return configured.lower() in {"true", "1", "yes", "on"}
|
||||
return bool(configured)
|
||||
return os.getenv("SLACK_STRICT_MENTION", "false").lower() in ("true", "1", "yes", "on")
|
||||
return os.getenv("SLACK_STRICT_MENTION", "false").lower() in {"true", "1", "yes", "on"}
|
||||
|
||||
def _slack_free_response_channels(self) -> set:
|
||||
"""Return channel IDs where no @mention is required."""
|
||||
|
|
|
|||
|
|
@ -616,7 +616,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
def _looks_like_network_error(error: Exception) -> bool:
|
||||
"""Return True for transient network errors that warrant a reconnect attempt."""
|
||||
name = error.__class__.__name__.lower()
|
||||
if name in ("networkerror", "timedout", "connectionerror"):
|
||||
if name in {"networkerror", "timedout", "connectionerror"}:
|
||||
return True
|
||||
try:
|
||||
from telegram.error import NetworkError, TimedOut
|
||||
|
|
@ -632,9 +632,9 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
return default
|
||||
if isinstance(value, str):
|
||||
lowered = value.strip().lower()
|
||||
if lowered in ("true", "1", "yes", "on"):
|
||||
if lowered in {"true", "1", "yes", "on"}:
|
||||
return True
|
||||
if lowered in ("false", "0", "no", "off"):
|
||||
if lowered in {"false", "0", "no", "off"}:
|
||||
return False
|
||||
return default
|
||||
return bool(value)
|
||||
|
|
@ -1171,7 +1171,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
"write_timeout": _env_float("HERMES_TELEGRAM_HTTP_WRITE_TIMEOUT", 20.0),
|
||||
}
|
||||
|
||||
disable_fallback = (os.getenv("HERMES_TELEGRAM_DISABLE_FALLBACK_IPS", "").strip().lower() in ("1", "true", "yes", "on"))
|
||||
disable_fallback = (os.getenv("HERMES_TELEGRAM_DISABLE_FALLBACK_IPS", "").strip().lower() in {"1", "true", "yes", "on"})
|
||||
fallback_ips = self._fallback_ips()
|
||||
if not fallback_ips:
|
||||
fallback_ips = await discover_fallback_ips()
|
||||
|
|
@ -1917,7 +1917,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
"""
|
||||
if not self._bot or not hasattr(self._bot, "send_message_draft"):
|
||||
return False
|
||||
return (chat_type or "").lower() in ("dm", "private")
|
||||
return (chat_type or "").lower() in {"dm", "private"}
|
||||
|
||||
async def send_draft(
|
||||
self,
|
||||
|
|
@ -2723,7 +2723,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
with open(audio_path, "rb") as audio_file:
|
||||
ext = os.path.splitext(audio_path)[1].lower()
|
||||
# .ogg / .opus files -> send as voice (round playable bubble)
|
||||
if ext in (".ogg", ".opus"):
|
||||
if ext in {".ogg", ".opus"}:
|
||||
_voice_thread = self._metadata_thread_id(metadata)
|
||||
reply_to_id = self._reply_to_message_id_for_send(reply_to, metadata)
|
||||
voice_thread_kwargs = self._thread_kwargs_for_send(
|
||||
|
|
@ -2747,7 +2747,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
"voice",
|
||||
reset_media=lambda: audio_file.seek(0),
|
||||
)
|
||||
elif ext in (".mp3", ".m4a"):
|
||||
elif ext in {".mp3", ".m4a"}:
|
||||
# Telegram's Bot API sendAudio only accepts MP3 / M4A.
|
||||
_audio_thread = self._metadata_thread_id(metadata)
|
||||
reply_to_id = self._reply_to_message_id_for_send(reply_to, metadata)
|
||||
|
|
@ -3498,18 +3498,18 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
configured = self.config.extra.get("require_mention")
|
||||
if configured is not None:
|
||||
if isinstance(configured, str):
|
||||
return configured.lower() in ("true", "1", "yes", "on")
|
||||
return configured.lower() in {"true", "1", "yes", "on"}
|
||||
return bool(configured)
|
||||
return os.getenv("TELEGRAM_REQUIRE_MENTION", "false").lower() in ("true", "1", "yes", "on")
|
||||
return os.getenv("TELEGRAM_REQUIRE_MENTION", "false").lower() in {"true", "1", "yes", "on"}
|
||||
|
||||
def _telegram_guest_mode(self) -> bool:
|
||||
"""Return whether non-allowlisted groups may trigger via direct @mention."""
|
||||
configured = self.config.extra.get("guest_mode")
|
||||
if configured is not None:
|
||||
if isinstance(configured, str):
|
||||
return configured.lower() in ("true", "1", "yes", "on")
|
||||
return configured.lower() in {"true", "1", "yes", "on"}
|
||||
return bool(configured)
|
||||
return os.getenv("TELEGRAM_GUEST_MODE", "false").lower() in ("true", "1", "yes", "on")
|
||||
return os.getenv("TELEGRAM_GUEST_MODE", "false").lower() in {"true", "1", "yes", "on"}
|
||||
|
||||
def _telegram_free_response_chats(self) -> set[str]:
|
||||
raw = self.config.extra.get("free_response_chats")
|
||||
|
|
@ -3598,7 +3598,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
if not chat:
|
||||
return False
|
||||
chat_type = str(getattr(chat, "type", "")).split(".")[-1].lower()
|
||||
return chat_type in ("group", "supergroup")
|
||||
return chat_type in {"group", "supergroup"}
|
||||
|
||||
def _is_reply_to_bot(self, message: Message) -> bool:
|
||||
if not self._bot or not getattr(message, "reply_to_message", None):
|
||||
|
|
@ -4157,7 +4157,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
|
||||
# For text files, inject content into event.text (capped at 100 KB)
|
||||
MAX_TEXT_INJECT_BYTES = 100 * 1024
|
||||
if ext in (".md", ".txt") and len(raw_bytes) <= MAX_TEXT_INJECT_BYTES:
|
||||
if ext in {".md", ".txt"} and len(raw_bytes) <= MAX_TEXT_INJECT_BYTES:
|
||||
try:
|
||||
text_content = raw_bytes.decode("utf-8")
|
||||
display_name = original_filename or f"document{ext}"
|
||||
|
|
@ -4396,7 +4396,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
|
||||
# Determine chat type
|
||||
chat_type = "dm"
|
||||
if chat.type in (ChatType.GROUP, ChatType.SUPERGROUP):
|
||||
if chat.type in {ChatType.GROUP, ChatType.SUPERGROUP}:
|
||||
chat_type = "group"
|
||||
elif chat.type == ChatType.CHANNEL:
|
||||
chat_type = "channel"
|
||||
|
|
@ -4512,7 +4512,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
|
||||
def _reactions_enabled(self) -> bool:
|
||||
"""Check if message reactions are enabled via config/env."""
|
||||
return os.getenv("TELEGRAM_REACTIONS", "false").lower() not in ("false", "0", "no")
|
||||
return os.getenv("TELEGRAM_REACTIONS", "false").lower() not in {"false", "0", "no"}
|
||||
|
||||
async def _set_reaction(self, chat_id: str, message_id: str, emoji: str) -> bool:
|
||||
"""Set a single emoji reaction on a Telegram message."""
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ class WeComAdapter(BasePlatformAdapter):
|
|||
|
||||
auth_payload = await self._wait_for_handshake(req_id)
|
||||
errcode = auth_payload.get("errcode", 0)
|
||||
if errcode not in (0, None):
|
||||
if errcode not in {0, None}:
|
||||
errmsg = auth_payload.get("errmsg", "authentication failed")
|
||||
raise RuntimeError(f"{errmsg} (errcode={errcode})")
|
||||
|
||||
|
|
@ -320,7 +320,7 @@ class WeComAdapter(BasePlatformAdapter):
|
|||
if self._payload_req_id(payload) == req_id:
|
||||
return payload
|
||||
logger.debug("[%s] Ignoring pre-auth payload: %s", self.name, payload.get("cmd"))
|
||||
elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.ERROR):
|
||||
elif msg.type in {aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.ERROR}:
|
||||
raise RuntimeError("WeCom websocket closed during authentication")
|
||||
|
||||
async def _listen_loop(self) -> None:
|
||||
|
|
@ -360,7 +360,7 @@ class WeComAdapter(BasePlatformAdapter):
|
|||
payload = self._parse_json(msg.data)
|
||||
if payload:
|
||||
await self._dispatch_payload(payload)
|
||||
elif msg.type in (aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR):
|
||||
elif msg.type in {aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR}:
|
||||
raise RuntimeError("WeCom websocket closed")
|
||||
|
||||
async def _heartbeat_loop(self) -> None:
|
||||
|
|
@ -998,7 +998,7 @@ class WeComAdapter(BasePlatformAdapter):
|
|||
@staticmethod
|
||||
def _response_error(response: Dict[str, Any]) -> Optional[str]:
|
||||
errcode = response.get("errcode", 0)
|
||||
if errcode in (0, None):
|
||||
if errcode in {0, None}:
|
||||
return None
|
||||
errmsg = str(response.get("errmsg") or "unknown error")
|
||||
return f"WeCom errcode {errcode}: {errmsg}"
|
||||
|
|
|
|||
|
|
@ -605,7 +605,7 @@ def _assert_weixin_cdn_url(url: str) -> None:
|
|||
except Exception as exc: # noqa: BLE001
|
||||
raise ValueError(f"Unparseable media URL: {url!r}") from exc
|
||||
|
||||
if scheme not in ("http", "https"):
|
||||
if scheme not in {"http", "https"}:
|
||||
raise ValueError(
|
||||
f"Media URL has disallowed scheme {scheme!r}; only http/https are permitted."
|
||||
)
|
||||
|
|
@ -983,7 +983,7 @@ def _extract_text(item_list: List[Dict[str, Any]]) -> str:
|
|||
ref = item.get("ref_msg") or {}
|
||||
ref_item = ref.get("message_item") or {}
|
||||
ref_type = ref_item.get("type")
|
||||
if ref_type in (ITEM_IMAGE, ITEM_VIDEO, ITEM_FILE, ITEM_VOICE):
|
||||
if ref_type in {ITEM_IMAGE, ITEM_VIDEO, ITEM_FILE, ITEM_VOICE}:
|
||||
title = ref.get("title") or ""
|
||||
prefix = f"[引用媒体: {title}]\n" if title else "[引用媒体]\n"
|
||||
return f"{prefix}{text}".strip()
|
||||
|
|
@ -1331,7 +1331,7 @@ class WeixinAdapter(BasePlatformAdapter):
|
|||
|
||||
ret = response.get("ret", 0)
|
||||
errcode = response.get("errcode", 0)
|
||||
if ret not in (0, None) or errcode not in (0, None):
|
||||
if ret not in {0, None} or errcode not in {0, None}:
|
||||
if (ret == SESSION_EXPIRED_ERRCODE or errcode == SESSION_EXPIRED_ERRCODE
|
||||
or _is_stale_session_ret(ret, errcode, response.get("errmsg"))):
|
||||
logger.error("[%s] Session expired; pausing for 10 minutes", self.name)
|
||||
|
|
@ -1601,7 +1601,7 @@ class WeixinAdapter(BasePlatformAdapter):
|
|||
if resp and isinstance(resp, dict):
|
||||
ret = resp.get("ret")
|
||||
errcode = resp.get("errcode")
|
||||
if (ret is not None and ret not in (0,)) or (errcode is not None and errcode not in (0,)):
|
||||
if (ret is not None and ret not in {0,}) or (errcode is not None and errcode not in {0,}):
|
||||
is_session_expired = (
|
||||
ret == SESSION_EXPIRED_ERRCODE
|
||||
or errcode == SESSION_EXPIRED_ERRCODE
|
||||
|
|
|
|||
|
|
@ -301,9 +301,9 @@ class WhatsAppAdapter(BasePlatformAdapter):
|
|||
configured = self.config.extra.get("require_mention")
|
||||
if configured is not None:
|
||||
if isinstance(configured, str):
|
||||
return configured.lower() in ("true", "1", "yes", "on")
|
||||
return configured.lower() in {"true", "1", "yes", "on"}
|
||||
return bool(configured)
|
||||
return os.getenv("WHATSAPP_REQUIRE_MENTION", "false").lower() in ("true", "1", "yes", "on")
|
||||
return os.getenv("WHATSAPP_REQUIRE_MENTION", "false").lower() in {"true", "1", "yes", "on"}
|
||||
|
||||
def _whatsapp_free_response_chats(self) -> set[str]:
|
||||
raw = self.config.extra.get("free_response_chats")
|
||||
|
|
@ -679,7 +679,7 @@ class WhatsAppAdapter(BasePlatformAdapter):
|
|||
# getattr-with-default keeps tests that construct the adapter via
|
||||
# ``WhatsAppAdapter.__new__`` (bypassing __init__) working without
|
||||
# every _make_adapter() helper having to seed the attribute.
|
||||
if getattr(self, "_shutting_down", False) and returncode in (0, -2, -15):
|
||||
if getattr(self, "_shutting_down", False) and returncode in {0, -2, -15}:
|
||||
logger.info(
|
||||
"[%s] Bridge exited during shutdown (code %d).",
|
||||
self.name,
|
||||
|
|
@ -1183,7 +1183,7 @@ class WhatsAppAdapter(BasePlatformAdapter):
|
|||
if msg_type == MessageType.DOCUMENT and cached_urls:
|
||||
for doc_path in cached_urls:
|
||||
ext = Path(doc_path).suffix.lower()
|
||||
if ext in (".txt", ".md", ".csv", ".json", ".xml", ".yaml", ".yml", ".log", ".py", ".js", ".ts", ".html", ".css"):
|
||||
if ext in {".txt", ".md", ".csv", ".json", ".xml", ".yaml", ".yml", ".log", ".py", ".js", ".ts", ".html", ".css"}:
|
||||
try:
|
||||
file_size = Path(doc_path).stat().st_size
|
||||
if file_size > MAX_TEXT_INJECT_BYTES:
|
||||
|
|
|
|||
|
|
@ -2228,7 +2228,7 @@ class MediaResolveMiddleware(InboundMiddleware):
|
|||
resp.raise_for_status()
|
||||
payload = resp.json()
|
||||
code = payload.get("code")
|
||||
if code not in (None, 0):
|
||||
if code not in {None, 0}:
|
||||
raise RuntimeError(
|
||||
f"resource/v1/download failed: code={code}, msg={payload.get('msg', '')}"
|
||||
)
|
||||
|
|
@ -2391,7 +2391,7 @@ class MediaResolveMiddleware(InboundMiddleware):
|
|||
rid = m.group(2)
|
||||
kind, _, filename = head.partition(":")
|
||||
kind = kind.strip()
|
||||
if kind not in ("image", "file"):
|
||||
if kind not in {"image", "file"}:
|
||||
continue
|
||||
if rid in seen:
|
||||
continue
|
||||
|
|
@ -2993,10 +2993,10 @@ class ConnectionManager:
|
|||
|
||||
# Fire-and-forget heartbeat ACKs — server always responds but callers don't
|
||||
# wait on these; silently discard to avoid "Unmatched Response" noise.
|
||||
if cmd_type == CMD_TYPE["Response"] and cmd in (
|
||||
if cmd_type == CMD_TYPE["Response"] and cmd in {
|
||||
"send_group_heartbeat",
|
||||
"send_private_heartbeat",
|
||||
):
|
||||
}:
|
||||
logger.debug("[%s] Heartbeat ACK received: cmd=%s msg_id=%s", adapter.name, cmd, msg_id)
|
||||
return
|
||||
|
||||
|
|
@ -3369,7 +3369,7 @@ class MediaSendHandler(ABC):
|
|||
# Remove keys already passed explicitly to avoid "multiple values" TypeError
|
||||
fwd_kwargs = {
|
||||
k: v for k, v in kwargs.items()
|
||||
if k not in ("file_uuid", "filename", "content_type")
|
||||
if k not in {"file_uuid", "filename", "content_type"}
|
||||
}
|
||||
msg_body = self.build_msg_body(
|
||||
upload_result,
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ def _parse_jpeg_size(buf: bytes) -> Optional[dict[str, int]]:
|
|||
i += 1
|
||||
continue
|
||||
marker = buf[i + 1]
|
||||
if marker in (0xC0, 0xC2):
|
||||
if marker in {0xC0, 0xC2}:
|
||||
h = struct.unpack(">H", buf[i + 5: i + 7])[0]
|
||||
w = struct.unpack(">H", buf[i + 7: i + 9])[0]
|
||||
return {"width": w, "height": h}
|
||||
|
|
@ -165,7 +165,7 @@ def _parse_gif_size(buf: bytes) -> Optional[dict[str, int]]:
|
|||
if len(buf) < 10:
|
||||
return None
|
||||
sig = buf[:6].decode("ascii", errors="replace")
|
||||
if sig not in ("GIF87a", "GIF89a"):
|
||||
if sig not in {"GIF87a", "GIF89a"}:
|
||||
return None
|
||||
w = struct.unpack("<H", buf[6:8])[0]
|
||||
h = struct.unpack("<H", buf[8:10])[0]
|
||||
|
|
|
|||
|
|
@ -702,7 +702,7 @@ def decode_inbound_push(data: bytes) -> Optional[dict]:
|
|||
"trace_id": trace_id,
|
||||
}
|
||||
# 过滤空值(保持 API 整洁)
|
||||
return {k: v for k, v in result.items() if v or k in ("msg_body", "msg_seq")}
|
||||
return {k: v for k, v in result.items() if v or k in {"msg_body", "msg_seq"}}
|
||||
except Exception as e:
|
||||
if DEBUG_MODE:
|
||||
logger.debug("[yuanbao_proto] decode_inbound_push failed: %s", e)
|
||||
|
|
|
|||
122
gateway/run.py
122
gateway/run.py
|
|
@ -288,7 +288,7 @@ def _last_transcript_timestamp(history: Optional[List[Dict[str, Any]]]) -> Any:
|
|||
if not isinstance(msg, dict):
|
||||
continue
|
||||
role = msg.get("role")
|
||||
if not role or role in ("session_meta", "system"):
|
||||
if not role or role in {"session_meta", "system"}:
|
||||
continue
|
||||
ts = msg.get("timestamp")
|
||||
if ts is not None:
|
||||
|
|
@ -472,7 +472,7 @@ if _config_path.exists():
|
|||
# gateway resolves these to Path.home() later (line ~255).
|
||||
# Writing the raw placeholder here would just be noise.
|
||||
# Only bridge explicit absolute paths from config.yaml.
|
||||
if _cfg_key == "cwd" and str(_val) in (".", "auto", "cwd"):
|
||||
if _cfg_key == "cwd" and str(_val) in {".", "auto", "cwd"}:
|
||||
continue
|
||||
# Expand shell tilde in cwd so subprocess.Popen never
|
||||
# receives a literal "~/" which the kernel rejects.
|
||||
|
|
@ -616,7 +616,7 @@ os.environ["HERMES_EXEC_ASK"] = "1"
|
|||
# to home directory. MESSAGING_CWD is accepted as a backward-compat
|
||||
# fallback (deprecated — the warning above tells users to migrate).
|
||||
_configured_cwd = os.environ.get("TERMINAL_CWD", "")
|
||||
if not _configured_cwd or _configured_cwd in (".", "auto", "cwd"):
|
||||
if not _configured_cwd or _configured_cwd in {".", "auto", "cwd"}:
|
||||
_fallback = os.getenv("MESSAGING_CWD") or str(Path.home())
|
||||
os.environ["TERMINAL_CWD"] = _fallback
|
||||
|
||||
|
|
@ -849,7 +849,7 @@ def _skill_slug_from_frontmatter(skill_md: Path) -> tuple[str | None, str | None
|
|||
if line.startswith("name:"):
|
||||
raw = line.split(":", 1)[1].strip()
|
||||
# Strip YAML quote wrappers if present
|
||||
if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in ('"', "'"):
|
||||
if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in {'"', "'"}:
|
||||
raw = raw[1:-1]
|
||||
declared_name = raw.strip()
|
||||
break
|
||||
|
|
@ -891,7 +891,7 @@ def _check_unavailable_skill(command_name: str) -> str | None:
|
|||
if not skills_dir.exists():
|
||||
continue
|
||||
for skill_md in skills_dir.rglob("SKILL.md"):
|
||||
if any(part in ('.git', '.github', '.hub', '.archive') for part in skill_md.parts):
|
||||
if any(part in {'.git', '.github', '.hub', '.archive'} for part in skill_md.parts):
|
||||
continue
|
||||
slug, declared_name = _skill_slug_from_frontmatter(skill_md)
|
||||
if not slug or not declared_name:
|
||||
|
|
@ -1033,7 +1033,7 @@ def _parse_session_key(session_key: str) -> "dict | None":
|
|||
"chat_type": parts[3],
|
||||
"chat_id": parts[4],
|
||||
}
|
||||
if len(parts) > 5 and parts[3] in ("dm", "thread"):
|
||||
if len(parts) > 5 and parts[3] in {"dm", "thread"}:
|
||||
result["thread_id"] = parts[5]
|
||||
return result
|
||||
return None
|
||||
|
|
@ -1561,7 +1561,7 @@ class GatewayRunner:
|
|||
enabled_chats.clear()
|
||||
enabled_chats.update(
|
||||
key[len(prefix):] for key, mode in self._voice_mode.items()
|
||||
if mode in ("voice_only", "all") and key.startswith(prefix)
|
||||
if mode in {"voice_only", "all"} and key.startswith(prefix)
|
||||
)
|
||||
|
||||
async def _safe_adapter_disconnect(self, adapter, platform) -> None:
|
||||
|
|
@ -1991,7 +1991,7 @@ class GatewayRunner:
|
|||
# Both "queue" and "steer" modes imply the user doesn't want messages
|
||||
# to be lost during restart — queue them for the newly-spawned gateway
|
||||
# process to pick up. "interrupt" mode drops them (current behaviour).
|
||||
return self._restart_requested and self._busy_input_mode in ("queue", "steer")
|
||||
return self._restart_requested and self._busy_input_mode in {"queue", "steer"}
|
||||
|
||||
# -------- /queue FIFO helpers --------------------------------------
|
||||
# /queue must produce one full agent turn per invocation, in FIFO
|
||||
|
|
@ -2401,7 +2401,7 @@ class GatewayRunner:
|
|||
raw = cfg_get(cfg, "display", "background_process_notifications")
|
||||
if raw is False:
|
||||
mode = "off"
|
||||
elif raw not in (None, ""):
|
||||
elif raw not in {None, ""}:
|
||||
mode = str(raw)
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -3247,7 +3247,7 @@ class GatewayRunner:
|
|||
# for this process's lifetime.
|
||||
try:
|
||||
_redact_raw = os.getenv("HERMES_REDACT_SECRETS", "true")
|
||||
_redact_on = _redact_raw.lower() in ("1", "true", "yes", "on")
|
||||
_redact_on = _redact_raw.lower() in {"1", "true", "yes", "on"}
|
||||
if _redact_on:
|
||||
logger.info(
|
||||
"Secret redaction: ENABLED (tool output, logs, and chat "
|
||||
|
|
@ -3329,8 +3329,8 @@ class GatewayRunner:
|
|||
_any_allowlist = any(
|
||||
os.getenv(v) for v in _builtin_allowed_vars + _plugin_allowed_vars
|
||||
)
|
||||
_allow_all = os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in ("true", "1", "yes") or any(
|
||||
os.getenv(v, "").lower() in ("true", "1", "yes")
|
||||
_allow_all = os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in {"true", "1", "yes"} or any(
|
||||
os.getenv(v, "").lower() in {"true", "1", "yes"}
|
||||
for v in _builtin_allow_all_vars + _plugin_allow_all_vars
|
||||
)
|
||||
if not _any_allowlist and not _allow_all:
|
||||
|
|
@ -4379,7 +4379,7 @@ class GatewayRunner:
|
|||
# dispatcher respawns the task and it cycles into the
|
||||
# same state. See the longer comment on TERMINAL_KINDS
|
||||
# above for the failure mode this prevents.
|
||||
task_terminal = task and task.status in ("done", "archived")
|
||||
task_terminal = task and task.status in {"done", "archived"}
|
||||
if task_terminal:
|
||||
await asyncio.to_thread(
|
||||
self._kanban_unsub, sub, board_slug,
|
||||
|
|
@ -4479,7 +4479,7 @@ class GatewayRunner:
|
|||
logger.warning("kanban dispatcher: config loader unavailable; disabled")
|
||||
return
|
||||
env_override = os.environ.get("HERMES_KANBAN_DISPATCH_IN_GATEWAY", "").strip().lower()
|
||||
if env_override in ("0", "false", "no", "off"):
|
||||
if env_override in {"0", "false", "no", "off"}:
|
||||
logger.info("kanban dispatcher: disabled via HERMES_KANBAN_DISPATCH_IN_GATEWAY env")
|
||||
return
|
||||
|
||||
|
|
@ -5156,12 +5156,12 @@ class GatewayRunner:
|
|||
try:
|
||||
_gw_cfg = _load_gateway_config()
|
||||
_raw = cfg_get(_gw_cfg, "display", "platforms", "telegram", "notifications")
|
||||
if _raw not in (None, ""):
|
||||
if _raw not in {None, ""}:
|
||||
_notify_mode = str(_raw).strip().lower()
|
||||
except Exception:
|
||||
pass
|
||||
_notify_mode = _notify_mode or "important"
|
||||
if _notify_mode not in ("all", "important"):
|
||||
if _notify_mode not in {"all", "important"}:
|
||||
logger.warning(
|
||||
"Unknown telegram notifications mode '%s', "
|
||||
"defaulting to 'important' (valid: all, important)",
|
||||
|
|
@ -5338,7 +5338,7 @@ class GatewayRunner:
|
|||
# connection, so HA events are always authorized.
|
||||
# Webhook events are authenticated via HMAC signature validation in
|
||||
# the adapter itself — no user allowlist applies.
|
||||
if source.platform in (Platform.HOMEASSISTANT, Platform.WEBHOOK):
|
||||
if source.platform in {Platform.HOMEASSISTANT, Platform.WEBHOOK}:
|
||||
return True
|
||||
|
||||
user_id = source.user_id
|
||||
|
|
@ -5411,12 +5411,12 @@ class GatewayRunner:
|
|||
|
||||
# Per-platform allow-all flag (e.g., DISCORD_ALLOW_ALL_USERS=true)
|
||||
platform_allow_all_var = platform_allow_all_map.get(source.platform, "")
|
||||
if platform_allow_all_var and os.getenv(platform_allow_all_var, "").lower() in ("true", "1", "yes"):
|
||||
if platform_allow_all_var and os.getenv(platform_allow_all_var, "").lower() in {"true", "1", "yes"}:
|
||||
return True
|
||||
|
||||
if getattr(source, "is_bot", False):
|
||||
allow_bots_var = platform_allow_bots_map.get(source.platform)
|
||||
if allow_bots_var and os.getenv(allow_bots_var, "none").lower().strip() in ("mentions", "all"):
|
||||
if allow_bots_var and os.getenv(allow_bots_var, "none").lower().strip() in {"mentions", "all"}:
|
||||
return True
|
||||
|
||||
# Discord role-based access (DISCORD_ALLOWED_ROLES): the adapter's
|
||||
|
|
@ -5447,7 +5447,7 @@ class GatewayRunner:
|
|||
|
||||
if not platform_allowlist and not group_user_allowlist and not group_chat_allowlist and not global_allowlist:
|
||||
# No allowlists configured -- check global allow-all flag
|
||||
return os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in ("true", "1", "yes")
|
||||
return os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in {"true", "1", "yes"}
|
||||
|
||||
# Telegram can optionally authorize group traffic by chat ID.
|
||||
# Keep this separate from TELEGRAM_GROUP_ALLOWED_USERS, which gates
|
||||
|
|
@ -5742,9 +5742,9 @@ class GatewayRunner:
|
|||
raw = (event.text or "").strip()
|
||||
# Accept /approve and /deny as shorthand for yes/no
|
||||
cmd = event.get_command()
|
||||
if cmd in ("approve", "yes"):
|
||||
if cmd in {"approve", "yes"}:
|
||||
response_text = "y"
|
||||
elif cmd in ("deny", "no"):
|
||||
elif cmd in {"deny", "no"}:
|
||||
response_text = "n"
|
||||
else:
|
||||
_recognized_cmd = None
|
||||
|
|
@ -5826,17 +5826,17 @@ class GatewayRunner:
|
|||
_raw_reply = (event.text or "").strip()
|
||||
_cmd_reply = event.get_command()
|
||||
_confirm_choice = None
|
||||
if _cmd_reply in ("approve", "yes", "ok", "confirm"):
|
||||
if _cmd_reply in {"approve", "yes", "ok", "confirm"}:
|
||||
_confirm_choice = "once"
|
||||
elif _cmd_reply in ("always", "remember"):
|
||||
elif _cmd_reply in {"always", "remember"}:
|
||||
_confirm_choice = "always"
|
||||
elif _cmd_reply in ("cancel", "no", "deny", "nevermind"):
|
||||
elif _cmd_reply in {"cancel", "no", "deny", "nevermind"}:
|
||||
_confirm_choice = "cancel"
|
||||
elif _raw_reply.lower() in ("approve", "approve once", "once"):
|
||||
elif _raw_reply.lower() in {"approve", "approve once", "once"}:
|
||||
_confirm_choice = "once"
|
||||
elif _raw_reply.lower() in ("always", "always approve"):
|
||||
elif _raw_reply.lower() in {"always", "always approve"}:
|
||||
_confirm_choice = "always"
|
||||
elif _raw_reply.lower() in ("cancel", "nevermind", "no"):
|
||||
elif _raw_reply.lower() in {"cancel", "nevermind", "no"}:
|
||||
_confirm_choice = "cancel"
|
||||
if _confirm_choice is not None:
|
||||
_resolved = await _slash_confirm_mod.resolve(
|
||||
|
|
@ -5972,7 +5972,7 @@ class GatewayRunner:
|
|||
# Semantics: each /queue invocation produces its own full agent
|
||||
# turn, processed in FIFO order after the current run (and any
|
||||
# earlier /queue items) finishes. Messages are NOT merged.
|
||||
if event.get_command() in ("queue", "q"):
|
||||
if event.get_command() in {"queue", "q"}:
|
||||
queued_text = event.get_command_args().strip()
|
||||
if not queued_text:
|
||||
return "Usage: /queue <prompt>"
|
||||
|
|
@ -6045,7 +6045,7 @@ class GatewayRunner:
|
|||
# The agent thread is blocked on a threading.Event inside
|
||||
# tools/approval.py — sending an interrupt won't unblock it.
|
||||
# Route directly to the approval handler so the event is signalled.
|
||||
if _cmd_def_inner and _cmd_def_inner.name in ("approve", "deny"):
|
||||
if _cmd_def_inner and _cmd_def_inner.name in {"approve", "deny"}:
|
||||
if _cmd_def_inner.name == "approve":
|
||||
return await self._handle_approve_command(event)
|
||||
return await self._handle_deny_command(event)
|
||||
|
|
@ -6076,7 +6076,7 @@ class GatewayRunner:
|
|||
# continuation prompt against the current turn.
|
||||
if _cmd_def_inner and _cmd_def_inner.name == "goal":
|
||||
_goal_arg = (event.get_command_args() or "").strip().lower()
|
||||
if not _goal_arg or _goal_arg in ("status", "pause", "resume", "clear", "stop", "done"):
|
||||
if not _goal_arg or _goal_arg in {"status", "pause", "resume", "clear", "stop", "done"}:
|
||||
return await self._handle_goal_command(event)
|
||||
return "Agent is running — use /goal status / pause / clear mid-run, or /stop before setting a new goal."
|
||||
|
||||
|
|
@ -6088,7 +6088,7 @@ class GatewayRunner:
|
|||
# /fast and /reasoning are config-only and take effect next
|
||||
# message, so they fall through to the catch-all busy response
|
||||
# below — users should wait and set them between turns.
|
||||
if _cmd_def_inner and _cmd_def_inner.name in ("yolo", "verbose"):
|
||||
if _cmd_def_inner and _cmd_def_inner.name in {"yolo", "verbose"}:
|
||||
if _cmd_def_inner.name == "yolo":
|
||||
return await self._handle_yolo_command(event)
|
||||
if _cmd_def_inner.name == "verbose":
|
||||
|
|
@ -6711,7 +6711,7 @@ class GatewayRunner:
|
|||
mtype = event.media_types[i] if i < len(event.media_types) else ""
|
||||
if mtype.startswith("image/") or event.message_type == MessageType.PHOTO:
|
||||
image_paths.append(path)
|
||||
if mtype.startswith("audio/") or event.message_type in (MessageType.VOICE, MessageType.AUDIO):
|
||||
if mtype.startswith("audio/") or event.message_type in {MessageType.VOICE, MessageType.AUDIO}:
|
||||
audio_paths.append(path)
|
||||
|
||||
if image_paths:
|
||||
|
|
@ -6780,7 +6780,7 @@ class GatewayRunner:
|
|||
_TEXT_EXTENSIONS = {".txt", ".md", ".csv", ".log", ".json", ".xml", ".yaml", ".yml", ".toml", ".ini", ".cfg"}
|
||||
for i, path in enumerate(event.media_urls):
|
||||
mtype = event.media_types[i] if i < len(event.media_types) else ""
|
||||
if mtype in ("", "application/octet-stream"):
|
||||
if mtype in {"", "application/octet-stream"}:
|
||||
_ext = os.path.splitext(path)[1].lower()
|
||||
if _ext in _TEXT_EXTENSIONS:
|
||||
mtype = "text/plain"
|
||||
|
|
@ -7164,7 +7164,7 @@ class GatewayRunner:
|
|||
if isinstance(_comp_cfg, dict):
|
||||
_hyg_compression_enabled = str(
|
||||
_comp_cfg.get("enabled", True)
|
||||
).lower() in ("true", "1", "yes")
|
||||
).lower() in {"true", "1", "yes"}
|
||||
_raw_hard_limit = _comp_cfg.get("hygiene_hard_message_limit")
|
||||
if _raw_hard_limit is not None:
|
||||
try:
|
||||
|
|
@ -7287,7 +7287,7 @@ class GatewayRunner:
|
|||
_hyg_msgs = [
|
||||
{"role": m.get("role"), "content": m.get("content")}
|
||||
for m in history
|
||||
if m.get("role") in ("user", "assistant")
|
||||
if m.get("role") in {"user", "assistant"}
|
||||
and m.get("content")
|
||||
]
|
||||
|
||||
|
|
@ -7651,7 +7651,7 @@ class GatewayRunner:
|
|||
while not _pr.completion_queue.empty():
|
||||
evt = _pr.completion_queue.get_nowait()
|
||||
evt_type = evt.get("type", "completion")
|
||||
if evt_type in ("watch_match", "watch_disabled"):
|
||||
if evt_type in {"watch_match", "watch_disabled"}:
|
||||
_watch_events.append(evt)
|
||||
# else: completion events are handled by the watcher task
|
||||
for evt in _watch_events:
|
||||
|
|
@ -7893,7 +7893,7 @@ class GatewayRunner:
|
|||
status_hint = " You are being rate-limited. Please wait a moment and try again."
|
||||
elif status_code == 529:
|
||||
status_hint = " The API is temporarily overloaded. Please try again shortly."
|
||||
elif status_code in (400, 500):
|
||||
elif status_code in {400, 500}:
|
||||
# 400 with a large session is context overflow.
|
||||
# 500 with a large session often means the payload is too large
|
||||
# for the API to process — treat it the same way.
|
||||
|
|
@ -8255,7 +8255,7 @@ class GatewayRunner:
|
|||
policy = _policy_for_source(self.config, source)
|
||||
platform = source.platform.value if source and source.platform else "?"
|
||||
chat_type = (source.chat_type if source else "") or "dm"
|
||||
scope = "DM" if chat_type.lower() in ("dm", "direct", "private", "") else "group/channel"
|
||||
scope = "DM" if chat_type.lower() in {"dm", "direct", "private", ""} else "group/channel"
|
||||
user_id = (source.user_id if source else None) or "?"
|
||||
|
||||
if not policy.enabled:
|
||||
|
|
@ -9193,7 +9193,7 @@ class GatewayRunner:
|
|||
return "\n".join(p for p in parts if p)
|
||||
return str(value)
|
||||
|
||||
if args in ("none", "default", "neutral"):
|
||||
if args in {"none", "default", "neutral"}:
|
||||
try:
|
||||
if "agent" not in config or not isinstance(config.get("agent"), dict):
|
||||
config["agent"] = {}
|
||||
|
|
@ -9345,7 +9345,7 @@ class GatewayRunner:
|
|||
return t("gateway.goal.no_resume")
|
||||
return t("gateway.goal.resumed", goal=state.goal)
|
||||
|
||||
if lower in ("clear", "stop", "done"):
|
||||
if lower in {"clear", "stop", "done"}:
|
||||
had = mgr.has_goal()
|
||||
mgr.clear()
|
||||
try:
|
||||
|
|
@ -9598,13 +9598,13 @@ class GatewayRunner:
|
|||
|
||||
adapter = self.adapters.get(platform)
|
||||
|
||||
if args in ("on", "enable"):
|
||||
if args in {"on", "enable"}:
|
||||
self._voice_mode[voice_key] = "voice_only"
|
||||
self._save_voice_modes()
|
||||
if adapter:
|
||||
self._set_adapter_auto_tts_enabled(adapter, chat_id, enabled=True)
|
||||
return t("gateway.voice.enabled_voice_only")
|
||||
elif args in ("off", "disable"):
|
||||
elif args in {"off", "disable"}:
|
||||
self._voice_mode[voice_key] = "off"
|
||||
self._save_voice_modes()
|
||||
if adapter:
|
||||
|
|
@ -9616,7 +9616,7 @@ class GatewayRunner:
|
|||
if adapter:
|
||||
self._set_adapter_auto_tts_enabled(adapter, chat_id, enabled=True)
|
||||
return t("gateway.voice.tts_enabled")
|
||||
elif args in ("channel", "join"):
|
||||
elif args in {"channel", "join"}:
|
||||
return await self._handle_voice_channel_join(event)
|
||||
elif args == "leave":
|
||||
return await self._handle_voice_channel_leave(event)
|
||||
|
|
@ -10390,12 +10390,12 @@ class GatewayRunner:
|
|||
|
||||
# Display toggle (per-platform)
|
||||
platform_key = _platform_config_key(event.source.platform)
|
||||
if args in ("show", "on"):
|
||||
if args in {"show", "on"}:
|
||||
self._show_reasoning = True
|
||||
_save_config_key(f"display.platforms.{platform_key}.show_reasoning", True)
|
||||
return t("gateway.reasoning.display_set_on", platform=platform_key)
|
||||
|
||||
if args in ("hide", "off"):
|
||||
if args in {"hide", "off"}:
|
||||
self._show_reasoning = False
|
||||
_save_config_key(f"display.platforms.{platform_key}.show_reasoning", False)
|
||||
return t("gateway.reasoning.display_set_off", platform=platform_key)
|
||||
|
|
@ -10411,7 +10411,7 @@ class GatewayRunner:
|
|||
return t("gateway.reasoning.reset_done")
|
||||
if effort == "none":
|
||||
parsed = {"enabled": False}
|
||||
elif effort in ("minimal", "low", "medium", "high", "xhigh"):
|
||||
elif effort in {"minimal", "low", "medium", "high", "xhigh"}:
|
||||
parsed = {"enabled": True, "effort": effort}
|
||||
else:
|
||||
return t(
|
||||
|
|
@ -10603,7 +10603,7 @@ class GatewayRunner:
|
|||
|
||||
effective = resolve_footer_config(user_config, platform_key)
|
||||
|
||||
if arg in ("status", "?"):
|
||||
if arg in {"status", "?"}:
|
||||
state = t("gateway.footer.state_on") if effective["enabled"] else t("gateway.footer.state_off")
|
||||
fields = ", ".join(effective.get("fields") or [])
|
||||
return t(
|
||||
|
|
@ -10613,9 +10613,9 @@ class GatewayRunner:
|
|||
platform=platform_key,
|
||||
)
|
||||
|
||||
if arg in ("on", "enable", "true", "1"):
|
||||
if arg in {"on", "enable", "true", "1"}:
|
||||
new_state = True
|
||||
elif arg in ("off", "disable", "false", "0"):
|
||||
elif arg in {"off", "disable", "false", "0"}:
|
||||
new_state = False
|
||||
elif arg == "":
|
||||
new_state = not effective["enabled"]
|
||||
|
|
@ -10683,7 +10683,7 @@ class GatewayRunner:
|
|||
msgs = [
|
||||
{"role": m.get("role"), "content": m.get("content")}
|
||||
for m in history
|
||||
if m.get("role") in ("user", "assistant") and m.get("content")
|
||||
if m.get("role") in {"user", "assistant"} and m.get("content")
|
||||
]
|
||||
|
||||
tmp_agent = AIAgent(
|
||||
|
|
@ -11597,7 +11597,7 @@ class GatewayRunner:
|
|||
history = self.session_store.load_transcript(session_entry.session_id)
|
||||
if history:
|
||||
from agent.model_metadata import estimate_messages_tokens_rough
|
||||
msgs = [m for m in history if m.get("role") in ("user", "assistant") and m.get("content")]
|
||||
msgs = [m for m in history if m.get("role") in {"user", "assistant"} and m.get("content")]
|
||||
approx = estimate_messages_tokens_rough(msgs)
|
||||
lines = [
|
||||
t("gateway.usage.header_session_info"),
|
||||
|
|
@ -12151,9 +12151,9 @@ class GatewayRunner:
|
|||
resolve_all = "all" in args
|
||||
remaining = [a for a in args if a != "all"]
|
||||
|
||||
if any(a in ("always", "permanent", "permanently") for a in remaining):
|
||||
if any(a in {"always", "permanent", "permanently"} for a in remaining):
|
||||
choice = "always"
|
||||
elif any(a in ("session", "ses") for a in remaining):
|
||||
elif any(a in {"session", "ses"} for a in remaining):
|
||||
choice = "session"
|
||||
else:
|
||||
choice = "once"
|
||||
|
|
@ -13270,8 +13270,8 @@ class GatewayRunner:
|
|||
# --- Normal text-only notification ---
|
||||
# Decide whether to notify based on mode
|
||||
should_notify = (
|
||||
notify_mode in ("all", "result")
|
||||
or (notify_mode == "error" and session.exit_code not in (0, None))
|
||||
notify_mode in {"all", "result"}
|
||||
or (notify_mode == "error" and session.exit_code not in {0, None})
|
||||
)
|
||||
if should_notify:
|
||||
new_output = session.output_buffer[-1000:] if session.output_buffer else ""
|
||||
|
|
@ -13866,7 +13866,7 @@ class GatewayRunner:
|
|||
for msg in history:
|
||||
role = msg.get("role")
|
||||
content = msg.get("content")
|
||||
if role in ("user", "assistant") and content:
|
||||
if role in {"user", "assistant"} and content:
|
||||
api_messages.append({"role": role, "content": content})
|
||||
|
||||
api_messages.append({"role": "user", "content": message})
|
||||
|
|
@ -14257,7 +14257,7 @@ class GatewayRunner:
|
|||
|
||||
|
||||
# Only act on tool.started events (ignore tool.completed, reasoning.available, etc.)
|
||||
if event_type not in ("tool.started",):
|
||||
if event_type not in {"tool.started",}:
|
||||
return
|
||||
|
||||
# Suppress tool-progress bubbles once the user has sent `stop`.
|
||||
|
|
@ -14954,7 +14954,7 @@ class GatewayRunner:
|
|||
|
||||
# Skip metadata entries (tool definitions, session info)
|
||||
# -- these are for transcript logging, not for the LLM
|
||||
if role in ("session_meta",):
|
||||
if role in {"session_meta",}:
|
||||
continue
|
||||
|
||||
# Skip system messages -- the agent rebuilds its own system prompt
|
||||
|
|
@ -14991,7 +14991,7 @@ class GatewayRunner:
|
|||
# even if the message list shrinks, we know which paths are old.
|
||||
_history_media_paths: set = set()
|
||||
for _hm in agent_history:
|
||||
if _hm.get("role") in ("tool", "function"):
|
||||
if _hm.get("role") in {"tool", "function"}:
|
||||
_hc = _hm.get("content", "")
|
||||
if "MEDIA:" in _hc:
|
||||
for _match in re.finditer(r'MEDIA:(\S+)', _hc):
|
||||
|
|
@ -15263,7 +15263,7 @@ class GatewayRunner:
|
|||
media_tags = []
|
||||
has_voice_directive = False
|
||||
for msg in result.get("messages", []):
|
||||
if msg.get("role") in ("tool", "function"):
|
||||
if msg.get("role") in {"tool", "function"}:
|
||||
content = msg.get("content", "")
|
||||
if "MEDIA:" in content:
|
||||
for match in re.finditer(r'MEDIA:(\S+)', content):
|
||||
|
|
|
|||
|
|
@ -764,12 +764,12 @@ class SessionStore:
|
|||
|
||||
now = _now()
|
||||
|
||||
if policy.mode in ("idle", "both"):
|
||||
if policy.mode in {"idle", "both"}:
|
||||
idle_deadline = entry.updated_at + timedelta(minutes=policy.idle_minutes)
|
||||
if now > idle_deadline:
|
||||
return True
|
||||
|
||||
if policy.mode in ("daily", "both"):
|
||||
if policy.mode in {"daily", "both"}:
|
||||
today_reset = now.replace(
|
||||
hour=policy.at_hour,
|
||||
minute=0, second=0, microsecond=0,
|
||||
|
|
@ -805,12 +805,12 @@ class SessionStore:
|
|||
|
||||
now = _now()
|
||||
|
||||
if policy.mode in ("idle", "both"):
|
||||
if policy.mode in {"idle", "both"}:
|
||||
idle_deadline = entry.updated_at + timedelta(minutes=policy.idle_minutes)
|
||||
if now > idle_deadline:
|
||||
return "idle"
|
||||
|
||||
if policy.mode in ("daily", "both"):
|
||||
if policy.mode in {"daily", "both"}:
|
||||
today_reset = now.replace(
|
||||
hour=policy.at_hour,
|
||||
minute=0,
|
||||
|
|
|
|||
|
|
@ -604,7 +604,7 @@ def acquire_scoped_lock(scope: str, identity: str, metadata: Optional[dict[str,
|
|||
for _line in _proc_status.read_text(encoding="utf-8").splitlines():
|
||||
if _line.startswith("State:"):
|
||||
_state = _line.split()[1]
|
||||
if _state in ("T", "t"): # stopped or tracing stop
|
||||
if _state in {"T", "t"}: # stopped or tracing stop
|
||||
stale = True
|
||||
break
|
||||
except (OSError, PermissionError):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue