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:
@@ -1,5 +1,4 @@
|
||||
from pathlib import Path
|
||||
import re
|
||||
import tomllib
|
||||
|
||||
import pytest
|
||||
@@ -15,22 +14,6 @@ find_packages = pytest.importorskip("setuptools", exc_type=ImportError).find_pac
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _distribution_name(requirement: str) -> str:
|
||||
"""Extract the PEP 508 distribution name from a requirement string.
|
||||
|
||||
Robust to markers (``; python_version < '3.12'``), direct references
|
||||
(``name @ https://...``), extras (``name[extra]``) and every version
|
||||
operator (``==``, ``>=``, ``<=``, ``~=``, ``!=``, ``<``, ``>``), so a
|
||||
future dep declared with any valid specifier shape doesn't silently
|
||||
mis-parse here.
|
||||
"""
|
||||
spec = requirement.split(";", 1)[0] # drop environment markers
|
||||
spec = spec.split("@", 1)[0] # drop direct-reference URLs
|
||||
spec = spec.split("[", 1)[0] # drop extras
|
||||
spec = re.split(r"[=<>!~]", spec, maxsplit=1)[0] # drop any version operator
|
||||
return spec.strip().lower()
|
||||
|
||||
|
||||
def _packages_find_include():
|
||||
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
return data["tool"]["setuptools"]["packages"]["find"]["include"]
|
||||
@@ -78,27 +61,6 @@ def test_every_on_disk_subpackage_is_covered_by_packages_find():
|
||||
)
|
||||
|
||||
|
||||
def test_packaging_declared_as_core_dependency():
|
||||
"""Regression for #40503.
|
||||
|
||||
``packaging`` is imported directly on three production paths
|
||||
(plugins/memory/hindsight/__init__.py, tools/lazy_deps.py,
|
||||
hermes_cli/main.py) yet was undeclared, so it only reached users
|
||||
transitively. The slim Docker image shipped without it, silently
|
||||
disabling Hindsight append-mode and version-constraint checks. It must
|
||||
be a declared core dependency so it installs everywhere and the
|
||||
update-repair step (``_verify_core_dependencies_installed``) guards it.
|
||||
"""
|
||||
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
core = data["project"]["dependencies"]
|
||||
names = {_distribution_name(dep) for dep in core}
|
||||
assert "packaging" in names, (
|
||||
"packaging is imported on production paths (hindsight version compare, "
|
||||
"lazy_deps version constraints, requirement parsing) and must be a "
|
||||
"declared core dependency, not a transitive — see #40503"
|
||||
)
|
||||
|
||||
|
||||
def test_faster_whisper_is_not_a_base_dependency():
|
||||
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
deps = data["project"]["dependencies"]
|
||||
@@ -264,40 +226,3 @@ def test_locale_catalogs_ship_in_both_wheel_and_sdist():
|
||||
# Every on-disk catalog has the .yaml extension the globs above match.
|
||||
on_disk = list((REPO_ROOT / "locales").glob("*.yaml"))
|
||||
assert on_disk, "expected locales/*.yaml catalogs on disk"
|
||||
|
||||
|
||||
def test_optional_mcps_manifests_ship_in_both_wheel_and_sdist():
|
||||
"""Regression guard: the shipped MCP catalog must reach packaged installs.
|
||||
|
||||
hermes_cli/mcp_catalog.py resolves the catalog via get_optional_mcps_dir()
|
||||
-> _get_packaged_data_dir("optional-mcps"), and list_catalog() returns []
|
||||
when that directory is absent. optional-mcps/ is a bare data directory (no
|
||||
__init__.py), invisible to packages.find and package-data. It must ship as
|
||||
setuptools data-files (wheel) AND be grafted in MANIFEST.in (sdist), or
|
||||
`hermes mcp catalog` and the dashboard catalog screen come up empty on
|
||||
pip / Homebrew / Nix installs even though the manifests exist in the repo.
|
||||
|
||||
data-files flattens every glob match into its single target dir, so each
|
||||
catalog entry needs its OWN target to preserve the optional-mcps/<name>/
|
||||
directory the catalog iterates over. This asserts one target per on-disk
|
||||
entry so a newly-added MCP can't silently miss the wheel.
|
||||
"""
|
||||
entries = sorted(
|
||||
p.parent.name for p in (REPO_ROOT / "optional-mcps").glob("*/manifest.yaml")
|
||||
)
|
||||
assert entries, "expected optional-mcps/<name>/manifest.yaml on disk"
|
||||
|
||||
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
data_files = data["tool"]["setuptools"].get("data-files", {})
|
||||
for name in entries:
|
||||
target = f"optional-mcps/{name}"
|
||||
assert target in data_files, (
|
||||
f"pyproject [tool.setuptools.data-files] must declare a '{target}' "
|
||||
f"target so the wheel ships optional-mcps/{name}/manifest.yaml "
|
||||
f"(data-files flattens globs, so each catalog entry needs its own target)"
|
||||
)
|
||||
|
||||
manifest = (REPO_ROOT / "MANIFEST.in").read_text(encoding="utf-8")
|
||||
assert "graft optional-mcps" in manifest, (
|
||||
"MANIFEST.in must `graft optional-mcps` so the sdist ships MCP manifests"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user