opentui(phase3): launcher integration — HERMES_TUI_ENGINE dual-engine
hermes --tui launches the native OpenTUI engine (Bun) when HERMES_TUI_ENGINE=opentui (env) or display.tui_engine=opentui (config); Ink stays the default and the shipping path is untouched. - _resolve_tui_engine() (env > config > ink); refuses opentui on Windows/Termux (no Bun) -> falls back to ink with a notice. - _make_opentui_argv() -> [bun, src/entry.real.tsx] (no build step). - _bun_bin() with HERMES_BUN override. - Branch at top of _make_tui_argv BEFORE _ensure_tui_node (Bun-only host must not bootstrap Node). - Gate _launch_tui NODE_OPTIONS/--max-old-space-size on engine==ink (Bun is JSC; the V8 flag errors/ignores). Verified end-to-end via tmux: real hermes --tui -> Bun -> OpenTUI -> real Python gateway streamed a real reply. No-flag default still ink.
This commit is contained in:
+2
-52
@@ -151,13 +151,7 @@ def _is_gateway_approval_context() -> bool:
|
||||
return bool(_get_session_platform())
|
||||
|
||||
# Sensitive write targets that should trigger approval even when referenced
|
||||
# via shell expansions like $HOME or $HERMES_HOME, or by the resolved absolute
|
||||
# active profile home path such as /home/hermes/.hermes/config.yaml. The
|
||||
# resolved-absolute form is folded into the ~/.hermes/ patterns at detection
|
||||
# time by _normalize_command_for_detection() — see the rewrite step there — so
|
||||
# these static patterns stay free of any import-time path snapshot (which would
|
||||
# go stale when HERMES_HOME is set after this module is imported, e.g. under the
|
||||
# hermetic test conftest or any deferred-profile-resolution path).
|
||||
# via shell expansions like $HOME or $HERMES_HOME.
|
||||
_SSH_SENSITIVE_PATH = r'(?:~|\$home|\$\{home\})/\.ssh(?:/|$)'
|
||||
_HERMES_ENV_PATH = (
|
||||
r'(?:~\/\.hermes/|'
|
||||
@@ -545,49 +539,8 @@ def _normalize_command_for_detection(command: str) -> str:
|
||||
command = unicodedata.normalize('NFKC', command)
|
||||
# Strip shell backslash-escapes: r\m → rm. Prevents \-injection bypass.
|
||||
command = re.sub(r'\\([^\n])', r'\1', command)
|
||||
# Strip empty-string literals that split tokens: r''m → rm, r"\"m → rm.
|
||||
# Strip empty-string literals that split tokens: r''m → rm, r""m → rm.
|
||||
command = re.sub(r"''|\"\"", '', command)
|
||||
# Fold the resolved absolute active-profile home path into the canonical
|
||||
# ~/.hermes/ form so the Hermes config/env patterns catch it. In Docker and
|
||||
# gateway deployments the agent often references the resolved absolute path
|
||||
# directly (e.g. `sed -i ... /home/hermes/.hermes/config.yaml`) rather than
|
||||
# ~, $HOME, or $HERMES_HOME. Done at detection time (not via an import-time
|
||||
# pattern snapshot) so it tracks the live HERMES_HOME even when that is set
|
||||
# after this module is imported — as the hermetic test conftest does.
|
||||
command = _rewrite_resolved_hermes_home(command)
|
||||
return command
|
||||
|
||||
|
||||
def _rewrite_resolved_hermes_home(command: str) -> str:
|
||||
"""Rewrite the resolved absolute Hermes home prefix to ``~/.hermes/``.
|
||||
|
||||
Resolves the active ``HERMES_HOME`` at call time (and its symlink-resolved
|
||||
form) and replaces an occurrence of ``<home>/`` in *command* with
|
||||
``~/.hermes/`` so the static ``_HERMES_CONFIG_PATH`` / ``_HERMES_ENV_PATH``
|
||||
patterns match. No-op when the path can't be resolved or doesn't appear.
|
||||
"""
|
||||
try:
|
||||
from hermes_constants import get_hermes_home
|
||||
home = get_hermes_home().expanduser()
|
||||
candidates = [
|
||||
str(home).rstrip("/"),
|
||||
str(home.resolve(strict=False)).rstrip("/"),
|
||||
]
|
||||
except Exception:
|
||||
return command
|
||||
seen: set[str] = set()
|
||||
for path in candidates:
|
||||
if not path or path in seen:
|
||||
continue
|
||||
seen.add(path)
|
||||
# Guard against a degenerate HERMES_HOME (e.g. "/" or "") rewriting
|
||||
# unrelated paths: require an absolute path with at least one non-root
|
||||
# component. The active profile home is always a real directory like
|
||||
# /home/hermes/.hermes or a per-test tempdir, never a bare root.
|
||||
normalized = path.rstrip("/")
|
||||
if not normalized.startswith("/") or normalized.count("/") < 2:
|
||||
continue
|
||||
command = command.replace(normalized + "/", "~/.hermes/")
|
||||
return command
|
||||
|
||||
|
||||
@@ -1428,9 +1381,6 @@ def check_all_command_guards(command: str, env_type: str,
|
||||
"pattern_key": primary_key,
|
||||
"pattern_keys": all_keys,
|
||||
"description": combined_desc,
|
||||
# Mirror the CLI's allow_permanent gate: a tirith warning downgrades
|
||||
# "always" to session scope below, so the UI must not offer it.
|
||||
"allow_permanent": not has_tirith,
|
||||
}
|
||||
decision = _await_gateway_decision(
|
||||
session_key, notify_cb, approval_data, surface="gateway"
|
||||
|
||||
Reference in New Issue
Block a user