mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-22 05:22:09 +00:00
feat(terminal,cli): docker_extra_args + display.timestamps
Two independent opt-in QoL toggles, both off by default. terminal.docker_extra_args: - List of extra flags appended verbatim to docker run after security defaults. Useful for adding capabilities (e.g. --cap-add SETUID) or other docker run options not exposed by existing config keys. - Non-string entries are logged and skipped. - Also available via TERMINAL_DOCKER_EXTRA_ARGS='[...]' env var. display.timestamps: - Appends [HH:MM] to user input bullet and the assistant response box header. Single hub in _format_submitted_user_message_preview() covers both single-line and multi-line user previews; assistant response label gets the timestamp at box-open time. Closes #1569 (timestamps). Co-authored-by: Mibayy <Mibayy@users.noreply.github.com>
This commit is contained in:
parent
228b7d27bd
commit
ebf2ea584a
5 changed files with 38 additions and 2 deletions
|
|
@ -203,6 +203,12 @@ terminal:
|
||||||
# docker_forward_env:
|
# docker_forward_env:
|
||||||
# - "GITHUB_TOKEN"
|
# - "GITHUB_TOKEN"
|
||||||
# - "NPM_TOKEN"
|
# - "NPM_TOKEN"
|
||||||
|
# # Optional: extra flags passed verbatim to docker run (appended after security defaults).
|
||||||
|
# # Useful for adding capabilities (e.g. apt installs needing SETUID) or custom options.
|
||||||
|
# # Example: add a Linux capability not included by default
|
||||||
|
# # docker_extra_args:
|
||||||
|
# # - "--cap-add"
|
||||||
|
# # - "SETUID"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# OPTION 4: Singularity/Apptainer container
|
# OPTION 4: Singularity/Apptainer container
|
||||||
|
|
@ -947,6 +953,9 @@ display:
|
||||||
# false: Wait for the full response before rendering
|
# false: Wait for the full response before rendering
|
||||||
streaming: true
|
streaming: true
|
||||||
|
|
||||||
|
# Show [HH:MM] timestamps on user input and assistant response labels.
|
||||||
|
# timestamps: false
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────────
|
# ───────────────────────────────────────────────────────────────────────────
|
||||||
# Skin / Theme
|
# Skin / Theme
|
||||||
# ───────────────────────────────────────────────────────────────────────────
|
# ───────────────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
14
cli.py
14
cli.py
|
|
@ -2300,6 +2300,8 @@ class HermesCLI:
|
||||||
|
|
||||||
# streaming: stream tokens to the terminal as they arrive (display.streaming in config.yaml)
|
# streaming: stream tokens to the terminal as they arrive (display.streaming in config.yaml)
|
||||||
self.streaming_enabled = CLI_CONFIG["display"].get("streaming", False)
|
self.streaming_enabled = CLI_CONFIG["display"].get("streaming", False)
|
||||||
|
# show_timestamps: prefix user and assistant labels with [HH:MM]
|
||||||
|
self.show_timestamps = CLI_CONFIG["display"].get("timestamps", False)
|
||||||
self.final_response_markdown = str(
|
self.final_response_markdown = str(
|
||||||
CLI_CONFIG["display"].get("final_response_markdown", "strip")
|
CLI_CONFIG["display"].get("final_response_markdown", "strip")
|
||||||
).strip().lower() or "strip"
|
).strip().lower() or "strip"
|
||||||
|
|
@ -3315,9 +3317,13 @@ class HermesCLI:
|
||||||
|
|
||||||
def _format_submitted_user_message_preview(self, user_input: str) -> str:
|
def _format_submitted_user_message_preview(self, user_input: str) -> str:
|
||||||
"""Format the submitted user-message scrollback preview."""
|
"""Format the submitted user-message scrollback preview."""
|
||||||
|
ts_suffix = (
|
||||||
|
f" [dim]{datetime.now().strftime('%H:%M')}[/]"
|
||||||
|
if getattr(self, "show_timestamps", False) else ""
|
||||||
|
)
|
||||||
lines = user_input.split("\n")
|
lines = user_input.split("\n")
|
||||||
if len(lines) <= 1:
|
if len(lines) <= 1:
|
||||||
return f"[bold {_accent_hex()}]●[/] [bold]{_escape(user_input)}[/]"
|
return f"[bold {_accent_hex()}]●[/] [bold]{_escape(user_input)}[/]{ts_suffix}"
|
||||||
|
|
||||||
first_lines = int(getattr(self, "user_message_preview_first_lines", 2))
|
first_lines = int(getattr(self, "user_message_preview_first_lines", 2))
|
||||||
last_lines = int(getattr(self, "user_message_preview_last_lines", 2))
|
last_lines = int(getattr(self, "user_message_preview_last_lines", 2))
|
||||||
|
|
@ -3334,7 +3340,7 @@ class HermesCLI:
|
||||||
tail = []
|
tail = []
|
||||||
|
|
||||||
preview_lines = [
|
preview_lines = [
|
||||||
f"[bold {_accent_hex()}]●[/] [bold]{_escape(head[0])}[/]"
|
f"[bold {_accent_hex()}]●[/] [bold]{_escape(head[0])}[/]{ts_suffix}"
|
||||||
]
|
]
|
||||||
preview_lines.extend(f"[bold]{_escape(line)}[/]" for line in head[1:])
|
preview_lines.extend(f"[bold]{_escape(line)}[/]" for line in head[1:])
|
||||||
|
|
||||||
|
|
@ -3606,6 +3612,8 @@ class HermesCLI:
|
||||||
self._stream_text_ansi = f"\033[38;2;{_r};{_g};{_b}m"
|
self._stream_text_ansi = f"\033[38;2;{_r};{_g};{_b}m"
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
self._stream_text_ansi = ""
|
self._stream_text_ansi = ""
|
||||||
|
if self.show_timestamps:
|
||||||
|
label = f"{label} {datetime.now().strftime('%H:%M')}"
|
||||||
w = shutil.get_terminal_size().columns
|
w = shutil.get_terminal_size().columns
|
||||||
fill = w - 2 - len(label)
|
fill = w - 2 - len(label)
|
||||||
_cprint(f"\n{_ACCENT}╭─{label}{'─' * max(fill - 1, 0)}╮{_RST}")
|
_cprint(f"\n{_ACCENT}╭─{label}{'─' * max(fill - 1, 0)}╮{_RST}")
|
||||||
|
|
@ -10162,6 +10170,8 @@ class HermesCLI:
|
||||||
_streaming_box_opened = True
|
_streaming_box_opened = True
|
||||||
w = self.console.width
|
w = self.console.width
|
||||||
label = " ⚕ Hermes "
|
label = " ⚕ Hermes "
|
||||||
|
if self.show_timestamps:
|
||||||
|
label = f"{label}{datetime.now().strftime('%H:%M')} "
|
||||||
fill = w - 2 - len(label)
|
fill = w - 2 - len(label)
|
||||||
_cprint(f"\n{_ACCENT}╭─{label}{'─' * max(fill - 1, 0)}╮{_RST}")
|
_cprint(f"\n{_ACCENT}╭─{label}{'─' * max(fill - 1, 0)}╮{_RST}")
|
||||||
_cprint(f"{_STREAM_PAD}{sentence.rstrip()}")
|
_cprint(f"{_STREAM_PAD}{sentence.rstrip()}")
|
||||||
|
|
|
||||||
|
|
@ -579,6 +579,7 @@ DEFAULT_CONFIG = {
|
||||||
# Explicit opt-in: mount the host cwd into /workspace for Docker sessions.
|
# Explicit opt-in: mount the host cwd into /workspace for Docker sessions.
|
||||||
# Default off because passing host directories into a sandbox weakens isolation.
|
# Default off because passing host directories into a sandbox weakens isolation.
|
||||||
"docker_mount_cwd_to_workspace": False,
|
"docker_mount_cwd_to_workspace": False,
|
||||||
|
"docker_extra_args": [], # Extra flags passed verbatim to docker run
|
||||||
# Explicit opt-in: run the Docker container as the host user's uid:gid
|
# Explicit opt-in: run the Docker container as the host user's uid:gid
|
||||||
# (via `--user`). When enabled, files written into bind-mounted dirs
|
# (via `--user`). When enabled, files written into bind-mounted dirs
|
||||||
# (docker_volumes, the persistent workspace, or the auto-mounted cwd)
|
# (docker_volumes, the persistent workspace, or the auto-mounted cwd)
|
||||||
|
|
@ -901,6 +902,7 @@ DEFAULT_CONFIG = {
|
||||||
"bell_on_complete": False,
|
"bell_on_complete": False,
|
||||||
"show_reasoning": False,
|
"show_reasoning": False,
|
||||||
"streaming": False,
|
"streaming": False,
|
||||||
|
"timestamps": False, # Show [HH:MM] on user and assistant labels
|
||||||
"final_response_markdown": "strip", # render | strip | raw
|
"final_response_markdown": "strip", # render | strip | raw
|
||||||
# Preserve recent classic CLI output across Ctrl+L, /redraw, and
|
# Preserve recent classic CLI output across Ctrl+L, /redraw, and
|
||||||
# terminal resize full-screen clears. Disable if a terminal emulator
|
# terminal resize full-screen clears. Disable if a terminal emulator
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,7 @@ class DockerEnvironment(BaseEnvironment):
|
||||||
host_cwd: str = None,
|
host_cwd: str = None,
|
||||||
auto_mount_cwd: bool = False,
|
auto_mount_cwd: bool = False,
|
||||||
run_as_host_user: bool = False,
|
run_as_host_user: bool = False,
|
||||||
|
extra_args: list = None,
|
||||||
):
|
):
|
||||||
if cwd == "~":
|
if cwd == "~":
|
||||||
cwd = "/root"
|
cwd = "/root"
|
||||||
|
|
@ -476,6 +477,15 @@ class DockerEnvironment(BaseEnvironment):
|
||||||
security_args = _build_security_args(run_as_host_user and bool(user_args))
|
security_args = _build_security_args(run_as_host_user and bool(user_args))
|
||||||
|
|
||||||
logger.info(f"Docker volume_args: {volume_args}")
|
logger.info(f"Docker volume_args: {volume_args}")
|
||||||
|
# User-supplied extra docker run flags (docker_extra_args in config.yaml).
|
||||||
|
# Appended last so they can override defaults if needed.
|
||||||
|
validated_extra = []
|
||||||
|
for arg in (extra_args or []):
|
||||||
|
if not isinstance(arg, str):
|
||||||
|
logger.warning("Ignoring non-string docker_extra_args entry: %r", arg)
|
||||||
|
continue
|
||||||
|
validated_extra.append(arg)
|
||||||
|
|
||||||
all_run_args = (
|
all_run_args = (
|
||||||
security_args
|
security_args
|
||||||
+ user_args
|
+ user_args
|
||||||
|
|
@ -483,6 +493,7 @@ class DockerEnvironment(BaseEnvironment):
|
||||||
+ resource_args
|
+ resource_args
|
||||||
+ volume_args
|
+ volume_args
|
||||||
+ env_args
|
+ env_args
|
||||||
|
+ validated_extra
|
||||||
)
|
)
|
||||||
logger.info(f"Docker run_args: {all_run_args}")
|
logger.info(f"Docker run_args: {all_run_args}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1087,6 +1087,7 @@ def _get_env_config() -> Dict[str, Any]:
|
||||||
"docker_volumes": _parse_env_var("TERMINAL_DOCKER_VOLUMES", "[]", json.loads, "valid JSON"),
|
"docker_volumes": _parse_env_var("TERMINAL_DOCKER_VOLUMES", "[]", json.loads, "valid JSON"),
|
||||||
"docker_env": _parse_env_var("TERMINAL_DOCKER_ENV", "{}", json.loads, "valid JSON"),
|
"docker_env": _parse_env_var("TERMINAL_DOCKER_ENV", "{}", json.loads, "valid JSON"),
|
||||||
"docker_run_as_host_user": os.getenv("TERMINAL_DOCKER_RUN_AS_HOST_USER", "false").lower() in ("true", "1", "yes"),
|
"docker_run_as_host_user": os.getenv("TERMINAL_DOCKER_RUN_AS_HOST_USER", "false").lower() in ("true", "1", "yes"),
|
||||||
|
"docker_extra_args": _parse_env_var("TERMINAL_DOCKER_EXTRA_ARGS", "[]", json.loads, "valid JSON"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1129,6 +1130,7 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
|
||||||
volumes = cc.get("docker_volumes", [])
|
volumes = cc.get("docker_volumes", [])
|
||||||
docker_forward_env = cc.get("docker_forward_env", [])
|
docker_forward_env = cc.get("docker_forward_env", [])
|
||||||
docker_env = cc.get("docker_env", {})
|
docker_env = cc.get("docker_env", {})
|
||||||
|
docker_extra_args = cc.get("docker_extra_args", [])
|
||||||
|
|
||||||
if env_type == "local":
|
if env_type == "local":
|
||||||
return _LocalEnvironment(cwd=cwd, timeout=timeout)
|
return _LocalEnvironment(cwd=cwd, timeout=timeout)
|
||||||
|
|
@ -1144,6 +1146,7 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
|
||||||
forward_env=docker_forward_env,
|
forward_env=docker_forward_env,
|
||||||
env=docker_env,
|
env=docker_env,
|
||||||
run_as_host_user=cc.get("docker_run_as_host_user", False),
|
run_as_host_user=cc.get("docker_run_as_host_user", False),
|
||||||
|
extra_args=docker_extra_args,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif env_type == "singularity":
|
elif env_type == "singularity":
|
||||||
|
|
@ -1792,6 +1795,7 @@ def terminal_tool(
|
||||||
"docker_forward_env": config.get("docker_forward_env", []),
|
"docker_forward_env": config.get("docker_forward_env", []),
|
||||||
"docker_env": config.get("docker_env", {}),
|
"docker_env": config.get("docker_env", {}),
|
||||||
"docker_run_as_host_user": config.get("docker_run_as_host_user", False),
|
"docker_run_as_host_user": config.get("docker_run_as_host_user", False),
|
||||||
|
"docker_extra_args": config.get("docker_extra_args", []),
|
||||||
}
|
}
|
||||||
|
|
||||||
local_config = None
|
local_config = None
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue