mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
fix(kanban): spawn workers headless — TUI can never eat a worker run
An inherited HERMES_TUI=1 or a `display.interface: tui` config default sent kanban workers into the Ink TUI, whose no-TTY bail-out exits 0 without doing the task — every attempt ended in "protocol violation". Two layers: - _default_spawn pins `--cli` (highest-precedence interface flag) and strips HERMES_TUI from the child env (covers older builds on PATH). - _resolve_use_tui gates ambient TUI prefs (env/config) behind a real TTY; an explicit --tui still wins so the informative bail-out stays reachable.
This commit is contained in:
parent
5829fe1378
commit
e87c495dc2
4 changed files with 108 additions and 5 deletions
|
|
@ -5,10 +5,18 @@ flip ``display.interface: tui`` in config.yaml to make the modern Ink TUI the
|
|||
default for bare ``hermes`` / ``hermes chat``. Explicit flags always win:
|
||||
|
||||
--cli forces the classic REPL (highest precedence)
|
||||
--tui / HERMES_TUI=1 forces the TUI
|
||||
--tui forces the TUI
|
||||
(no TTY) forces the classic REPL — ambient prefs don't apply
|
||||
HERMES_TUI=1 the env default
|
||||
display.interface the configured default
|
||||
(unset) classic REPL
|
||||
|
||||
The no-TTY gate exists because ambient TUI preferences must never hijack
|
||||
non-interactive invocations: kanban workers / cron / pipelines run
|
||||
``hermes … chat -q`` on a pipe, and the TUI's no-TTY bail-out exits 0
|
||||
without doing the work (a kanban worker then dies with "protocol
|
||||
violation" on every attempt).
|
||||
|
||||
These tests pin that precedence at every layer that makes the decision:
|
||||
|
||||
* ``_resolve_use_tui(args)`` — the canonical args-aware resolver used by
|
||||
|
|
@ -46,6 +54,14 @@ def _args(**kw):
|
|||
return SimpleNamespace(**kw)
|
||||
|
||||
|
||||
def _fake_tty(monkeypatch, interactive: bool):
|
||||
"""Pin stdin/stdout TTY-ness — pytest's capture is never a real TTY."""
|
||||
import sys as _sys
|
||||
|
||||
monkeypatch.setattr(_sys.stdin, "isatty", lambda: interactive, raising=False)
|
||||
monkeypatch.setattr(_sys.stdout, "isatty", lambda: interactive, raising=False)
|
||||
|
||||
|
||||
def _patch_config(monkeypatch, interface):
|
||||
import hermes_cli.config as cfg
|
||||
|
||||
|
|
@ -73,19 +89,23 @@ class TestResolveUseTui:
|
|||
|
||||
def test_env_beats_config_cli(self, monkeypatch):
|
||||
_patch_config(monkeypatch, "cli")
|
||||
_fake_tty(monkeypatch, True)
|
||||
monkeypatch.setenv("HERMES_TUI", "1")
|
||||
assert m._resolve_use_tui(_args()) is True
|
||||
|
||||
def test_config_tui_with_no_flags(self, monkeypatch):
|
||||
_patch_config(monkeypatch, "tui")
|
||||
_fake_tty(monkeypatch, True)
|
||||
assert m._resolve_use_tui(_args()) is True
|
||||
|
||||
def test_config_cli_is_default(self, monkeypatch):
|
||||
_patch_config(monkeypatch, "cli")
|
||||
_fake_tty(monkeypatch, True)
|
||||
assert m._resolve_use_tui(_args()) is False
|
||||
|
||||
def test_interface_value_is_case_insensitive(self, monkeypatch):
|
||||
_patch_config(monkeypatch, "TUI")
|
||||
_fake_tty(monkeypatch, True)
|
||||
assert m._resolve_use_tui(_args()) is True
|
||||
|
||||
def test_load_config_failure_falls_back_to_cli(self, monkeypatch):
|
||||
|
|
@ -95,8 +115,28 @@ class TestResolveUseTui:
|
|||
raise RuntimeError("config unreadable")
|
||||
|
||||
monkeypatch.setattr(cfg, "load_config", boom)
|
||||
_fake_tty(monkeypatch, True)
|
||||
assert m._resolve_use_tui(_args()) is False
|
||||
|
||||
# ── the no-TTY gate: ambient prefs never hijack non-interactive runs ────
|
||||
def test_no_tty_blocks_env_tui(self, monkeypatch):
|
||||
_patch_config(monkeypatch, "cli")
|
||||
_fake_tty(monkeypatch, False)
|
||||
monkeypatch.setenv("HERMES_TUI", "1")
|
||||
assert m._resolve_use_tui(_args()) is False
|
||||
|
||||
def test_no_tty_blocks_config_tui(self, monkeypatch):
|
||||
_patch_config(monkeypatch, "tui")
|
||||
_fake_tty(monkeypatch, False)
|
||||
assert m._resolve_use_tui(_args()) is False
|
||||
|
||||
def test_explicit_tui_flag_survives_no_tty(self, monkeypatch):
|
||||
# An explicit --tui is the user's own ask — keep the informative
|
||||
# no-TTY bail-out instead of silently swapping interfaces.
|
||||
_patch_config(monkeypatch, "cli")
|
||||
_fake_tty(monkeypatch, False)
|
||||
assert m._resolve_use_tui(_args(tui=True)) is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _wants_tui_early — dependency-free early resolver
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue