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:
alt-glitch
2026-06-08 11:11:54 +00:00
parent 24f74eb888
commit 2bd9c9b881
741 changed files with 17733 additions and 79889 deletions
+1 -17
View File
@@ -177,7 +177,6 @@ def reap_orphan_containers(
listing = subprocess.run(
[docker, "ps", "-a", *filters, "--format", "{{.ID}}"],
capture_output=True, text=True, timeout=15, check=False,
stdin=subprocess.DEVNULL,
)
except (subprocess.TimeoutExpired, OSError) as e:
logger.debug("orphan reaper docker ps failed: %s", e)
@@ -211,7 +210,6 @@ def reap_orphan_containers(
result = subprocess.run(
[docker, "rm", "-f", cid],
capture_output=True, text=True, timeout=30,
stdin=subprocess.DEVNULL,
)
if result.returncode == 0:
removed += 1
@@ -241,7 +239,6 @@ def _container_finished_at(docker_exe: str, container_id: str):
result = subprocess.run(
[docker_exe, "inspect", "--format", "{{.State.FinishedAt}}", container_id],
capture_output=True, text=True, timeout=10, check=False,
stdin=subprocess.DEVNULL,
)
except (subprocess.TimeoutExpired, OSError) as e:
logger.debug("orphan reaper docker inspect %s failed: %s", container_id[:12], e)
@@ -384,7 +381,6 @@ def _image_uses_init_entrypoint(docker_exe: str, image: str) -> bool:
capture_output=True,
text=True,
timeout=15,
stdin=subprocess.DEVNULL,
)
except (subprocess.SubprocessError, OSError) as e:
logger.debug("Docker: could not inspect entrypoint for %s: %s", image, e)
@@ -457,7 +453,6 @@ def _ensure_docker_available() -> None:
capture_output=True,
text=True,
timeout=5,
stdin=subprocess.DEVNULL,
)
except FileNotFoundError:
logger.error(
@@ -838,7 +833,6 @@ class DockerEnvironment(BaseEnvironment):
text=True,
timeout=30,
check=True,
stdin=subprocess.DEVNULL,
)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
logger.warning(
@@ -877,7 +871,6 @@ class DockerEnvironment(BaseEnvironment):
text=True,
timeout=120, # image pull may take a while
check=True,
stdin=subprocess.DEVNULL,
)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
# Docker may create the container object before `docker run`
@@ -894,7 +887,6 @@ class DockerEnvironment(BaseEnvironment):
subprocess.run(
[self._docker_exe, "rm", "-f", container_name],
capture_output=True, timeout=10,
stdin=subprocess.DEVNULL,
)
raise
self._container_id = result.stdout.strip()
@@ -1005,7 +997,6 @@ class DockerEnvironment(BaseEnvironment):
subprocess.run(
[self._docker_exe, "start", cid],
capture_output=True, text=True, timeout=30, check=True,
stdin=subprocess.DEVNULL,
)
self._container_id = cid
logger.info("Recovery: restarted container %s", cid[:12])
@@ -1036,7 +1027,6 @@ class DockerEnvironment(BaseEnvironment):
]
result = subprocess.run(
run_cmd, capture_output=True, text=True, timeout=120, check=True,
stdin=subprocess.DEVNULL,
)
self._container_id = result.stdout.strip()
self._container_name = new_name
@@ -1091,7 +1081,6 @@ class DockerEnvironment(BaseEnvironment):
result = subprocess.run(
[docker, "info", "--format", "{{.Driver}}"],
capture_output=True, text=True, timeout=10,
stdin=subprocess.DEVNULL,
)
driver = result.stdout.strip().lower()
if driver != "overlay2":
@@ -1102,15 +1091,13 @@ class DockerEnvironment(BaseEnvironment):
probe = subprocess.run(
[docker, "create", "--storage-opt", "size=1m", "hello-world"],
capture_output=True, text=True, timeout=15,
stdin=subprocess.DEVNULL,
)
if probe.returncode == 0:
# Clean up the created container
container_id = probe.stdout.strip()
if container_id:
subprocess.run([docker, "rm", container_id],
capture_output=True, timeout=5,
stdin=subprocess.DEVNULL)
capture_output=True, timeout=5)
_storage_opt_ok = True
else:
_storage_opt_ok = False
@@ -1145,7 +1132,6 @@ class DockerEnvironment(BaseEnvironment):
text=True,
timeout=10,
check=False,
stdin=subprocess.DEVNULL,
)
except (subprocess.TimeoutExpired, OSError) as e:
logger.debug("docker ps probe failed: %s — will start a fresh container", e)
@@ -1262,7 +1248,6 @@ class DockerEnvironment(BaseEnvironment):
subprocess.run(
[docker_exe, "stop", "-t", "10", container_id],
capture_output=True, timeout=30,
stdin=subprocess.DEVNULL,
)
except (subprocess.TimeoutExpired, OSError) as e:
logger.warning("docker stop %s timed out / failed: %s", log_id, e)
@@ -1271,7 +1256,6 @@ class DockerEnvironment(BaseEnvironment):
subprocess.run(
[docker_exe, "rm", "-f", container_id],
capture_output=True, timeout=30,
stdin=subprocess.DEVNULL,
)
except (subprocess.TimeoutExpired, OSError) as e:
logger.warning("docker rm -f %s failed: %s", log_id, e)
+11 -69
View File
@@ -300,72 +300,6 @@ _SANE_PATH = (
)
def _append_missing_sane_path_entries(existing_path: str) -> str:
"""Return a normalised POSIX PATH with missing sane entries appended.
On POSIX the caller-supplied PATH is rewritten (not merely appended to):
empty entries and duplicate entries are dropped, preserving
first-occurrence order, then each missing ``_SANE_PATH`` entry is appended
once at the end so existing entries keep their precedence.
Two intentional normalisations beyond the bare "add Homebrew dirs" fix:
- **Empty entries are stripped.** A leading/trailing/double ``:`` encodes
an empty PATH element, which POSIX shells interpret as the current
working directory — a mild foot-gun in a default terminal environment.
We drop these rather than carry them through.
- **Duplicates are collapsed** (first occurrence wins), so a caller PATH
that already contains repeats is not propagated verbatim.
For a well-formed PATH (no empties, no duplicates) the leading segment is
byte-identical to the input and ordering is preserved; only the missing
sane entries are appended. On Windows this is a no-op passthrough (the
separator is ``;`` and the native PATH must not be touched).
"""
if _IS_WINDOWS:
return existing_path
sane_entries = [entry for entry in _SANE_PATH.split(":") if entry]
if not existing_path:
return ":".join(sane_entries)
# De-duplicate the caller PATH (first occurrence wins) and drop empty
# entries before merging in the sane fallbacks.
seen: set[str] = set()
ordered_entries: list[str] = []
for entry in existing_path.split(":"):
if not entry or entry in seen:
continue
seen.add(entry)
ordered_entries.append(entry)
# _SANE_PATH is a static, duplicate-free constant, so a membership check
# against the caller entries is sufficient — no need to track `seen` here.
for entry in sane_entries:
if entry not in seen:
ordered_entries.append(entry)
return ":".join(ordered_entries)
def _path_env_key(run_env: dict) -> str | None:
"""Return the PATH env key to update without altering Windows casing.
Note: this is deliberately a *second* Windows guard, distinct from the
early-return in ``_append_missing_sane_path_entries``. Its job is to pick
the correctly-cased key (``Path`` vs ``PATH``) so completion writes back to
the key the caller already used; the helper's guard makes that helper safe
to call standalone (it is, e.g. in the Windows unit tests). Both are
intentional.
"""
if not _IS_WINDOWS:
return "PATH"
for key in run_env:
if key.upper() == "PATH":
return key
return None
def _make_run_env(env: dict) -> dict:
"""Build a run environment with a sane PATH and provider-var stripping."""
try:
@@ -381,9 +315,17 @@ def _make_run_env(env: dict) -> dict:
run_env[real_key] = v
elif k not in _HERMES_PROVIDER_ENV_BLOCKLIST or _is_passthrough(k):
run_env[k] = v
path_key = _path_env_key(run_env)
if path_key is not None:
run_env[path_key] = _append_missing_sane_path_entries(run_env.get(path_key, ""))
existing_path = run_env.get("PATH", "")
# The "/usr/bin not already present → inject sane POSIX path" heuristic
# only makes sense on POSIX. On Windows the PATH separator is ";"
# (the split(":") above turns a full Windows PATH into a single
# unrecognisable chunk, which then triggers prepending POSIX paths
# to a Windows PATH — completely wrong). Skip the injection entirely
# on Windows; the native PATH already points at whatever shell
# Hermes is driving via _find_bash (Git Bash), and Git Bash itself
# prepends its MSYS2 /usr/bin equivalent via the shell-init files.
if not _IS_WINDOWS and "/usr/bin" not in existing_path.split(":"):
run_env["PATH"] = f"{existing_path}:{_SANE_PATH}" if existing_path else _SANE_PATH
_inject_context_hermes_home(run_env)
+1 -4
View File
@@ -46,7 +46,6 @@ def _ensure_singularity_available() -> str:
try:
result = subprocess.run(
[exe, "version"], capture_output=True, text=True, timeout=10,
stdin=subprocess.DEVNULL,
)
except FileNotFoundError:
raise RuntimeError(
@@ -137,7 +136,6 @@ def _get_or_build_sif(image: str, executable: str = "apptainer") -> str:
result = subprocess.run(
[executable, "build", str(sif_path), image],
capture_output=True, text=True, timeout=600, env=env,
stdin=subprocess.DEVNULL,
)
if result.returncode != 0:
logger.warning("SIF build failed, falling back to docker:// URL")
@@ -220,7 +218,7 @@ class SingularityEnvironment(BaseEnvironment):
cmd.extend([str(self.image), self.instance_id])
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
raise RuntimeError(f"Failed to start instance: {result.stderr}")
self._instance_started = True
@@ -252,7 +250,6 @@ class SingularityEnvironment(BaseEnvironment):
subprocess.run(
[self.executable, "instance", "stop", self.instance_id],
capture_output=True, text=True, timeout=30,
stdin=subprocess.DEVNULL,
)
logger.info("Singularity instance %s stopped", self.instance_id)
except Exception as e: