diff --git a/agent/prompt_builder.py b/agent/prompt_builder.py index 3e042f65dfa4..7444dab937a6 100644 --- a/agent/prompt_builder.py +++ b/agent/prompt_builder.py @@ -613,12 +613,16 @@ def build_skills_system_prompt( or get_session_env("HERMES_SESSION_PLATFORM") or "" ) + # Load disabled set before cache check so changes to skills.disabled + # invalidate the cache (fixes #12294). + disabled = get_disabled_skill_names() cache_key = ( str(skills_dir.resolve()), tuple(str(d) for d in external_dirs), tuple(sorted(str(t) for t in (available_tools or set()))), tuple(sorted(str(ts) for ts in (available_toolsets or set()))), _platform_hint, + tuple(sorted(disabled)), ) with _SKILLS_PROMPT_CACHE_LOCK: cached = _SKILLS_PROMPT_CACHE.get(cache_key) @@ -626,8 +630,6 @@ def build_skills_system_prompt( _SKILLS_PROMPT_CACHE.move_to_end(cache_key) return cached - disabled = get_disabled_skill_names() - # ── Layer 2: disk snapshot ──────────────────────────────────────── snapshot = _load_skills_snapshot(skills_dir) diff --git a/gateway/run.py b/gateway/run.py index 1525ad147766..3b3c8be5c1e1 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -4573,13 +4573,14 @@ async def _handle_reset_command(self, event: MessageEvent) -> str: self._session_model_overrides.pop(session_key, None) # Fire plugin on_session_finalize hook (session boundary) - try: - from hermes_cli.plugins import invoke_hook as _invoke_hook - _old_sid = old_entry.session_id if old_entry else None - _invoke_hook("on_session_finalize", session_id=_old_sid, - platform=source.platform.value if source.platform else "") - except Exception: - pass + if old_entry is not None: + try: + from hermes_cli.plugins import invoke_hook as _invoke_hook + _old_sid = old_entry.session_id if old_entry else None + _invoke_hook("on_session_finalize", session_id=_old_sid, + platform=source.platform.value if source.platform else "") + except Exception: + pass # Emit session:end hook (session is ending) await self.hooks.emit("session:end", { @@ -6170,13 +6171,20 @@ def run_sync(): except Exception: pass - # Send media files + # Send media files — route voice files through send_voice + # when available (fixes #12064). for media_path, _is_voice in (media_files or []): try: - await adapter.send_document( - chat_id=source.chat_id, - file_path=media_path, - ) + if _is_voice and hasattr(adapter, "send_voice"): + await adapter.send_voice( + chat_id=source.chat_id, + audio_path=media_path, + ) + else: + await adapter.send_document( + chat_id=source.chat_id, + file_path=media_path, + ) except Exception: pass else: @@ -10416,18 +10424,25 @@ def restart_signal_handler(): name="cron-ticker", ) cron_thread.start() - + # Wait for shutdown await runner.wait_for_shutdown() + _failure = False if runner.should_exit_with_failure: if runner.exit_reason: logger.error("Gateway exiting with failure: %s", runner.exit_reason) + _failure = True + + try: + pass + finally: + # Stop cron ticker cleanly (always, even on failure) + cron_stop.set() + cron_thread.join(timeout=5) + + if _failure: return False - - # Stop cron ticker cleanly - cron_stop.set() - cron_thread.join(timeout=5) # Close MCP server connections try: