feat(desktop): polish credentials settings and messaging env routing (#39217)
* feat(desktop): polish credentials settings and messaging env routing Align Provider API Keys and Tools & Keys with Advanced ListRow inputs, add Tools & Keys sidebar subnav, move platform env vars to Messaging via channel_managed discovery, strip toolset emojis, and condense cron actions. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): align Messaging credential inputs with settings ListRow style Remove monospace inputs and use CREDENTIAL_CONTROL_CLASS + ListRow layout to match Provider API Keys and Tools & Keys. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -82,6 +82,22 @@ CONFIGURABLE_TOOLSETS = [
|
||||
("computer_use", "🖱️ Computer Use (macOS)", "background desktop control via cua-driver"),
|
||||
]
|
||||
|
||||
|
||||
def gui_toolset_label(label: str) -> str:
|
||||
"""Strip leading emoji/icons from toolset titles for GUI surfaces.
|
||||
|
||||
Registry labels use ``<emoji> <title>``; plugin toolsets prefix with ``🔌``.
|
||||
CLI/TUI keeps the raw ``label`` — only HTTP APIs call this helper.
|
||||
"""
|
||||
text = (label or "").strip()
|
||||
if not text:
|
||||
return text
|
||||
parts = text.split(None, 1)
|
||||
if len(parts) == 2 and parts[0] and not any(ch.isascii() and ch.isalnum() for ch in parts[0]):
|
||||
return parts[1].strip()
|
||||
return text
|
||||
|
||||
|
||||
# Toolsets that are OFF by default for new installs.
|
||||
# They're still in _HERMES_CORE_TOOLS (available at runtime if enabled),
|
||||
# but the setup checklist won't pre-select them for first-time users.
|
||||
|
||||
@ -2833,18 +2833,66 @@ def _channel_managed_env_keys() -> frozenset[str]:
|
||||
return frozenset()
|
||||
|
||||
|
||||
# Cross-cutting gateway / relay knobs stay on the Keys → Settings tab even though
|
||||
# they use the ``messaging`` category in OPTIONAL_ENV_VARS. Platform-scoped vars
|
||||
# (``DISCORD_*``, ``MATRIX_*``, …) are owned by the Messaging UI instead.
|
||||
_MESSAGING_KEYS_PAGE_KEYS = frozenset({
|
||||
"GATEWAY_ALLOW_ALL_USERS",
|
||||
"GATEWAY_PROXY_KEY",
|
||||
"GATEWAY_PROXY_URL",
|
||||
})
|
||||
|
||||
|
||||
def _platform_env_prefixes(platform_id: str) -> tuple[str, ...]:
|
||||
"""Env-var prefixes owned by a messaging platform card."""
|
||||
aliases: dict[str, tuple[str, ...]] = {
|
||||
"email": ("EMAIL_",),
|
||||
"homeassistant": ("HASS_",),
|
||||
"qqbot": ("QQ_", "QQBOT_"),
|
||||
"sms": ("TWILIO_",),
|
||||
"wecom": ("WECOM_BOT_", "WECOM_SECRET"),
|
||||
"wecom_callback": ("WECOM_CALLBACK_",),
|
||||
}
|
||||
if platform_id in aliases:
|
||||
return aliases[platform_id]
|
||||
return (platform_id.upper().replace("-", "_") + "_",)
|
||||
|
||||
|
||||
def _discover_platform_env_vars(platform_id: str) -> tuple[str, ...]:
|
||||
"""All messaging-category env vars for a platform (override + plugin + prefix)."""
|
||||
prefixes = _platform_env_prefixes(platform_id)
|
||||
keys: list[str] = []
|
||||
for name, info in OPTIONAL_ENV_VARS.items():
|
||||
if info.get("category") != "messaging":
|
||||
continue
|
||||
if name in _MESSAGING_KEYS_PAGE_KEYS:
|
||||
continue
|
||||
if not any(name.startswith(prefix) for prefix in prefixes):
|
||||
continue
|
||||
keys.append(name)
|
||||
return tuple(sorted(set(keys)))
|
||||
|
||||
|
||||
def _merge_platform_env_vars(
|
||||
platform_id: str,
|
||||
override: dict[str, Any],
|
||||
plugin_entry: Any | None,
|
||||
) -> tuple[str, ...]:
|
||||
"""Canonical env-var list for a messaging platform card."""
|
||||
discovered = _discover_platform_env_vars(platform_id)
|
||||
if "env_vars" in override:
|
||||
return tuple(dict.fromkeys((*override["env_vars"], *discovered)))
|
||||
if plugin_entry is not None and plugin_entry.required_env:
|
||||
return tuple(dict.fromkeys((*tuple(plugin_entry.required_env), *discovered)))
|
||||
return discovered
|
||||
|
||||
|
||||
def _build_catalog_entry(
|
||||
platform_id: str, plugin_entry: Any | None = None
|
||||
) -> dict[str, Any]:
|
||||
override = _PLATFORM_OVERRIDES.get(platform_id, {})
|
||||
|
||||
if "env_vars" in override:
|
||||
env_vars: tuple[str, ...] = tuple(override["env_vars"])
|
||||
elif plugin_entry is not None and plugin_entry.required_env:
|
||||
env_vars = tuple(plugin_entry.required_env)
|
||||
else:
|
||||
prefix = platform_id.upper() + "_"
|
||||
env_vars = tuple(k for k in OPTIONAL_ENV_VARS if k.startswith(prefix))
|
||||
env_vars = _merge_platform_env_vars(platform_id, override, plugin_entry)
|
||||
|
||||
if "required_env" in override:
|
||||
required_env = tuple(override["required_env"])
|
||||
@ -6663,6 +6711,7 @@ async def get_toolsets():
|
||||
_get_effective_configurable_toolsets,
|
||||
_get_platform_tools,
|
||||
_toolset_has_keys,
|
||||
gui_toolset_label,
|
||||
)
|
||||
from toolsets import resolve_toolset
|
||||
|
||||
@ -6680,7 +6729,9 @@ async def get_toolsets():
|
||||
tools = []
|
||||
is_enabled = name in enabled_toolsets
|
||||
result.append({
|
||||
"name": name, "label": label, "description": desc,
|
||||
"name": name,
|
||||
"label": gui_toolset_label(label),
|
||||
"description": desc,
|
||||
"enabled": is_enabled,
|
||||
"available": is_enabled,
|
||||
"configured": _toolset_has_keys(name, config),
|
||||
|
||||
Reference in New Issue
Block a user