feat: add slash command for busy input mode

This commit is contained in:
Julia Bennet 2026-04-13 11:35:54 -04:00 committed by Teknium
parent 2de8a7a229
commit 1dcf79a864
4 changed files with 137 additions and 0 deletions

32
cli.py
View file

@ -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():

View file

@ -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",

View 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"

View file

@ -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: