Skip to content

feat(plugins): register_command(override=True) to shadow a built-in command - #50054

Draft
arminanton wants to merge 1 commit into
NousResearch:mainfrom
arminanton:feat/plugin-register-command-override
Draft

feat(plugins): register_command(override=True) to shadow a built-in command#50054
arminanton wants to merge 1 commit into
NousResearch:mainfrom
arminanton:feat/plugin-register-command-override

Conversation

@arminanton

Copy link
Copy Markdown
Contributor

Adds an override: bool = False parameter to register_command. By default a plugin command name conflicting with a built-in is rejected with a warning (unchanged); with override=True the plugin command shadows the built-in, mirroring the override semantics register_tool already has. Lets a plugin enhance/wrap a built-in command intentionally.

@alt-glitch alt-glitch added type/feature New feature or request comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have labels Jun 21, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for extending the plugin command surface. The collision is currently rejected on main (hermes_cli/plugins.py:560-569), but this implementation does not yet provide a safe, consistent override.

Problems

  • CLI reaches plugin handlers only in its post-built-in fallback (cli.py:8921-8985), and gateway does the same (gateway/run.py:10057-10072). TUI checks plugins first (tui_gateway/server.py:11892-11901), so override=True would behave differently by surface.
  • Current tool overrides require an explicit, fail-closed per-plugin operator opt-in (hermes_cli/plugins.py:409-470). The new command override has no equivalent authorization boundary.
  • The diff has no tests or docs update, while the current collision test is at tests/hermes_cli/test_plugins.py:1863-1872 and the documented API promises built-ins take precedence at website/docs/developer-guide/plugins/index.md:788-805.

Suggested changes

  • Centralize authorized override resolution across CLI, gateway, and TUI; add a fail-closed per-plugin gate and cross-surface regression tests; then update the documented API and precedence contract.

Automated hermes-sweeper review.

Comment thread hermes_cli/plugins.py
override: bool = False,
) -> None:
"""Register a slash command (e.g. ``/lcm``) available in CLI and gateway sessions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

override=True only changes registration here. CLI and gateway dispatch built-ins before their plugin fallback (cli.py:8921-8985, gateway/run.py:10057-10072), while TUI checks plugins first (tui_gateway/server.py:11892-11901). Please add a shared, authorized resolution path and cross-surface tests before exposing this option.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 14, 2026
@arminanton
arminanton force-pushed the feat/plugin-register-command-override branch from 352b131 to 393642c Compare August 22, 2026 21:39
@arminanton

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and made override behavior consistent + gated.

Consistent precedence across all three surfaces (sweeper): previously CLI/gateway resolved plugins only in the post-built-in fallback while TUI checked plugins first, so override=True behaved differently per surface. Added a shared get_plugin_command_override_handler(name) that returns a handler only for registrations flagged override_builtin=True, and each surface now short-circuits to it before built-in dispatch — CLI (cli.process_command, also covers the TUI slash-worker), gateway (gateway/run.py), and TUI (tui_gateway/methods_tools.py). Plain/unauthorized plugin commands still yield to built-ins everywhere.

Fail-closed operator opt-in (sweeper): command override is gated behind the same mechanism as tool override — a new commands.override capability (plugin_capabilities.py) with _command_override_allowed() mirroring _tool_override_allowed() (bundled = trusted; everyone else needs explicit consent/legacy key; any config-read failure = denied). register_command(override=True) without consent raises PluginCommandOverrideError and leaves the built-in intact.

Added a cross-surface precedence test + opt-in gate tests. test_plugins.py → 71 passed (172 across the plugin/command regression suites). Precedence contract documented in the plugins developer guide.

Add an `override: bool = False` parameter to `register_command`. By
default a plugin command that collides with a built-in is rejected with a
warning (unchanged); with override=True the plugin command shadows the
built-in, mirroring register_tool's override semantics.

Two problems the reviewer flagged are fixed:

1. Consistent precedence across all three surfaces. Previously CLI and
   gateway dispatched built-ins first (plugin only reached in the
   post-built-in fallback) while TUI checked plugins first, so
   override=True would have worked on TUI but been silently ignored where
   the built-in won. Now every surface (cli.process_command,
   gateway/run.py, tui_gateway/methods_tools.py slash.exec) short-circuits
   to the shared get_plugin_command_override_handler() BEFORE built-in
   dispatch, so an authorized override wins identically everywhere.

2. Fail-closed operator opt-in. Command override is gated by the SAME
   mechanism as tool override: a new commands.override capability
   (legacy key plugins.entries.<id>.allow_command_override). Without
   operator consent register_command(override=True) raises
   PluginCommandOverrideError and the built-in is untouched. Bundled
   plugins are trusted. Only authorized overrides get override_builtin=True
   in the registration, and the resolver returns handlers only for those.

Adds cross-surface + gate tests, updates the documented precedence
contract.
@arminanton
arminanton force-pushed the feat/plugin-register-command-override branch from 393642c to 7e72003 Compare August 23, 2026 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants