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:
@@ -88,103 +88,6 @@ def test_lazy_installable_extras_excluded_from_all():
|
||||
)
|
||||
|
||||
|
||||
def _exact_pins(specs):
|
||||
pins = {}
|
||||
for spec in specs:
|
||||
requirement = spec.split(";", 1)[0].strip()
|
||||
if "==" not in requirement:
|
||||
continue
|
||||
package, version = requirement.split("==", 1)
|
||||
package = package.split("[", 1)[0].lower().replace("_", "-")
|
||||
pins[package] = version
|
||||
return pins
|
||||
|
||||
|
||||
def test_pyproject_aiohttp_pins_match_lazy_slack_pin():
|
||||
"""Avoid update/lazy-install churn from conflicting aiohttp pins.
|
||||
|
||||
pyproject extras (messaging/slack/homeassistant/sms) exact-pin aiohttp.
|
||||
The Slack lazy-install deps (LAZY_DEPS['platform.slack']) also pin it.
|
||||
If the two drift, `hermes update` resolves the pyproject pin and
|
||||
downgrades aiohttp, reopening the CVEs the lazy pin fixed (#31817) —
|
||||
only for Slack's lazy refresh to upgrade it again on next use.
|
||||
"""
|
||||
from tools.lazy_deps import LAZY_DEPS
|
||||
|
||||
optional_dependencies = _load_optional_dependencies()
|
||||
lazy_aiohttp = _exact_pins(LAZY_DEPS["platform.slack"])["aiohttp"]
|
||||
|
||||
pyproject_aiohttp_pins = {
|
||||
extra: pins["aiohttp"]
|
||||
for extra, specs in optional_dependencies.items()
|
||||
if "aiohttp" in (pins := _exact_pins(specs))
|
||||
}
|
||||
|
||||
assert pyproject_aiohttp_pins, "expected at least one pyproject extra to pin aiohttp"
|
||||
mismatches = {
|
||||
extra: pin
|
||||
for extra, pin in pyproject_aiohttp_pins.items()
|
||||
if pin != lazy_aiohttp
|
||||
}
|
||||
assert not mismatches, (
|
||||
"pyproject.toml aiohttp pins must match "
|
||||
"LAZY_DEPS['platform.slack'] to avoid hermes update downgrading "
|
||||
"aiohttp before Slack's lazy refresh upgrades it again. "
|
||||
f"lazy aiohttp=={lazy_aiohttp}; mismatched extras: {mismatches}"
|
||||
)
|
||||
|
||||
|
||||
def test_pyproject_pins_match_lazy_deps_pins():
|
||||
"""Generalize #31817 to the whole pin surface, not just aiohttp.
|
||||
|
||||
Any package that is exact-pinned in BOTH a pyproject extra and a
|
||||
`tools/lazy_deps.py` LAZY_DEPS entry must use the SAME version in both
|
||||
places. When they drift, `hermes update` resolves the pyproject extra
|
||||
pin and downgrades the package to the older version, reopening whatever
|
||||
the lazy pin fixed (the aiohttp #31817 case, and the anthropic
|
||||
CVE-2026-34450/34452 case found alongside it) — only for the lazy
|
||||
refresh to re-upgrade it on next feature use. The lazy pin is the
|
||||
security-current source of truth; extras must track it.
|
||||
"""
|
||||
from tools.lazy_deps import LAZY_DEPS
|
||||
|
||||
optional_dependencies = _load_optional_dependencies()
|
||||
|
||||
# package -> version, as pinned across all pyproject extras. If an
|
||||
# extra pins a package at a different version than another extra, that
|
||||
# is itself a bug (caught below); here we just collect the set.
|
||||
pyproject_pins: dict[str, set[str]] = {}
|
||||
for specs in optional_dependencies.values():
|
||||
for package, version in _exact_pins(specs).items():
|
||||
pyproject_pins.setdefault(package, set()).add(version)
|
||||
|
||||
# package -> version, as pinned across all LAZY_DEPS entries.
|
||||
lazy_pins: dict[str, set[str]] = {}
|
||||
for specs in LAZY_DEPS.values():
|
||||
if isinstance(specs, str):
|
||||
specs = (specs,)
|
||||
for package, version in _exact_pins(specs).items():
|
||||
lazy_pins.setdefault(package, set()).add(version)
|
||||
|
||||
shared = sorted(set(pyproject_pins) & set(lazy_pins))
|
||||
assert shared, "expected at least one package pinned in both pyproject and LAZY_DEPS"
|
||||
|
||||
drift = {
|
||||
package: {
|
||||
"pyproject": sorted(pyproject_pins[package]),
|
||||
"lazy_deps": sorted(lazy_pins[package]),
|
||||
}
|
||||
for package in shared
|
||||
if pyproject_pins[package] != lazy_pins[package]
|
||||
}
|
||||
assert not drift, (
|
||||
"pyproject extras pins must match tools/lazy_deps.py LAZY_DEPS pins "
|
||||
"for every shared package — otherwise `hermes update` downgrades the "
|
||||
"package below the security-current lazy pin (see #31817). Drift: "
|
||||
f"{drift}"
|
||||
)
|
||||
|
||||
|
||||
def test_dev_extra_excluded_from_all():
|
||||
"""End-user installs should not pull test/lint/debug tooling."""
|
||||
optional_dependencies = _load_optional_dependencies()
|
||||
|
||||
Reference in New Issue
Block a user