diff --git a/cli.py b/cli.py index 37a4942937..0ff5ad5cd0 100755 --- a/cli.py +++ b/cli.py @@ -823,8 +823,8 @@ class HermesCLI: try: from hermes_state import SessionDB self._session_db = SessionDB() - except Exception: - pass # SQLite session store is optional + except Exception as e: + logger.debug("SQLite session store not available: %s", e) try: self.agent = AIAgent( @@ -2130,8 +2130,8 @@ class HermesCLI: if hasattr(self, '_session_db') and self._session_db and self.agent: try: self._session_db.end_session(self.agent.session_id, "cli_close") - except Exception: - pass + except Exception as e: + logger.debug("Could not close session in DB: %s", e) _run_cleanup() print("\nGoodbye! āš•") diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 5dac3599f3..f1c19bf17b 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -8,9 +8,12 @@ Uses discord.py library for: """ import asyncio +import logging import os from typing import Dict, List, Optional, Any +logger = logging.getLogger(__name__) + try: import discord from discord import Message as DiscordMessage, Intents @@ -177,8 +180,8 @@ class DiscordAdapter(BasePlatformAdapter): try: ref_msg = await channel.fetch_message(int(reply_to)) reference = ref_msg - except Exception: - pass # Ignore if we can't find the referenced message + except Exception as e: + logger.debug("Could not fetch reply-to message: %s", e) for i, chunk in enumerate(chunks): msg = await channel.send( @@ -363,8 +366,8 @@ class DiscordAdapter(BasePlatformAdapter): # Send a followup to close the interaction if needed try: await interaction.followup.send("Processing complete~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) @tree.command(name="new", description="Start a new conversation") async def slash_new(interaction: discord.Interaction): @@ -373,8 +376,8 @@ class DiscordAdapter(BasePlatformAdapter): await self.handle_message(event) try: await interaction.followup.send("New conversation started~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) @tree.command(name="reset", description="Reset your Hermes session") async def slash_reset(interaction: discord.Interaction): @@ -383,8 +386,8 @@ class DiscordAdapter(BasePlatformAdapter): await self.handle_message(event) try: await interaction.followup.send("Session reset~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) @tree.command(name="model", description="Show or change the model") @discord.app_commands.describe(name="Model name (e.g. anthropic/claude-sonnet-4). Leave empty to see current.") @@ -394,8 +397,8 @@ class DiscordAdapter(BasePlatformAdapter): await self.handle_message(event) try: await interaction.followup.send("Done~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) @tree.command(name="personality", description="Set a personality") @discord.app_commands.describe(name="Personality name. Leave empty to list available.") @@ -405,8 +408,8 @@ class DiscordAdapter(BasePlatformAdapter): await self.handle_message(event) try: await interaction.followup.send("Done~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) @tree.command(name="retry", description="Retry your last message") async def slash_retry(interaction: discord.Interaction): @@ -415,8 +418,8 @@ class DiscordAdapter(BasePlatformAdapter): await self.handle_message(event) try: await interaction.followup.send("Retrying~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) @tree.command(name="undo", description="Remove the last exchange") async def slash_undo(interaction: discord.Interaction): @@ -425,8 +428,8 @@ class DiscordAdapter(BasePlatformAdapter): await self.handle_message(event) try: await interaction.followup.send("Done~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) @tree.command(name="status", description="Show Hermes session status") async def slash_status(interaction: discord.Interaction): @@ -435,8 +438,8 @@ class DiscordAdapter(BasePlatformAdapter): await self.handle_message(event) try: await interaction.followup.send("Status sent~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) @tree.command(name="stop", description="Stop the running Hermes agent") async def slash_stop(interaction: discord.Interaction): @@ -445,8 +448,8 @@ class DiscordAdapter(BasePlatformAdapter): await self.handle_message(event) try: await interaction.followup.send("Stop requested~", ephemeral=True) - except Exception: - pass + except Exception as e: + logger.debug("Discord followup failed: %s", e) def _build_slash_event(self, interaction: discord.Interaction, text: str) -> MessageEvent: """Build a MessageEvent from a Discord slash command interaction.""" diff --git a/gateway/platforms/whatsapp.py b/gateway/platforms/whatsapp.py index ac0015ed04..71f0c33c56 100644 --- a/gateway/platforms/whatsapp.py +++ b/gateway/platforms/whatsapp.py @@ -17,10 +17,13 @@ with different backends via a bridge pattern. import asyncio import json +import logging import subprocess from pathlib import Path from typing import Dict, List, Optional, Any +logger = logging.getLogger(__name__) + import sys sys.path.insert(0, str(__file__).rsplit("/", 3)[0]) @@ -246,8 +249,8 @@ class WhatsAppAdapter(BasePlatformAdapter): "type": "group" if data.get("isGroup") else "dm", "participants": data.get("participants", []), } - except Exception: - pass + except Exception as e: + logger.debug("Could not get WhatsApp chat info for %s: %s", chat_id, e) return {"name": chat_id, "type": "dm"} diff --git a/gateway/run.py b/gateway/run.py index dcff9f2ec3..509cc0f2b4 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -479,8 +479,8 @@ class GatewayRunner: self._pending_approvals[session_key] = _last_pending_approval.copy() # Clear the global so it doesn't leak to other sessions _last_pending_approval.clear() - except Exception: - pass + except Exception as e: + logger.debug("Failed to check pending approvals: %s", e) # Save the full conversation to the transcript, including tool calls. # This preserves the complete agent loop (tool_calls, tool results, @@ -973,8 +973,8 @@ class GatewayRunner: with open(config_path, 'r') as f: user_config = yaml.safe_load(f) or {} platform_toolsets_config = user_config.get("platform_toolsets", {}) - except Exception: - pass + except Exception as e: + logger.debug("Could not load platform_toolsets config: %s", e) # Map platform enum to config key platform_config_key = { diff --git a/gateway/session.py b/gateway/session.py index d102f8b5f5..c66c638b46 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -8,6 +8,7 @@ Handles: - Dynamic system prompt injection (agent knows its context) """ +import logging import os import json import uuid @@ -16,6 +17,8 @@ from datetime import datetime, timedelta from dataclasses import dataclass, field from typing import Dict, List, Optional, Any +logger = logging.getLogger(__name__) + from .config import ( Platform, GatewayConfig, @@ -388,8 +391,8 @@ class SessionStore: if self._db: try: self._db.end_session(entry.session_id, "session_reset") - except Exception: - pass + except Exception as e: + logger.debug("Session DB operation failed: %s", e) # Create new session session_id = f"{now.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}" @@ -443,8 +446,8 @@ class SessionStore: self._db.update_token_counts( entry.session_id, input_tokens, output_tokens ) - except Exception: - pass + except Exception as e: + logger.debug("Session DB operation failed: %s", e) def reset_session(self, session_key: str) -> Optional[SessionEntry]: """Force reset a session, creating a new session ID.""" @@ -459,8 +462,8 @@ class SessionStore: if self._db: try: self._db.end_session(old_entry.session_id, "session_reset") - except Exception: - pass + except Exception as e: + logger.debug("Session DB operation failed: %s", e) now = datetime.now() session_id = f"{now.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}" @@ -487,8 +490,8 @@ class SessionStore: source=old_entry.platform.value if old_entry.platform else "unknown", user_id=old_entry.origin.user_id if old_entry.origin else None, ) - except Exception: - pass + except Exception as e: + logger.debug("Session DB operation failed: %s", e) return new_entry @@ -523,8 +526,8 @@ class SessionStore: tool_calls=message.get("tool_calls"), tool_call_id=message.get("tool_call_id"), ) - except Exception: - pass + except Exception as e: + logger.debug("Session DB operation failed: %s", e) # Also write legacy JSONL (keeps existing tooling working during transition) transcript_path = self.get_transcript_path(session_id) @@ -539,8 +542,8 @@ class SessionStore: messages = self._db.get_messages_as_conversation(session_id) if messages: return messages - except Exception: - pass + except Exception as e: + logger.debug("Could not load messages from DB: %s", e) # Fall back to legacy JSONL transcript_path = self.get_transcript_path(session_id) diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 861b92018c..dcdd6fcb65 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -16,6 +16,7 @@ Architecture: from __future__ import annotations import json +import logging import os import stat import time @@ -32,6 +33,8 @@ import yaml from hermes_cli.config import get_hermes_home, get_config_path from hermes_constants import OPENROUTER_BASE_URL +logger = logging.getLogger(__name__) + try: import fcntl except Exception: @@ -314,8 +317,8 @@ def resolve_provider( state = _load_provider_state(auth_store, active) if state and (state.get("access_token") or state.get("refresh_token")): return active - except Exception: - pass + except Exception as e: + logger.debug("Could not detect active auth provider: %s", e) if os.getenv("OPENAI_API_KEY") or os.getenv("OPENROUTER_API_KEY"): return "openrouter" @@ -578,8 +581,8 @@ def fetch_nous_models( try: err = response.json() description = str(err.get("error_description") or err.get("error") or description) - except Exception: - pass + except Exception as e: + logger.debug("Could not parse error response JSON: %s", e) raise AuthError(description, provider="nous", code="models_fetch_failed") payload = response.json() diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 09d56bc63f..c6efb7065c 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -39,9 +39,13 @@ env_path = PROJECT_ROOT / '.env' if env_path.exists(): load_dotenv(dotenv_path=env_path) +import logging + from hermes_cli import __version__ from hermes_constants import OPENROUTER_BASE_URL +logger = logging.getLogger(__name__) + def cmd_chat(args): """Run interactive chat CLI.""" @@ -512,8 +516,8 @@ def cmd_update(args): print(f" + {len(result['copied'])} new skill(s): {', '.join(result['copied'])}") else: print(" āœ“ Skills are up to date") - except Exception: - pass + except Exception as e: + logger.debug("Skills sync during update failed: %s", e) # Check for config migrations print() diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 7ade18f8ca..1dd670858d 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -12,11 +12,14 @@ Guides users through: Config files are stored in ~/.hermes/ for easy access. """ +import logging import os import sys from pathlib import Path from typing import Optional, Dict, Any +logger = logging.getLogger(__name__) + PROJECT_ROOT = Path(__file__).parent.parent.resolve() # Import config helpers @@ -488,8 +491,8 @@ def run_setup_wizard(args): inference_base_url=creds.get("base_url", ""), api_key=creds.get("api_key", ""), ) - except Exception: - pass + except Exception as e: + logger.debug("Could not fetch Nous models after login: %s", e) except SystemExit: print_warning("Nous Portal login was cancelled or failed.") diff --git a/run_agent.py b/run_agent.py index e922e4fa57..8c56512b4c 100644 --- a/run_agent.py +++ b/run_agent.py @@ -636,8 +636,8 @@ def build_skills_system_prompt() -> str: match = re.search(r"^---\s*\n.*?description:\s*(.+?)\s*\n.*?^---", content, re.MULTILINE | re.DOTALL) if match: category_descriptions[category] = match.group(1).strip() - except Exception: - pass + except Exception as e: + logger.debug("Could not read skill description %s: %s", desc_file, e) index_lines = [] for category in sorted(skills_by_category.keys()): @@ -748,8 +748,8 @@ def build_context_files_prompt(cwd: str = None) -> str: if content: rel_path = agents_path.relative_to(cwd_path) total_agents_content += f"## {rel_path}\n\n{content}\n\n" - except Exception: - pass + except Exception as e: + logger.debug("Could not read %s: %s", agents_path, e) if total_agents_content: total_agents_content = _truncate_content(total_agents_content, "AGENTS.md") @@ -765,8 +765,8 @@ def build_context_files_prompt(cwd: str = None) -> str: content = cursorrules_file.read_text(encoding="utf-8").strip() if content: cursorrules_content += f"## .cursorrules\n\n{content}\n\n" - except Exception: - pass + except Exception as e: + logger.debug("Could not read .cursorrules: %s", e) # Check for .cursor/rules/*.mdc files cursor_rules_dir = cwd_path / ".cursor" / "rules" @@ -777,8 +777,8 @@ def build_context_files_prompt(cwd: str = None) -> str: content = mdc_file.read_text(encoding="utf-8").strip() if content: cursorrules_content += f"## .cursor/rules/{mdc_file.name}\n\n{content}\n\n" - except Exception: - pass + except Exception as e: + logger.debug("Could not read %s: %s", mdc_file, e) if cursorrules_content: cursorrules_content = _truncate_content(cursorrules_content, ".cursorrules") @@ -807,8 +807,8 @@ def build_context_files_prompt(cwd: str = None) -> str: content = _truncate_content(content, "SOUL.md") soul_content = f"## SOUL.md\n\nIf SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.\n\n{content}" sections.append(soul_content) - except Exception: - pass + except Exception as e: + logger.debug("Could not read SOUL.md from %s: %s", soul_path, e) # ----- Assemble ----- if not sections: @@ -1320,8 +1320,8 @@ class AIAgent: }, user_id=None, ) - except Exception: - pass + except Exception as e: + logger.debug("Session DB create_session failed: %s", e) # In-memory todo list for task planning (one per agent/session) from tools.todo_tool import TodoStore @@ -1730,8 +1730,8 @@ class AIAgent: tool_call_id=msg.get("tool_call_id"), finish_reason=msg.get("finish_reason"), ) - except Exception: - pass + except Exception as e: + logger.debug("Session DB append_message failed: %s", e) def _get_messages_up_to_last_assistant(self, messages: List[Dict]) -> List[Dict]: """ @@ -2048,8 +2048,8 @@ class AIAgent: api_key = None try: api_key = getattr(self.client, "api_key", None) - except Exception: - pass + except Exception as e: + logger.debug("Could not extract API key for debug dump: %s", e) dump_payload: Dict[str, Any] = { "timestamp": datetime.now().isoformat(), @@ -2085,8 +2085,8 @@ class AIAgent: try: error_info["response_status"] = getattr(response_obj, "status_code", None) error_info["response_text"] = response_obj.text - except Exception: - pass + except Exception as e: + logger.debug("Could not extract error response details: %s", e) dump_payload["error"] = error_info @@ -2174,8 +2174,8 @@ class AIAgent: for child in self._active_children: try: child.interrupt(message) - except Exception: - pass + except Exception as e: + logger.debug("Failed to propagate interrupt to child agent: %s", e) if not self.quiet_mode: print(f"\n⚔ Interrupt requested" + (f": '{message[:40]}...'" if message and len(message) > 40 else f": '{message}'" if message else "")) @@ -2346,8 +2346,8 @@ class AIAgent: if self._session_db: try: self._session_db.update_system_prompt(self.session_id, self._cached_system_prompt) - except Exception: - pass + except Exception as e: + logger.debug("Session DB update_system_prompt failed: %s", e) active_system_prompt = self._cached_system_prompt @@ -2355,8 +2355,8 @@ class AIAgent: if self._session_db: try: self._session_db.append_message(self.session_id, "user", user_message) - except Exception: - pass + except Exception as e: + logger.debug("Session DB append_message failed: %s", e) # Main conversation loop api_call_count = 0 @@ -2743,8 +2743,8 @@ class AIAgent: parent_session_id=old_session_id, ) self._session_db.update_system_prompt(self.session_id, active_system_prompt) - except Exception: - pass + except Exception as e: + logger.debug("Session DB compression split failed: %s", e) print(f"{self.log_prefix} šŸ—œļø Compressed {original_len} → {len(messages)} messages, retrying...") continue # Retry with compressed messages else: @@ -3175,8 +3175,8 @@ class AIAgent: parent_session_id=old_session_id, ) self._session_db.update_system_prompt(self.session_id, active_system_prompt) - except Exception: - pass + except Exception as e: + logger.debug("Session DB compression split failed: %s", e) # Save session log incrementally (so progress is visible even if interrupted) self._session_messages = messages diff --git a/tools/code_execution_tool.py b/tools/code_execution_tool.py index 87e9134a05..4036e34ace 100644 --- a/tools/code_execution_tool.py +++ b/tools/code_execution_tool.py @@ -31,6 +31,8 @@ import uuid from typing import Any, Dict, List, Optional # Availability gate: UDS requires a POSIX OS +logger = logging.getLogger(__name__) + SANDBOX_AVAILABLE = sys.platform != "win32" # The 7 tools allowed inside the sandbox. The intersection of this list @@ -488,8 +490,8 @@ def execute_code( try: import shutil shutil.rmtree(tmpdir, ignore_errors=True) - except Exception: - pass + except Exception as e: + logger.debug("Could not clean temp dir: %s", e) try: os.unlink(sock_path) except OSError: @@ -503,8 +505,8 @@ def _kill_process_group(proc, escalate: bool = False): except (ProcessLookupError, PermissionError): try: proc.kill() - except Exception: - pass + except Exception as e: + logger.debug("Could not kill process: %s", e) if escalate: # Give the process 5s to exit after SIGTERM, then SIGKILL @@ -516,8 +518,8 @@ def _kill_process_group(proc, escalate: bool = False): except (ProcessLookupError, PermissionError): try: proc.kill() - except Exception: - pass + except Exception as e: + logger.debug("Could not kill process: %s", e) def _load_config() -> dict: diff --git a/tools/process_registry.py b/tools/process_registry.py index f8942fe39a..c8db412d1b 100644 --- a/tools/process_registry.py +++ b/tools/process_registry.py @@ -277,14 +277,14 @@ class ProcessRegistry: session.output_buffer += chunk if len(session.output_buffer) > session.max_output_chars: session.output_buffer = session.output_buffer[-session.max_output_chars:] - except Exception: - pass + except Exception as e: + logger.debug("Process stdout reader ended: %s", e) # Process exited try: session.process.wait(timeout=5) - except Exception: - pass + except Exception as e: + logger.debug("Process wait timed out or failed: %s", e) session.exited = True session.exit_code = session.process.returncode self._move_to_finished(session) @@ -351,14 +351,14 @@ class ProcessRegistry: break except Exception: break - except Exception: - pass + except Exception as e: + logger.debug("PTY stdout reader ended: %s", e) # Process exited try: pty.wait() - except Exception: - pass + except Exception as e: + logger.debug("PTY wait timed out or failed: %s", e) session.exited = True session.exit_code = pty.exitstatus if hasattr(pty, 'exitstatus') else -1 self._move_to_finished(session) @@ -719,8 +719,8 @@ class ProcessRegistry: # Clear the checkpoint (will be rewritten as processes finish) try: CHECKPOINT_PATH.write_text("[]", encoding="utf-8") - except Exception: - pass + except Exception as e: + logger.debug("Could not write checkpoint file: %s", e) return recovered diff --git a/tools/skills_hub.py b/tools/skills_hub.py index fc7544ed9d..5eb78205e5 100644 --- a/tools/skills_hub.py +++ b/tools/skills_hub.py @@ -154,8 +154,8 @@ class GitHubAuth: ) if result.returncode == 0 and result.stdout.strip(): return result.stdout.strip() - except (FileNotFoundError, subprocess.TimeoutExpired): - pass + except (FileNotFoundError, subprocess.TimeoutExpired) as e: + logger.debug("gh CLI token lookup failed: %s", e) return None def _try_github_app(self) -> Optional[str]: @@ -438,8 +438,8 @@ class GitHubSource(SkillSource): ) if resp.status_code == 200: return resp.text - except httpx.HTTPError: - pass + except httpx.HTTPError as e: + logger.debug("GitHub contents API fetch failed: %s", e) return None def _read_cache(self, key: str) -> Optional[list]: @@ -461,8 +461,8 @@ class GitHubSource(SkillSource): cache_file = INDEX_CACHE_DIR / f"{key}.json" try: cache_file.write_text(json.dumps(data, ensure_ascii=False)) - except OSError: - pass + except OSError as e: + logger.debug("Could not write cache: %s", e) @staticmethod def _meta_to_dict(meta: SkillMeta) -> dict: @@ -826,8 +826,8 @@ class LobeHubSource(SkillSource): resp = httpx.get(url, timeout=15) if resp.status_code == 200: return resp.json() - except (httpx.HTTPError, json.JSONDecodeError): - pass + except (httpx.HTTPError, json.JSONDecodeError) as e: + logger.debug("LobeHub agent fetch failed: %s", e) return None @staticmethod @@ -890,8 +890,8 @@ def _write_index_cache(key: str, data: Any) -> None: cache_file = INDEX_CACHE_DIR / f"{key}.json" try: cache_file.write_text(json.dumps(data, ensure_ascii=False, default=str)) - except OSError: - pass + except OSError as e: + logger.debug("Could not write cache: %s", e) def _skill_meta_to_dict(meta: SkillMeta) -> dict: @@ -1037,8 +1037,8 @@ def append_audit_log(action: str, skill_name: str, source: str, try: with open(AUDIT_LOG, "a") as f: f.write(line) - except OSError: - pass + except OSError as e: + logger.debug("Could not write audit log: %s", e) # --------------------------------------------------------------------------- diff --git a/tools/skills_sync.py b/tools/skills_sync.py index 7e5c26e996..8aaa6f1ef6 100644 --- a/tools/skills_sync.py +++ b/tools/skills_sync.py @@ -13,11 +13,14 @@ newline-delimited list of skill names that have been offered to the user. """ import json +import logging import os import shutil from pathlib import Path from typing import List, Tuple +logger = logging.getLogger(__name__) + HERMES_HOME = Path(os.getenv("HERMES_HOME", Path.home() / ".hermes")) SKILLS_DIR = HERMES_HOME / "skills" @@ -131,8 +134,8 @@ def sync_skills(quiet: bool = False) -> dict: try: dest_desc.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(desc_md, dest_desc) - except (OSError, IOError): - pass + except (OSError, IOError) as e: + logger.debug("Could not copy %s: %s", desc_md, e) _write_manifest(manifest) diff --git a/tools/tts_tool.py b/tools/tts_tool.py index 7ca68ee5b1..1610b86ec3 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -122,8 +122,8 @@ def _convert_to_opus(mp3_path: str) -> Optional[str]: ) if os.path.exists(ogg_path) and os.path.getsize(ogg_path) > 0: return ogg_path - except Exception: - pass + except Exception as e: + logger.warning("ffmpeg OGG conversion failed: %s", e) return None