Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12525,6 +12525,13 @@ def cmd_dashboard(args):
# the missing-provider state if it matters.
print(f"⚠ Plugin discovery failed: {exc}", file=sys.stderr)

# Dashboard and serve host agent turns in-process. Register config-owned
# hooks here because the lean Desktop serve path bypasses the shared CLI
# startup preparation entirely.
_register_config_hooks(
accept_hooks=bool(getattr(args, "accept_hooks", False)),
)

# Desktop chat uses the dashboard's in-process /api/ws gateway, which builds
# agents via tui_gateway.server._make_agent. That path only snapshots the
# tool registry — it never starts MCP discovery (the stdio TUI does that in
Expand Down Expand Up @@ -12822,6 +12829,27 @@ def _should_background_mcp_startup(args) -> bool:
return args.command in {None, "chat", "rl"}


def _register_config_hooks(*, accept_hooks: bool = False) -> None:
"""Register config-owned shell hooks and outbound webhooks once."""
try:
from hermes_cli.config import load_config
from agent.shell_hooks import register_from_config

hooks_cfg = load_config()
register_from_config(hooks_cfg, accept_hooks=accept_hooks)

from agent.outbound_webhooks import (
register_from_config as register_outbound_webhooks,
)

register_outbound_webhooks(hooks_cfg)
except Exception:
logger.debug(
"shell-hook registration failed at CLI startup",
exc_info=True,
)


def _prepare_agent_startup(args) -> None:
"""Discover plugins/MCP/hooks for commands that can run an agent turn."""
# --yolo: chokepoint guarantee that HERMES_YOLO_MODE is set before ANY
Expand Down Expand Up @@ -12916,23 +12944,7 @@ def _prepare_agent_startup(args) -> None:
"MCP tool discovery failed at CLI startup",
exc_info=True,
)
try:
from hermes_cli.config import load_config
from agent.shell_hooks import register_from_config

_hooks_cfg = load_config()
register_from_config(_hooks_cfg, accept_hooks=_accept_hooks)

from agent.outbound_webhooks import (
register_from_config as register_outbound_webhooks,
)

register_outbound_webhooks(_hooks_cfg)
except Exception:
logger.debug(
"shell-hook registration failed at CLI startup",
exc_info=True,
)
_register_config_hooks(accept_hooks=_accept_hooks)


def _apply_safe_mode(args) -> None:
Expand Down
47 changes: 47 additions & 0 deletions tests/hermes_cli/test_fast_serve_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,50 @@ def test_fast_serve_launch_dispatches_only_unambiguous_serve(monkeypatch) -> Non
monkeypatch.setattr(config_mod, "get_container_exec_info", lambda: {"name": "managed"})
assert main_mod._try_fast_serve_launch() is False
assert len(captured) == 1


def test_fast_serve_launch_registers_config_hooks(monkeypatch) -> None:
import agent.outbound_webhooks as outbound_webhooks
import agent.shell_hooks as shell_hooks
import hermes_cli.mcp_startup as mcp_startup
import hermes_cli.plugins as plugins
import hermes_cli.profiles as profiles
import hermes_cli.resource_limits as resource_limits
import hermes_cli.web_server as web_server

config = {"hooks": {"pre_tool_call": [{"command": "guard"}]}}
registrations = []

monkeypatch.setenv("HERMES_SERVE_HEADLESS", "0")
monkeypatch.setattr(config_mod, "get_container_exec_info", lambda: None)
monkeypatch.setattr(config_mod, "require_parseable_user_config", lambda **_kwargs: None)
monkeypatch.setattr(config_mod, "load_config", lambda: config)
monkeypatch.setattr(config_mod, "apply_terminal_config_to_env", lambda: None)
monkeypatch.setattr(profiles, "get_active_profile_name", lambda: "default")
monkeypatch.setattr(resource_limits, "apply_nofile_soft_limit", lambda: None)
monkeypatch.setattr(main_mod, "_sync_bundled_skills_quietly", lambda: None)
monkeypatch.setattr(main_mod, "_maybe_setup_dashboard_auth_interactively", lambda _args: None)
monkeypatch.setattr(plugins, "discover_plugins", lambda: None)
monkeypatch.setattr(mcp_startup, "start_background_mcp_discovery", lambda **_kwargs: None)
monkeypatch.setattr(web_server, "start_server", lambda **_kwargs: None)
monkeypatch.setattr(
shell_hooks,
"register_from_config",
lambda cfg, **kwargs: registrations.append(("shell", cfg, kwargs)),
)
monkeypatch.setattr(
outbound_webhooks,
"register_from_config",
lambda cfg: registrations.append(("outbound", cfg, {})),
)
monkeypatch.setattr(
sys,
"argv",
["hermes", "serve", "--host", "127.0.0.1", "--port", "0"],
)

assert main_mod._try_fast_serve_launch() is True
assert registrations == [
("shell", config, {"accept_hooks": False}),
("outbound", config, {}),
]
Loading