mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-09 13:21:42 +00:00
tui: default to the OpenTUI engine when the host can run it (Ink fallback)
Flip the default engine: with no explicit HERMES_TUI_ENGINE env / display.tui_engine config, resolve to 'opentui' when this host is genuinely set up for it (Bun resolves + the v2 package's entry + node_modules present + not Windows/Termux), else 'ink'. An explicit env/config choice still wins, and 'ink' remains the universal opt-out. Hosts without the OpenTUI setup are unaffected (stay on Ink), so nothing strands a user. - _config_tui_engine_early() now returns None (not 'ink') when unset, so the caller distinguishes 'explicitly ink' from 'unset' and applies the availability-gated default. - _bun_bin() split: _bun_bin_or_none() is the non-fatal probe; _bun_bin() still exit(1)s on the explicit launch path. New _opentui_available() gates the default. - Verified the full resolution matrix (7 cases) + that the platform/availability gates hold.
This commit is contained in:
parent
51031ec655
commit
87b33cb10c
1 changed files with 42 additions and 9 deletions
|
|
@ -1527,10 +1527,11 @@ def _find_bundled_tui(hermes_cli_dir: Path | None = None) -> Path | None:
|
|||
return bundled if bundled.is_file() else None
|
||||
|
||||
|
||||
def _config_tui_engine_early() -> str:
|
||||
def _config_tui_engine_early() -> str | None:
|
||||
"""Read ``display.tui_engine`` from config via a minimal YAML read.
|
||||
|
||||
Best-effort: any error → "ink" (the shipping default). Mirrors
|
||||
Returns the configured engine string, or ``None`` when unset/unreadable so the
|
||||
caller can apply the availability-gated default. Mirrors
|
||||
:func:`_config_default_interface_early`.
|
||||
"""
|
||||
try:
|
||||
|
|
@ -1552,19 +1553,22 @@ def _config_tui_engine_early() -> str:
|
|||
return eng.strip().lower()
|
||||
except Exception:
|
||||
pass
|
||||
return "ink"
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_tui_engine() -> str:
|
||||
"""Which TUI engine to launch: "ink" (default) or "opentui".
|
||||
|
||||
Precedence: ``HERMES_TUI_ENGINE`` env > ``display.tui_engine`` config > "ink".
|
||||
Precedence: ``HERMES_TUI_ENGINE`` env > ``display.tui_engine`` config >
|
||||
(OpenTUI when this host can run it — Bun + the installed package — else Ink).
|
||||
The OpenTUI engine runs on Bun, which is not reliably available on Windows
|
||||
or Termux — request "opentui" there falls back to "ink" with a notice so a
|
||||
stale flag never strands the user on an engine that can't start.
|
||||
"""
|
||||
env = (os.environ.get("HERMES_TUI_ENGINE") or "").strip().lower()
|
||||
engine = env or _config_tui_engine_early()
|
||||
# Explicit choice (env > config) wins; otherwise default to OpenTUI when this
|
||||
# host is genuinely set up for it (Bun + the installed package), else Ink.
|
||||
engine = env or _config_tui_engine_early() or ("opentui" if _opentui_available() else "ink")
|
||||
if engine != "opentui":
|
||||
return "ink"
|
||||
|
||||
|
|
@ -1582,11 +1586,10 @@ def _resolve_tui_engine() -> str:
|
|||
return "opentui"
|
||||
|
||||
|
||||
def _bun_bin() -> str:
|
||||
"""Resolve the Bun binary for the OpenTUI engine.
|
||||
def _bun_bin_or_none() -> str | None:
|
||||
"""Resolve the Bun binary, or ``None`` if not found (no exit — a probe).
|
||||
|
||||
``HERMES_BUN`` override > ``bun`` on PATH > common install dirs. Exits with a
|
||||
clear message if Bun is missing (the OpenTUI engine cannot run without it).
|
||||
``HERMES_BUN`` override > ``bun`` on PATH > common install dirs.
|
||||
"""
|
||||
env_bun = os.environ.get("HERMES_BUN")
|
||||
if env_bun and os.path.isfile(env_bun) and os.access(env_bun, os.X_OK):
|
||||
|
|
@ -1601,6 +1604,18 @@ def _bun_bin() -> str:
|
|||
):
|
||||
if cand.is_file() and os.access(cand, os.X_OK):
|
||||
return str(cand)
|
||||
return None
|
||||
|
||||
|
||||
def _bun_bin() -> str:
|
||||
"""Resolve the Bun binary for the OpenTUI engine, or exit with a clear message.
|
||||
|
||||
The OpenTUI engine cannot run without Bun, so the launch path exits(1) when
|
||||
it's missing. Use :func:`_bun_bin_or_none` for a non-fatal availability probe.
|
||||
"""
|
||||
bun = _bun_bin_or_none()
|
||||
if bun is not None:
|
||||
return bun
|
||||
print(
|
||||
"bun not found — the OpenTUI TUI engine requires Bun.\n"
|
||||
"Install it (https://bun.sh) or set HERMES_BUN=/path/to/bun, "
|
||||
|
|
@ -1610,6 +1625,24 @@ def _bun_bin() -> str:
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
def _opentui_available() -> bool:
|
||||
"""Whether the OpenTUI engine can actually launch on this host.
|
||||
|
||||
True only when the platform supports Bun (not Windows/Termux), Bun resolves,
|
||||
AND the v2 package's entry + its installed ``node_modules`` are present. This
|
||||
gates the DEFAULT engine: a host that's genuinely set up for OpenTUI defaults
|
||||
to it; everyone else stays on Ink. An explicit ``HERMES_TUI_ENGINE`` env or
|
||||
``display.tui_engine`` config choice bypasses this probe entirely.
|
||||
"""
|
||||
if sys.platform.startswith("win") or _is_termux_startup_environment():
|
||||
return False
|
||||
if _bun_bin_or_none() is None:
|
||||
return False
|
||||
pkg = PROJECT_ROOT / "ui-tui-opentui-v2"
|
||||
entry = pkg / "src" / "entry" / "main.tsx"
|
||||
return entry.is_file() and (pkg / "node_modules" / "@opentui").is_dir()
|
||||
|
||||
|
||||
def _make_opentui_argv(tui_dev: bool) -> tuple[list[str], Path]:
|
||||
"""Argv for the native OpenTUI engine (Bun runs the TypeScript entry directly).
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue