mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Merge branch 'main' of github.com:NousResearch/hermes-agent into feat/ink-refactor
This commit is contained in:
commit
f81dba0da2
128 changed files with 8357 additions and 842 deletions
69
cli.py
69
cli.py
|
|
@ -2057,7 +2057,17 @@ class HermesCLI:
|
|||
"""Return the visible height for the spinner/status text line above the status bar."""
|
||||
if not getattr(self, "_spinner_text", ""):
|
||||
return 0
|
||||
return 0 if self._use_minimal_tui_chrome(width=width) else 1
|
||||
if self._use_minimal_tui_chrome(width=width):
|
||||
return 0
|
||||
# Compute how many lines the spinner text needs when wrapped.
|
||||
# The rendered text is " {emoji} {label} ({elapsed})" — about
|
||||
# len(_spinner_text) + 16 chars for indent + timer suffix.
|
||||
width = width or self._get_tui_terminal_width()
|
||||
if width and width > 10:
|
||||
import math
|
||||
text_len = len(self._spinner_text) + 16 # indent + timer
|
||||
return max(1, math.ceil(text_len / width))
|
||||
return 1
|
||||
|
||||
def _get_voice_status_fragments(self, width: Optional[int] = None):
|
||||
"""Return the voice status bar fragments for the interactive TUI."""
|
||||
|
|
@ -4001,23 +4011,14 @@ class HermesCLI:
|
|||
|
||||
def _handle_profile_command(self):
|
||||
"""Display active profile name and home directory."""
|
||||
from hermes_constants import get_hermes_home, display_hermes_home
|
||||
from hermes_constants import display_hermes_home
|
||||
from hermes_cli.profiles import get_active_profile_name
|
||||
|
||||
home = get_hermes_home()
|
||||
display = display_hermes_home()
|
||||
|
||||
profiles_parent = Path.home() / ".hermes" / "profiles"
|
||||
try:
|
||||
rel = home.relative_to(profiles_parent)
|
||||
profile_name = str(rel).split("/")[0]
|
||||
except ValueError:
|
||||
profile_name = None
|
||||
profile_name = get_active_profile_name()
|
||||
|
||||
print()
|
||||
if profile_name:
|
||||
print(f" Profile: {profile_name}")
|
||||
else:
|
||||
print(" Profile: default")
|
||||
print(f" Profile: {profile_name}")
|
||||
print(f" Home: {display}")
|
||||
print()
|
||||
|
||||
|
|
@ -5599,7 +5600,8 @@ class HermesCLI:
|
|||
version = f" v{p['version']}" if p["version"] else ""
|
||||
tools = f"{p['tools']} tools" if p["tools"] else ""
|
||||
hooks = f"{p['hooks']} hooks" if p["hooks"] else ""
|
||||
parts = [x for x in [tools, hooks] if x]
|
||||
commands = f"{p['commands']} commands" if p.get("commands") else ""
|
||||
parts = [x for x in [tools, hooks, commands] if x]
|
||||
detail = f" ({', '.join(parts)})" if parts else ""
|
||||
error = f" — {p['error']}" if p["error"] else ""
|
||||
print(f" {status} {p['name']}{version}{detail}{error}")
|
||||
|
|
@ -7866,7 +7868,33 @@ class HermesCLI:
|
|||
# Fallback for non-interactive mode (e.g., single-query)
|
||||
agent_thread.join(0.1)
|
||||
|
||||
agent_thread.join() # Ensure agent thread completes
|
||||
# Wait for the agent thread to finish. After an interrupt the
|
||||
# agent may take a few seconds to clean up (kill subprocess, persist
|
||||
# session). Poll instead of a blocking join so the process_loop
|
||||
# stays responsive — if the user sent another interrupt or the
|
||||
# agent gets stuck, we can break out instead of freezing forever.
|
||||
if interrupt_msg is not None:
|
||||
# Interrupt path: poll briefly, then move on. The agent
|
||||
# thread is daemon — it dies on process exit regardless.
|
||||
for _wait_tick in range(50): # 50 * 0.2s = 10s max
|
||||
agent_thread.join(timeout=0.2)
|
||||
if not agent_thread.is_alive():
|
||||
break
|
||||
# Check if user fired ANOTHER interrupt (Ctrl+C sets
|
||||
# _should_exit which process_loop checks on next pass).
|
||||
if getattr(self, '_should_exit', False):
|
||||
break
|
||||
if agent_thread.is_alive():
|
||||
logger.warning(
|
||||
"Agent thread still alive after interrupt "
|
||||
"(thread %s). Daemon thread will be cleaned up "
|
||||
"on exit.",
|
||||
agent_thread.ident,
|
||||
)
|
||||
else:
|
||||
# Normal completion: agent thread should be done already,
|
||||
# but guard against edge cases.
|
||||
agent_thread.join(timeout=30)
|
||||
|
||||
# Proactively clean up async clients whose event loop is dead.
|
||||
# The agent thread may have created AsyncOpenAI clients bound
|
||||
|
|
@ -9159,6 +9187,7 @@ class HermesCLI:
|
|||
spinner_widget = Window(
|
||||
content=FormattedTextControl(get_spinner_text),
|
||||
height=get_spinner_height,
|
||||
wrap_lines=True,
|
||||
)
|
||||
|
||||
spacer = Window(
|
||||
|
|
@ -10118,6 +10147,11 @@ def main(
|
|||
):
|
||||
cli.agent.quiet_mode = True
|
||||
cli.agent.suppress_status_output = True
|
||||
# Suppress streaming display callbacks so stdout stays
|
||||
# machine-readable (no styled "Hermes" box, no tool-gen
|
||||
# status lines). The response is printed once below.
|
||||
cli.agent.stream_delta_callback = None
|
||||
cli.agent.tool_gen_callback = None
|
||||
result = cli.agent.run_conversation(
|
||||
user_message=effective_query,
|
||||
conversation_history=cli.conversation_history,
|
||||
|
|
@ -10125,7 +10159,8 @@ def main(
|
|||
response = result.get("final_response", "") if isinstance(result, dict) else str(result)
|
||||
if response:
|
||||
print(response)
|
||||
print(f"\nsession_id: {cli.session_id}")
|
||||
# Session ID goes to stderr so piped stdout is clean.
|
||||
print(f"\nsession_id: {cli.session_id}", file=sys.stderr)
|
||||
|
||||
# Ensure proper exit code for automation wrappers
|
||||
sys.exit(1 if isinstance(result, dict) and result.get("failed") else 0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue