refactor: remove config TypedDicts and fix ImportError propagation in clipboard

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.
This commit is contained in:
alt-glitch 2026-04-23 16:53:25 +05:30
parent 98eb32f39a
commit 415043315f
4 changed files with 9 additions and 386 deletions

View file

@ -387,6 +387,8 @@ def _wayland_save(dest: Path) -> bool:
except FileNotFoundError: except FileNotFoundError:
logger.debug("wl-paste not installed — Wayland clipboard unavailable") logger.debug("wl-paste not installed — Wayland clipboard unavailable")
except ImportError:
raise
except Exception as e: except Exception as e:
logger.debug("wl-paste clipboard extraction failed: %s", e) logger.debug("wl-paste clipboard extraction failed: %s", e)
dest.unlink(missing_ok=True) dest.unlink(missing_ok=True)

View file

@ -23,7 +23,7 @@ import sys
import tempfile import tempfile
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path 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__) logger = logging.getLogger(__name__)
@ -343,358 +343,7 @@ def _ensure_hermes_home_managed(home: Path):
# Config loading/saving # Config loading/saving
# ============================================================================= # =============================================================================
class _AgentConfig(TypedDict): DEFAULT_CONFIG = {
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 = {
"model": "", "model": "",
"providers": {}, "providers": {},
"fallback_providers": [], "fallback_providers": [],
@ -1305,7 +954,7 @@ ENV_VARS_BY_VERSION: Dict[int, List[str]] = {
REQUIRED_ENV_VARS = {} REQUIRED_ENV_VARS = {}
# Optional environment variables that enhance functionality # Optional environment variables that enhance functionality
OPTIONAL_ENV_VARS: Dict[str, _EnvVarInfo] = { OPTIONAL_ENV_VARS = {
# ── Provider (handled in provider selection, not shown in checklists) ── # ── Provider (handled in provider selection, not shown in checklists) ──
"NOUS_BASE_URL": { "NOUS_BASE_URL": {
"description": "Nous Portal base URL override", "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): elif isinstance(default_value, dict) and isinstance(current.get(key), dict):
_check(default_value, current[key], full_key) _check(default_value, current[key], full_key)
_check(dict(DEFAULT_CONFIG), config) _check(DEFAULT_CONFIG, config)
return missing return missing
@ -3407,7 +3056,7 @@ def load_config() -> Dict[str, Any]:
ensure_hermes_home() ensure_hermes_home()
config_path = get_config_path() config_path = get_config_path()
config: Dict[str, Any] = copy.deepcopy(DEFAULT_CONFIG) config = copy.deepcopy(DEFAULT_CONFIG)
if config_path.exists(): if config_path.exists():
try: try:
@ -4083,7 +3732,7 @@ def edit_config():
# Ensure config exists # Ensure config exists
if not config_path.exists(): if not config_path.exists():
save_config(dict(DEFAULT_CONFIG)) save_config(DEFAULT_CONFIG)
print(f"Created {config_path}") print(f"Created {config_path}")
# Find editor # Find editor

View file

@ -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

2
uv.lock generated
View file

@ -9,7 +9,7 @@ resolution-markers = [
] ]
[options] [options]
exclude-newer = "2026-04-16T12:11:13.647742Z" exclude-newer = "2026-04-16T12:11:21.909555Z"
exclude-newer-span = "P7D" exclude-newer-span = "P7D"
[[package]] [[package]]