fix: initialize self.config in HermesCLI to fix AttributeError on slash commands
HermesCLI.__init__ never assigned self.config, causing an
AttributeError ('HermesCLI' object has no attribute 'config')
whenever an unrecognized slash command fell through to the
quick_commands check on line 2838. This affected skill slash
commands like /x-thread-creation since the quick_commands lookup
runs before the skill command check.
Set self.config = CLI_CONFIG in __init__ to match the pattern used
by the gateway (run.py:199).
This commit is contained in:
parent
f5324f9aa5
commit
bd2606a576
1 changed files with 113 additions and 0 deletions
113
cli.py
113
cli.py
|
|
@ -1244,6 +1244,10 @@ class HermesCLI:
|
||||||
self._command_running = False
|
self._command_running = False
|
||||||
self._command_status = ""
|
self._command_status = ""
|
||||||
|
|
||||||
|
# Background task tracking: {task_id: threading.Thread}
|
||||||
|
self._background_tasks: Dict[str, threading.Thread] = {}
|
||||||
|
self._background_task_counter = 0
|
||||||
|
|
||||||
def _invalidate(self, min_interval: float = 0.25) -> None:
|
def _invalidate(self, min_interval: float = 0.25) -> None:
|
||||||
"""Throttled UI repaint — prevents terminal blinking on slow/SSH connections."""
|
"""Throttled UI repaint — prevents terminal blinking on slow/SSH connections."""
|
||||||
import time as _time
|
import time as _time
|
||||||
|
|
@ -2825,6 +2829,8 @@ class HermesCLI:
|
||||||
self._reload_mcp()
|
self._reload_mcp()
|
||||||
elif cmd_lower.startswith("/rollback"):
|
elif cmd_lower.startswith("/rollback"):
|
||||||
self._handle_rollback_command(cmd_original)
|
self._handle_rollback_command(cmd_original)
|
||||||
|
elif cmd_lower.startswith("/background"):
|
||||||
|
self._handle_background_command(cmd_original)
|
||||||
elif cmd_lower.startswith("/skin"):
|
elif cmd_lower.startswith("/skin"):
|
||||||
self._handle_skin_command(cmd_original)
|
self._handle_skin_command(cmd_original)
|
||||||
else:
|
else:
|
||||||
|
|
@ -2869,6 +2875,113 @@ class HermesCLI:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _handle_background_command(self, cmd: str):
|
||||||
|
"""Handle /background <prompt> — run a prompt in a separate background session.
|
||||||
|
|
||||||
|
Spawns a new AIAgent in a background thread with its own session.
|
||||||
|
When it completes, prints the result to the CLI without modifying
|
||||||
|
the active session's conversation history.
|
||||||
|
"""
|
||||||
|
parts = cmd.strip().split(maxsplit=1)
|
||||||
|
if len(parts) < 2 or not parts[1].strip():
|
||||||
|
_cprint(" Usage: /background <prompt>")
|
||||||
|
_cprint(" Example: /background Summarize the top HN stories today")
|
||||||
|
_cprint(" The task runs in a separate session and results display here when done.")
|
||||||
|
return
|
||||||
|
|
||||||
|
prompt = parts[1].strip()
|
||||||
|
self._background_task_counter += 1
|
||||||
|
task_num = self._background_task_counter
|
||||||
|
task_id = f"bg_{datetime.now().strftime('%H%M%S')}_{uuid.uuid4().hex[:6]}"
|
||||||
|
|
||||||
|
# Make sure we have valid credentials
|
||||||
|
if not self._ensure_runtime_credentials():
|
||||||
|
_cprint(" (>_<) Cannot start background task: no valid credentials.")
|
||||||
|
return
|
||||||
|
|
||||||
|
_cprint(f" 🔄 Background task #{task_num} started: \"{prompt[:60]}{'...' if len(prompt) > 60 else ''}\"")
|
||||||
|
_cprint(f" Task ID: {task_id}")
|
||||||
|
_cprint(f" You can continue chatting — results will appear when done.\n")
|
||||||
|
|
||||||
|
def run_background():
|
||||||
|
try:
|
||||||
|
bg_agent = AIAgent(
|
||||||
|
model=self.model,
|
||||||
|
api_key=self.api_key,
|
||||||
|
base_url=self.base_url,
|
||||||
|
provider=self.provider,
|
||||||
|
api_mode=self.api_mode,
|
||||||
|
max_iterations=self.max_turns,
|
||||||
|
enabled_toolsets=self.enabled_toolsets,
|
||||||
|
quiet_mode=True,
|
||||||
|
verbose_logging=False,
|
||||||
|
session_id=task_id,
|
||||||
|
platform="cli",
|
||||||
|
session_db=self._session_db,
|
||||||
|
reasoning_config=self.reasoning_config,
|
||||||
|
providers_allowed=self._providers_only,
|
||||||
|
providers_ignored=self._providers_ignore,
|
||||||
|
providers_order=self._providers_order,
|
||||||
|
provider_sort=self._provider_sort,
|
||||||
|
provider_require_parameters=self._provider_require_params,
|
||||||
|
provider_data_collection=self._provider_data_collection,
|
||||||
|
fallback_model=self._fallback_model,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = bg_agent.run_conversation(
|
||||||
|
user_message=prompt,
|
||||||
|
task_id=task_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = result.get("final_response", "") if result else ""
|
||||||
|
if not response and result and result.get("error"):
|
||||||
|
response = f"Error: {result['error']}"
|
||||||
|
|
||||||
|
# Display result in the CLI (thread-safe via patch_stdout)
|
||||||
|
print()
|
||||||
|
_cprint(f"{_GOLD}{'─' * 40}{_RST}")
|
||||||
|
_cprint(f" ✅ Background task #{task_num} complete")
|
||||||
|
_cprint(f" Prompt: \"{prompt[:60]}{'...' if len(prompt) > 60 else ''}\"")
|
||||||
|
_cprint(f"{_GOLD}{'─' * 40}{_RST}")
|
||||||
|
if response:
|
||||||
|
try:
|
||||||
|
from hermes_cli.skin_engine import get_active_skin
|
||||||
|
_skin = get_active_skin()
|
||||||
|
label = _skin.get_branding("response_label", "⚕ Hermes")
|
||||||
|
_resp_color = _skin.get_color("response_border", "#CD7F32")
|
||||||
|
except Exception:
|
||||||
|
label = "⚕ Hermes"
|
||||||
|
_resp_color = "#CD7F32"
|
||||||
|
|
||||||
|
_chat_console = ChatConsole()
|
||||||
|
_chat_console.print(Panel(
|
||||||
|
response,
|
||||||
|
title=f"[bold]{label} (background #{task_num})[/bold]",
|
||||||
|
title_align="left",
|
||||||
|
border_style=_resp_color,
|
||||||
|
box=rich_box.HORIZONTALS,
|
||||||
|
padding=(1, 2),
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
_cprint(" (No response generated)")
|
||||||
|
|
||||||
|
# Play bell if enabled
|
||||||
|
if self.bell_on_complete:
|
||||||
|
sys.stdout.write("\a")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print()
|
||||||
|
_cprint(f" ❌ Background task #{task_num} failed: {e}")
|
||||||
|
finally:
|
||||||
|
self._background_tasks.pop(task_id, None)
|
||||||
|
if self._app:
|
||||||
|
self._invalidate(min_interval=0)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=run_background, daemon=True, name=f"bg-task-{task_id}")
|
||||||
|
self._background_tasks[task_id] = thread
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def _handle_skin_command(self, cmd: str):
|
def _handle_skin_command(self, cmd: str):
|
||||||
"""Handle /skin [name] — show or change the display skin."""
|
"""Handle /skin [name] — show or change the display skin."""
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue