mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-26 01:01:40 +00:00
feat(cli): add shell noise filtering and improve command execution with interactive login shell
This commit is contained in:
parent
f14ff3e041
commit
fb7df099e0
2 changed files with 49 additions and 10 deletions
|
|
@ -9,6 +9,23 @@ import time
|
|||
|
||||
from tools.environments.base import BaseEnvironment
|
||||
|
||||
# Noise lines emitted by interactive shells when stdin is not a terminal.
|
||||
# Filtered from output to keep tool results clean.
|
||||
_SHELL_NOISE = frozenset({
|
||||
"bash: no job control in this shell",
|
||||
"bash: no job control in this shell\n",
|
||||
"no job control in this shell",
|
||||
"no job control in this shell\n",
|
||||
})
|
||||
|
||||
|
||||
def _clean_shell_noise(output: str) -> str:
|
||||
"""Strip shell startup warnings that leak when using -i without a TTY."""
|
||||
lines = output.split("\n", 2) # only check first two lines
|
||||
if lines and lines[0].strip() in _SHELL_NOISE:
|
||||
return "\n".join(lines[1:])
|
||||
return output
|
||||
|
||||
|
||||
class LocalEnvironment(BaseEnvironment):
|
||||
"""Run commands directly on the host machine.
|
||||
|
|
@ -18,7 +35,7 @@ class LocalEnvironment(BaseEnvironment):
|
|||
- Background stdout drain thread to prevent pipe buffer deadlocks
|
||||
- stdin_data support for piping content (bypasses ARG_MAX limits)
|
||||
- sudo -S transform via SUDO_PASSWORD env var
|
||||
- Uses bash login shell so user env (.profile/.bashrc) is available
|
||||
- Uses interactive login shell so full user env is available
|
||||
"""
|
||||
|
||||
def __init__(self, cwd: str = "", timeout: int = 60, env: dict = None):
|
||||
|
|
@ -34,14 +51,15 @@ class LocalEnvironment(BaseEnvironment):
|
|||
exec_command = self._prepare_command(command)
|
||||
|
||||
try:
|
||||
# Use the user's login shell so that rc files (.profile, .bashrc,
|
||||
# .zprofile, .zshrc, etc.) are sourced and user-installed tools
|
||||
# (nvm, pyenv, cargo, etc.) are available. Without this, Python's
|
||||
# Popen(shell=True) uses /bin/sh which is dash on Debian/Ubuntu
|
||||
# and old bash on macOS — neither sources the user's environment.
|
||||
# Use the user's shell as an interactive login shell (-lic) so
|
||||
# that ALL rc files are sourced — including content after the
|
||||
# interactive guard in .bashrc (case $- in *i*)..esac) where
|
||||
# tools like nvm, pyenv, and cargo install their init scripts.
|
||||
# -l alone isn't enough: .profile sources .bashrc, but the guard
|
||||
# returns early because the shell isn't interactive.
|
||||
user_shell = os.environ.get("SHELL") or shutil.which("bash") or "/bin/bash"
|
||||
proc = subprocess.Popen(
|
||||
[user_shell, "-lc", exec_command],
|
||||
[user_shell, "-lic", exec_command],
|
||||
text=True,
|
||||
cwd=work_dir,
|
||||
env=os.environ | self.env,
|
||||
|
|
@ -106,7 +124,8 @@ class LocalEnvironment(BaseEnvironment):
|
|||
time.sleep(0.2)
|
||||
|
||||
reader.join(timeout=5)
|
||||
return {"output": "".join(_output_chunks), "returncode": proc.returncode}
|
||||
output = _clean_shell_noise("".join(_output_chunks))
|
||||
return {"output": output, "returncode": proc.returncode}
|
||||
|
||||
except Exception as e:
|
||||
return {"output": f"Execution error: {str(e)}", "returncode": 1}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue