mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat: add slash command for busy input mode
This commit is contained in:
parent
2de8a7a229
commit
1dcf79a864
4 changed files with 137 additions and 0 deletions
32
cli.py
32
cli.py
|
|
@ -6165,6 +6165,8 @@ class HermesCLI:
|
||||||
self._handle_skin_command(cmd_original)
|
self._handle_skin_command(cmd_original)
|
||||||
elif canonical == "voice":
|
elif canonical == "voice":
|
||||||
self._handle_voice_command(cmd_original)
|
self._handle_voice_command(cmd_original)
|
||||||
|
elif canonical == "busy":
|
||||||
|
self._handle_busy_command(cmd_original)
|
||||||
else:
|
else:
|
||||||
# Check for user-defined quick commands (bypass agent loop, no LLM call)
|
# Check for user-defined quick commands (bypass agent loop, no LLM call)
|
||||||
base_cmd = cmd_lower.split()[0]
|
base_cmd = cmd_lower.split()[0]
|
||||||
|
|
@ -6901,6 +6903,36 @@ class HermesCLI:
|
||||||
else:
|
else:
|
||||||
_cprint(f" {_ACCENT}✓ Reasoning effort set to '{arg}' (session only){_RST}")
|
_cprint(f" {_ACCENT}✓ Reasoning effort set to '{arg}' (session only){_RST}")
|
||||||
|
|
||||||
|
def _handle_busy_command(self, cmd: str):
|
||||||
|
"""Handle /busy — control what Enter does while Hermes is working.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
/busy Show current busy input mode
|
||||||
|
/busy status Show current busy input mode
|
||||||
|
/busy queue Queue input for the next turn instead of interrupting
|
||||||
|
/busy interrupt Interrupt the current run on Enter (default)
|
||||||
|
"""
|
||||||
|
parts = cmd.strip().split(maxsplit=1)
|
||||||
|
if len(parts) < 2 or parts[1].strip().lower() == "status":
|
||||||
|
_cprint(f" {_ACCENT}Busy input mode: {self.busy_input_mode}{_RST}")
|
||||||
|
_cprint(f" {_DIM}Enter while busy: {'queues for next turn' if self.busy_input_mode == 'queue' else 'interrupts current run'}{_RST}")
|
||||||
|
_cprint(f" {_DIM}Usage: /busy [queue|interrupt|status]{_RST}")
|
||||||
|
return
|
||||||
|
|
||||||
|
arg = parts[1].strip().lower()
|
||||||
|
if arg not in {"queue", "interrupt"}:
|
||||||
|
_cprint(f" {_DIM}(._.) Unknown argument: {arg}{_RST}")
|
||||||
|
_cprint(f" {_DIM}Usage: /busy [queue|interrupt|status]{_RST}")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.busy_input_mode = arg
|
||||||
|
if save_config_value("display.busy_input_mode", arg):
|
||||||
|
behavior = "Enter will queue follow-up input while Hermes is busy." if arg == "queue" else "Enter will interrupt the current run while Hermes is busy."
|
||||||
|
_cprint(f" {_ACCENT}✓ Busy input mode set to '{arg}' (saved to config){_RST}")
|
||||||
|
_cprint(f" {_DIM}{behavior}{_RST}")
|
||||||
|
else:
|
||||||
|
_cprint(f" {_ACCENT}✓ Busy input mode set to '{arg}' (session only){_RST}")
|
||||||
|
|
||||||
def _handle_fast_command(self, cmd: str):
|
def _handle_fast_command(self, cmd: str):
|
||||||
"""Handle /fast — toggle fast mode (OpenAI Priority Processing / Anthropic Fast Mode)."""
|
"""Handle /fast — toggle fast mode (OpenAI Priority Processing / Anthropic Fast Mode)."""
|
||||||
if not self._fast_command_available():
|
if not self._fast_command_available():
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,9 @@ COMMAND_REGISTRY: list[CommandDef] = [
|
||||||
cli_only=True, args_hint="[name]"),
|
cli_only=True, args_hint="[name]"),
|
||||||
CommandDef("voice", "Toggle voice mode", "Configuration",
|
CommandDef("voice", "Toggle voice mode", "Configuration",
|
||||||
args_hint="[on|off|tts|status]", subcommands=("on", "off", "tts", "status")),
|
args_hint="[on|off|tts|status]", subcommands=("on", "off", "tts", "status")),
|
||||||
|
CommandDef("busy", "Control what Enter does while Hermes is working", "Configuration",
|
||||||
|
cli_only=True, args_hint="[queue|interrupt|status]",
|
||||||
|
subcommands=("queue", "interrupt", "status")),
|
||||||
|
|
||||||
# Tools & Skills
|
# Tools & Skills
|
||||||
CommandDef("tools", "Manage tools: /tools [list|disable|enable] [name...]", "Tools & Skills",
|
CommandDef("tools", "Manage tools: /tools [list|disable|enable] [name...]", "Tools & Skills",
|
||||||
|
|
|
||||||
94
tests/cli/test_busy_input_mode_command.py
Normal file
94
tests/cli/test_busy_input_mode_command.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
"""Tests for the /busy CLI command and busy-input-mode config handling."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
def _import_cli():
|
||||||
|
import hermes_cli.config as config_mod
|
||||||
|
|
||||||
|
if not hasattr(config_mod, "save_env_value_secure"):
|
||||||
|
config_mod.save_env_value_secure = lambda key, value: {
|
||||||
|
"success": True,
|
||||||
|
"stored_as": key,
|
||||||
|
"validated": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
import cli as cli_mod
|
||||||
|
|
||||||
|
return cli_mod
|
||||||
|
|
||||||
|
|
||||||
|
class TestHandleBusyCommand(unittest.TestCase):
|
||||||
|
def _make_cli(self, busy_input_mode="interrupt"):
|
||||||
|
return SimpleNamespace(
|
||||||
|
busy_input_mode=busy_input_mode,
|
||||||
|
agent=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_no_args_shows_status(self):
|
||||||
|
cli_mod = _import_cli()
|
||||||
|
stub = self._make_cli("queue")
|
||||||
|
with (
|
||||||
|
patch.object(cli_mod, "_cprint") as mock_cprint,
|
||||||
|
patch.object(cli_mod, "save_config_value") as mock_save,
|
||||||
|
):
|
||||||
|
cli_mod.HermesCLI._handle_busy_command(stub, "/busy")
|
||||||
|
|
||||||
|
mock_save.assert_not_called()
|
||||||
|
printed = " ".join(str(c) for c in mock_cprint.call_args_list)
|
||||||
|
self.assertIn("queue", printed)
|
||||||
|
self.assertIn("interrupt", printed)
|
||||||
|
|
||||||
|
def test_queue_argument_sets_queue_mode_and_saves(self):
|
||||||
|
cli_mod = _import_cli()
|
||||||
|
stub = self._make_cli("interrupt")
|
||||||
|
with (
|
||||||
|
patch.object(cli_mod, "_cprint"),
|
||||||
|
patch.object(cli_mod, "save_config_value", return_value=True) as mock_save,
|
||||||
|
):
|
||||||
|
cli_mod.HermesCLI._handle_busy_command(stub, "/busy queue")
|
||||||
|
|
||||||
|
self.assertEqual(stub.busy_input_mode, "queue")
|
||||||
|
mock_save.assert_called_once_with("display.busy_input_mode", "queue")
|
||||||
|
|
||||||
|
def test_interrupt_argument_sets_interrupt_mode_and_saves(self):
|
||||||
|
cli_mod = _import_cli()
|
||||||
|
stub = self._make_cli("queue")
|
||||||
|
with (
|
||||||
|
patch.object(cli_mod, "_cprint"),
|
||||||
|
patch.object(cli_mod, "save_config_value", return_value=True) as mock_save,
|
||||||
|
):
|
||||||
|
cli_mod.HermesCLI._handle_busy_command(stub, "/busy interrupt")
|
||||||
|
|
||||||
|
self.assertEqual(stub.busy_input_mode, "interrupt")
|
||||||
|
mock_save.assert_called_once_with("display.busy_input_mode", "interrupt")
|
||||||
|
|
||||||
|
def test_invalid_argument_prints_usage(self):
|
||||||
|
cli_mod = _import_cli()
|
||||||
|
stub = self._make_cli()
|
||||||
|
with (
|
||||||
|
patch.object(cli_mod, "_cprint") as mock_cprint,
|
||||||
|
patch.object(cli_mod, "save_config_value") as mock_save,
|
||||||
|
):
|
||||||
|
cli_mod.HermesCLI._handle_busy_command(stub, "/busy nonsense")
|
||||||
|
|
||||||
|
mock_save.assert_not_called()
|
||||||
|
printed = " ".join(str(c) for c in mock_cprint.call_args_list)
|
||||||
|
self.assertIn("Usage: /busy", printed)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBusyCommandRegistry(unittest.TestCase):
|
||||||
|
def test_busy_in_registry(self):
|
||||||
|
from hermes_cli.commands import COMMAND_REGISTRY
|
||||||
|
|
||||||
|
names = [c.name for c in COMMAND_REGISTRY]
|
||||||
|
assert "busy" in names
|
||||||
|
|
||||||
|
def test_busy_subcommands_documented(self):
|
||||||
|
from hermes_cli.commands import COMMAND_REGISTRY
|
||||||
|
|
||||||
|
busy = next(c for c in COMMAND_REGISTRY if c.name == "busy")
|
||||||
|
assert busy.args_hint == "[queue|interrupt|status]"
|
||||||
|
assert busy.category == "Configuration"
|
||||||
|
|
@ -234,6 +234,14 @@ display:
|
||||||
|
|
||||||
Queue mode is useful when you want to prepare follow-up messages without accidentally canceling in-flight work. Unknown values fall back to `"interrupt"`.
|
Queue mode is useful when you want to prepare follow-up messages without accidentally canceling in-flight work. Unknown values fall back to `"interrupt"`.
|
||||||
|
|
||||||
|
You can also change it inside the CLI:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/busy queue
|
||||||
|
/busy interrupt
|
||||||
|
/busy status
|
||||||
|
```
|
||||||
|
|
||||||
### Suspending to Background
|
### Suspending to Background
|
||||||
|
|
||||||
On Unix systems, press **`Ctrl+Z`** to suspend Hermes to the background — just like any terminal process. The shell prints a confirmation:
|
On Unix systems, press **`Ctrl+Z`** to suspend Hermes to the background — just like any terminal process. The shell prints a confirmation:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue