diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 955fd4ee2891..3982d0e86c94 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -111,10 +111,26 @@ def _exit_after_oneshot(rc: object) -> None: def _cleanup_oneshot_runtime() -> None: """Best-effort process-global cleanup before one-shot hard exit. - ``run_oneshot`` owns the agent-local cleanup. This mirrors the lightweight, - process-global pieces from the interactive CLI shutdown path that would - otherwise be skipped by ``os._exit``. + ``run_oneshot`` owns the agent-local cleanup (memory provider, agent.close, + session_db.close — all in ``_run_agent``'s finally block). This mirrors the + process-global pieces from ``cli.py:_run_cleanup()`` that would otherwise + be skipped by ``os._exit``. """ + try: + from tools.terminal_tool import cleanup_all_environments + cleanup_all_environments() + except Exception: + pass + try: + from tools.async_delegation import interrupt_all + interrupt_all(reason="oneshot shutdown") + except Exception: + pass + try: + from tools.browser_tool import _emergency_cleanup_all_sessions + _emergency_cleanup_all_sessions() + except Exception: + pass try: from tools.mcp_tool import shutdown_mcp_servers shutdown_mcp_servers() diff --git a/tests/hermes_cli/test_tui_resume_flow.py b/tests/hermes_cli/test_tui_resume_flow.py index 5ff0d473cceb..184aa7d6e20a 100644 --- a/tests/hermes_cli/test_tui_resume_flow.py +++ b/tests/hermes_cli/test_tui_resume_flow.py @@ -857,20 +857,41 @@ def test_run_and_exit_oneshot_cleans_global_runtime_before_hard_exit( ): events = [] + def _mod(name, **attrs): + fake = types.ModuleType(name) + for key, value in attrs.items(): + setattr(fake, key, value) + return fake + monkeypatch.setitem( sys.modules, "hermes_cli.oneshot", types.SimpleNamespace(run_oneshot=lambda *_args, **_kwargs: events.append("run") or 0), ) + monkeypatch.setitem( + sys.modules, + "tools.terminal_tool", + _mod("tools.terminal_tool", cleanup_all_environments=lambda: events.append("terminal")), + ) + monkeypatch.setitem( + sys.modules, + "tools.async_delegation", + _mod("tools.async_delegation", interrupt_all=lambda **kw: events.append("delegation")), + ) + monkeypatch.setitem( + sys.modules, + "tools.browser_tool", + _mod("tools.browser_tool", _emergency_cleanup_all_sessions=lambda: events.append("browser")), + ) monkeypatch.setitem( sys.modules, "tools.mcp_tool", - types.SimpleNamespace(shutdown_mcp_servers=lambda: events.append("mcp")), + _mod("tools.mcp_tool", shutdown_mcp_servers=lambda: events.append("mcp")), ) monkeypatch.setitem( sys.modules, "agent.auxiliary_client", - types.SimpleNamespace(shutdown_cached_clients=lambda: events.append("aux")), + _mod("agent.auxiliary_client", shutdown_cached_clients=lambda: events.append("aux")), ) monkeypatch.setattr( main_mod, "_exit_after_oneshot", lambda rc: events.append(f"exit:{rc}") @@ -878,7 +899,7 @@ def test_run_and_exit_oneshot_cleans_global_runtime_before_hard_exit( main_mod._run_and_exit_oneshot("hello") - assert events == ["run", "mcp", "aux", "exit:0"] + assert events == ["run", "terminal", "delegation", "browser", "mcp", "aux", "exit:0"] def test_run_and_exit_oneshot_still_exits_when_global_cleanup_raises( @@ -894,6 +915,21 @@ def test_run_and_exit_oneshot_still_exits_when_global_cleanup_raises( "hermes_cli.oneshot", types.SimpleNamespace(run_oneshot=lambda *_args, **_kwargs: 0), ) + monkeypatch.setitem( + sys.modules, + "tools.terminal_tool", + types.SimpleNamespace(cleanup_all_environments=lambda: events.append("terminal")), + ) + monkeypatch.setitem( + sys.modules, + "tools.async_delegation", + types.SimpleNamespace(interrupt_all=lambda **kw: events.append("delegation")), + ) + monkeypatch.setitem( + sys.modules, + "tools.browser_tool", + types.SimpleNamespace(_emergency_cleanup_all_sessions=lambda: events.append("browser")), + ) monkeypatch.setitem( sys.modules, "tools.mcp_tool", @@ -910,7 +946,7 @@ def test_run_and_exit_oneshot_still_exits_when_global_cleanup_raises( main_mod._run_and_exit_oneshot("hello") - assert events == ["aux", "exit:0"] + assert events == ["terminal", "delegation", "browser", "aux", "exit:0"] def test_run_and_exit_oneshot_hard_exits_when_cleanup_is_interrupted(