From 415043315f2a7e66f2cc5f3f03f043237019b445 Mon Sep 17 00:00:00 2001 From: alt-glitch Date: Thu, 23 Apr 2026 16:53:25 +0530 Subject: [PATCH] refactor: remove config TypedDicts and fix ImportError propagation in clipboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove 44 TypedDict classes from config.py — they were already stale (11 missing keys) and load_config() still returns Dict[str, Any], so they provided zero type-checking value. Keep the int() coercions and Dict[str, Any] annotations which are real fixes. Fix _wayland_save() swallowing ImportError at DEBUG level by adding an explicit except ImportError: raise before the broad except Exception. --- hermes_cli/clipboard.py | 2 + hermes_cli/config.py | 363 +------------------------ tests/hermes_cli/test_config_shapes.py | 28 -- uv.lock | 2 +- 4 files changed, 9 insertions(+), 386 deletions(-) delete mode 100644 tests/hermes_cli/test_config_shapes.py diff --git a/hermes_cli/clipboard.py b/hermes_cli/clipboard.py index bc9d5b6ad..43c9f3224 100644 --- a/hermes_cli/clipboard.py +++ b/hermes_cli/clipboard.py @@ -387,6 +387,8 @@ def _wayland_save(dest: Path) -> bool: except FileNotFoundError: logger.debug("wl-paste not installed — Wayland clipboard unavailable") + except ImportError: + raise except Exception as e: logger.debug("wl-paste clipboard extraction failed: %s", e) dest.unlink(missing_ok=True) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 9743cd13e..ba99b5055 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -23,7 +23,7 @@ import sys import tempfile from dataclasses import dataclass from pathlib import Path -from typing import Dict, Any, Optional, List, Tuple, TypedDict, Union +from typing import Dict, Any, Optional, List, Tuple logger = logging.getLogger(__name__) @@ -343,358 +343,7 @@ def _ensure_hermes_home_managed(home: Path): # Config loading/saving # ============================================================================= -class _AgentConfig(TypedDict): - max_turns: int - gateway_timeout: int - restart_drain_timeout: int - service_tier: str - tool_use_enforcement: str - gateway_timeout_warning: int - gateway_notify_interval: int - -class _TerminalConfig(TypedDict): - backend: str - modal_mode: str - cwd: str - timeout: int - env_passthrough: List[str] - docker_image: str - docker_forward_env: List[str] - docker_env: Dict[str, str] - singularity_image: str - modal_image: str - daytona_image: str - container_cpu: int - container_memory: int - container_disk: int - container_persistent: bool - docker_volumes: List[str] - docker_mount_cwd_to_workspace: bool - persistent_shell: bool - - -class _CamofoxConfig(TypedDict, total=False): - managed_persistence: bool - - -class _BrowserConfig(TypedDict): - inactivity_timeout: int - command_timeout: int - record_sessions: bool - allow_private_urls: bool - cdp_url: str - camofox: _CamofoxConfig - - -class _CheckpointsConfig(TypedDict): - enabled: bool - max_snapshots: int - - -class _CompressionConfig(TypedDict): - enabled: bool - threshold: float - target_ratio: float - protect_last_n: int - - -class _BedrockDiscoveryConfig(TypedDict): - enabled: bool - provider_filter: List[str] - refresh_interval: int - - -class _BedrockGuardrailConfig(TypedDict): - guardrail_identifier: str - guardrail_version: str - stream_processing_mode: str - trace: str - - -class _BedrockConfig(TypedDict): - region: str - discovery: _BedrockDiscoveryConfig - guardrail: _BedrockGuardrailConfig - - -class _AuxiliaryTaskConfig(TypedDict, total=False): - provider: str - model: str - base_url: str - api_key: str - timeout: int - extra_body: Dict[str, Any] - max_concurrency: int - download_timeout: int - - -class _AuxiliaryConfig(TypedDict): - vision: _AuxiliaryTaskConfig - web_extract: _AuxiliaryTaskConfig - compression: _AuxiliaryTaskConfig - session_search: _AuxiliaryTaskConfig - skills_hub: _AuxiliaryTaskConfig - approval: _AuxiliaryTaskConfig - mcp: _AuxiliaryTaskConfig - flush_memories: _AuxiliaryTaskConfig - title_generation: _AuxiliaryTaskConfig - - -class _UserMessagePreviewConfig(TypedDict): - first_lines: int - last_lines: int - - -class _DisplayConfig(TypedDict): - compact: bool - personality: str - resume_display: str - busy_input_mode: str - bell_on_complete: bool - show_reasoning: bool - streaming: bool - final_response_markdown: str - inline_diffs: bool - show_cost: bool - skin: str - user_message_preview: _UserMessagePreviewConfig - interim_assistant_messages: bool - tool_progress_command: bool - tool_progress_overrides: Dict[str, Any] - tool_preview_length: int - platforms: Dict[str, Any] - - -class _DashboardConfig(TypedDict): - theme: str - - -class _PrivacyConfig(TypedDict): - redact_pii: bool - - -class _EdgeTtsConfig(TypedDict): - voice: str - - -class _ElevenlabsTtsConfig(TypedDict): - voice_id: str - model_id: str - - -class _OpenaiTtsConfig(TypedDict): - model: str - voice: str - - -class _XaiTtsConfig(TypedDict): - voice_id: str - language: str - sample_rate: int - bit_rate: int - - -class _MistralTtsConfig(TypedDict): - model: str - voice_id: str - - -class _NeuttsConfig(TypedDict): - ref_audio: str - ref_text: str - model: str - device: str - - -class _TtsConfig(TypedDict): - provider: str - edge: _EdgeTtsConfig - elevenlabs: _ElevenlabsTtsConfig - openai: _OpenaiTtsConfig - xai: _XaiTtsConfig - mistral: _MistralTtsConfig - neutts: _NeuttsConfig - - -class _LocalSttConfig(TypedDict): - model: str - language: str - - -class _OpenaiSttConfig(TypedDict): - model: str - - -class _MistralSttConfig(TypedDict): - model: str - - -class _SttConfig(TypedDict): - enabled: bool - provider: str - local: _LocalSttConfig - openai: _OpenaiSttConfig - mistral: _MistralSttConfig - - -class _VoiceConfig(TypedDict): - record_key: str - max_recording_seconds: int - auto_tts: bool - silence_threshold: int - silence_duration: float - - -class _HumanDelayConfig(TypedDict): - mode: str - min_ms: int - max_ms: int - - -class _ContextConfig(TypedDict): - engine: str - - -class _MemoryConfig(TypedDict): - memory_enabled: bool - user_profile_enabled: bool - memory_char_limit: int - user_char_limit: int - provider: str - - -class _DelegationConfig(TypedDict): - model: str - provider: str - base_url: str - api_key: str - max_iterations: int - reasoning_effort: str - - -class _SkillsConfig(TypedDict): - external_dirs: List[str] - - -class _ChannelPromptsConfig(TypedDict): - channel_prompts: Dict[str, str] - - -class _DiscordConfig(TypedDict): - require_mention: bool - free_response_channels: str - allowed_channels: str - auto_thread: bool - reactions: bool - channel_prompts: Dict[str, str] - server_actions: str - - -class _ApprovalsConfig(TypedDict): - mode: str - timeout: int - cron_mode: str - - -class _WebsiteBlocklistConfig(TypedDict): - enabled: bool - domains: List[str] - shared_files: List[str] - - -class _SecurityConfig(TypedDict): - redact_secrets: bool - tirith_enabled: bool - tirith_path: str - tirith_timeout: int - tirith_fail_open: bool - website_blocklist: _WebsiteBlocklistConfig - - -class _CronConfig(TypedDict): - wrap_response: bool - max_parallel_jobs: Optional[int] - - -class _CodeExecutionConfig(TypedDict): - mode: str - - -class _LoggingConfig(TypedDict): - level: str - max_size_mb: int - backup_count: int - - -class _NetworkConfig(TypedDict): - force_ipv4: bool - - -class _DefaultConfig(TypedDict): - model: str - providers: Dict[str, Any] - fallback_providers: List[Any] - credential_pool_strategies: Dict[str, Any] - toolsets: List[str] - agent: _AgentConfig - terminal: _TerminalConfig - browser: _BrowserConfig - checkpoints: _CheckpointsConfig - file_read_max_chars: int - compression: _CompressionConfig - bedrock: _BedrockConfig - auxiliary: _AuxiliaryConfig - display: _DisplayConfig - dashboard: _DashboardConfig - privacy: _PrivacyConfig - tts: _TtsConfig - stt: _SttConfig - voice: _VoiceConfig - human_delay: _HumanDelayConfig - context: _ContextConfig - memory: _MemoryConfig - delegation: _DelegationConfig - prefill_messages_file: str - skills: _SkillsConfig - honcho: Dict[str, Any] - timezone: str - discord: _DiscordConfig - whatsapp: Dict[str, Any] - telegram: _ChannelPromptsConfig - slack: _ChannelPromptsConfig - mattermost: _ChannelPromptsConfig - approvals: _ApprovalsConfig - command_allowlist: List[str] - quick_commands: Dict[str, Any] - hooks: Dict[str, Any] - hooks_auto_accept: bool - personalities: Dict[str, Any] - security: _SecurityConfig - cron: _CronConfig - code_execution: _CodeExecutionConfig - logging: _LoggingConfig - network: _NetworkConfig - _config_version: int - - -class _EnvVarRequired(TypedDict): - description: str - prompt: str - category: str - - -class _EnvVarOptional(TypedDict, total=False): - url: Optional[str] - password: bool - tools: List[str] - advanced: bool - - -class _EnvVarInfo(_EnvVarRequired, _EnvVarOptional): - pass - - -DEFAULT_CONFIG: _DefaultConfig = { +DEFAULT_CONFIG = { "model": "", "providers": {}, "fallback_providers": [], @@ -1305,7 +954,7 @@ ENV_VARS_BY_VERSION: Dict[int, List[str]] = { REQUIRED_ENV_VARS = {} # Optional environment variables that enhance functionality -OPTIONAL_ENV_VARS: Dict[str, _EnvVarInfo] = { +OPTIONAL_ENV_VARS = { # ── Provider (handled in provider selection, not shown in checklists) ── "NOUS_BASE_URL": { "description": "Nous Portal base URL override", @@ -2269,7 +1918,7 @@ def get_missing_config_fields() -> List[Dict[str, Any]]: elif isinstance(default_value, dict) and isinstance(current.get(key), dict): _check(default_value, current[key], full_key) - _check(dict(DEFAULT_CONFIG), config) + _check(DEFAULT_CONFIG, config) return missing @@ -3407,7 +3056,7 @@ def load_config() -> Dict[str, Any]: ensure_hermes_home() config_path = get_config_path() - config: Dict[str, Any] = copy.deepcopy(DEFAULT_CONFIG) + config = copy.deepcopy(DEFAULT_CONFIG) if config_path.exists(): try: @@ -4083,7 +3732,7 @@ def edit_config(): # Ensure config exists if not config_path.exists(): - save_config(dict(DEFAULT_CONFIG)) + save_config(DEFAULT_CONFIG) print(f"Created {config_path}") # Find editor diff --git a/tests/hermes_cli/test_config_shapes.py b/tests/hermes_cli/test_config_shapes.py deleted file mode 100644 index 67f90d6a5..000000000 --- a/tests/hermes_cli/test_config_shapes.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Runtime smoke tests for `_CamofoxConfig` / `_BrowserConfig` TypedDict shapes.""" - -from __future__ import annotations - - -def test_camofox_config_is_partial_typeddict(): - from hermes_cli.config import _CamofoxConfig - - cfg_empty: _CamofoxConfig = {} - cfg_with_field: _CamofoxConfig = {"managed_persistence": True} - - assert cfg_empty == {} - assert cfg_with_field.get("managed_persistence") is True - - -def test_camofox_config_nested_in_browser_config(): - from hermes_cli.config import _BrowserConfig - - browser: _BrowserConfig = { - "inactivity_timeout": 60, - "command_timeout": 10, - "record_sessions": False, - "allow_private_urls": False, - "cdp_url": "http://localhost:9222", - "camofox": {"managed_persistence": False}, - } - - assert browser["camofox"].get("managed_persistence") is False diff --git a/uv.lock b/uv.lock index 956d0bd41..74f9d455d 100644 --- a/uv.lock +++ b/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-04-16T12:11:13.647742Z" +exclude-newer = "2026-04-16T12:11:21.909555Z" exclude-newer-span = "P7D" [[package]]