mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
* fix(install): use `--extra all` not `--all-extras`; drop lazy-covered extras from [all]
Two coupled fixes for the Windows install hang where uv sync built
python-olm from sdist and failed on missing make.
# Root cause: --all-extras vs --extra all (credit: ethernet)
`uv sync --all-extras` installs every key in [project.optional-
dependencies], bypassing the curated [all] extra entirely. So even
when [all] excluded [matrix], [rl], [yc-bench], etc., the installer
pulled them anyway because they were still defined as extras. On
Windows that meant python-olm (no wheel, needs make to build from
sdist) and the install died there.
The right flag is `--extra all` — install just the [all] extra's
contents, respecting curation. Empirically verified via dry-run:
--all-extras: pulls python-olm, mautrix, ctranslate2, onnxruntime,
atroposlib, tinker, wandb, modal, daytona, vercel,
python-telegram-bot, discord.py, slack-bolt,
dingtalk-stream, lark-oapi, anthropic, boto3,
edge-tts, elevenlabs, exa-py, fal-client, faster-
whisper, firecrawl-py, honcho-ai, parallel-web
--extra all: pulls none of those — just [all]'s curated set
Dockerfile already uses `--extra all` (with comment explaining the
gotcha) — knowledge existed; the gap was install.sh / install.ps1 /
setup-hermes.sh.
Sites fixed: scripts/install.sh L1118, scripts/install.ps1 L809,
setup-hermes.sh L245.
# Companion fix: drop lazy-covered extras from [all]
`tools/lazy_deps.py` already covers anthropic, bedrock, exa,
firecrawl, parallel-web, fal, edge-tts, elevenlabs, modal, daytona,
vercel, all messaging platforms (telegram/discord/slack/matrix/
dingtalk/feishu), honcho, and faster-whisper. They were ALSO in
[all], which defeats the whole point of lazy-install — fresh
installs eager-pulled them and inherited whatever was broken
upstream (the matrix → python-olm → no Windows wheel chain being
the proximate symptom).
[all] now contains only what genuinely can't be lazy-installed:
cron, cli, dev, pty, mcp, homeassistant, sms, acp, google, web,
youtube. Same trim applied to [termux-all]. New regression test
asserts the contract: every extra in LAZY_DEPS must NOT also appear
in [all].
# Companion fix: surface uv progress + errors
setup-hermes.sh's hash-verified path swallowed uv's stderr to a
tempfile, identical to the install.sh bug fixed in PR #24504. Same
fix applied: stream stderr through directly so users see live
progress instead of staring at a frozen prompt.
# Files
- pyproject.toml: trim [all] and [termux-all] to non-lazy extras only.
- scripts/install.sh: --all-extras → --extra all; trim _ALL_EXTRAS /
_PYPI_EXTRAS to match.
- scripts/install.ps1: --all-extras → --extra all; trim $allExtras /
$pypiExtras to match.
- setup-hermes.sh: --all-extras → --extra all; stream stderr.
- tests/test_project_metadata.py: invert matrix-in-[all] assertion;
add lazy-coverage contract test.
- uv.lock: regenerated.
# Validation
5/5 metadata tests pass. 37/37 in update_autostash + tool_token_
estimation. `uv lock --check` passes. Empirical dry-run confirms
`--extra all` excludes python-olm + RL chain on the new lockfile.
* fix(install): parse [all] from pyproject.toml instead of mirroring it
ethernet's review point: the previous patch left two hand-mirrored
copies of [all]'s contents (in install.sh's $_ALL_EXTRAS and
install.ps1's $allExtras). That guarantees future drift the next
time pyproject.toml's [all] changes.
Now both scripts parse pyproject.toml at install time using stdlib
tomllib (Python 3.11+, which the bootstrap step already requires).
Single source of truth. The only purpose of the parsed list is to
build the 'Tier 2: [all] minus broken extras' fallback spec — so we
parse, filter against $brokenExtras, and rebuild the .[a,b,c] spec.
Also: removed redundant fallback tiers.
Before: Tier 1 [all]
Tier 2 [all] minus broken
Tier 3 PyPI-only extras (no git deps)
Tier 4 [web,mcp,cron,cli,messaging,dev]
Tier 5 .
After: Tier 1 [all]
Tier 2 [all] minus broken
Tier 3 .
Tier 3 (PyPI-only) and Tier 4 (dashboard+core) used to dodge the [rl]
git+sdist deps and the [matrix] python-olm build. Both are no longer
in [all] post-2026-05-12 lazy-install migration, so the carve-out
tiers had no remaining content. Tier 4 also referenced [messaging],
which is now lazy-installed — the hardcoded fallback was actually
inconsistent with the new policy.
Defensive fallback: if tomllib parse fails (corrupted pyproject,
unexpected schema), Tier 2 collapses to '.[all]' (same as Tier 1) so
the broken-extras path becomes a no-op rather than crashing.
* fix(gateway): hide Matrix from setup picker on Windows
Matrix is the one messaging platform that has no working install path
on Windows: [matrix] -> mautrix[encryption] -> python-olm, which has
Linux-only wheels and needs make + libolm to build from sdist. The
[all] cleanup in this PR keeps mautrix out of fresh installs, but a
user who picked Matrix in 'hermes setup gateway' would still walk
into the same sdist build failure when the wizard tried to install
the extra.
Hide the option at the picker so users never get the chance to try.
The gate lives in _all_platforms() — single source of truth for the
setup wizard, the curses gateway-config menu, and any future picker.
Adapter loading at runtime is intentionally NOT gated: users who
already have MATRIX_* env vars set (e.g. config copied from a Linux
install) keep working if they somehow have python-olm available.
This is the lowest-friction fix — picker visibility only.
Tests cover linux/darwin/win32 and verify other platforms aren't
collateral damage.
105 lines
4.2 KiB
Python
105 lines
4.2 KiB
Python
"""Regression tests for packaging metadata in pyproject.toml."""
|
|
|
|
from pathlib import Path
|
|
import tomllib
|
|
|
|
|
|
def _load_optional_dependencies():
|
|
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
|
with pyproject_path.open("rb") as handle:
|
|
project = tomllib.load(handle)["project"]
|
|
return project["optional-dependencies"]
|
|
|
|
|
|
def test_matrix_extra_not_in_all():
|
|
"""The [matrix] extra pulls `mautrix[encryption]` -> `python-olm`,
|
|
which has Linux-only wheels and no native build path on Windows or
|
|
modern macOS (archived libolm, C++ errors with Clang 21+).
|
|
|
|
With matrix in [all], `uv sync --locked` on Windows tried to build
|
|
python-olm from sdist and failed on `make`. As of 2026-05-12 the
|
|
[matrix] extra is excluded from [all] entirely and routed through
|
|
`tools/lazy_deps.py` (LAZY_DEPS["platform.matrix"]) — installs at
|
|
first use, where the user is expected to have a toolchain.
|
|
"""
|
|
optional_dependencies = _load_optional_dependencies()
|
|
|
|
assert "matrix" in optional_dependencies, "[matrix] extra must still exist for explicit `pip install hermes-agent[matrix]`"
|
|
# Must NOT appear in [all] in any form — neither unconditional nor
|
|
# platform-gated. Lazy-install handles it.
|
|
matrix_in_all = [
|
|
dep for dep in optional_dependencies["all"]
|
|
if "matrix" in dep
|
|
]
|
|
assert not matrix_in_all, (
|
|
"matrix must not appear in [all] — it's lazy-installed via "
|
|
"tools/lazy_deps.py LAZY_DEPS['platform.matrix']. Found: "
|
|
f"{matrix_in_all}"
|
|
)
|
|
|
|
|
|
def test_lazy_installable_extras_excluded_from_all():
|
|
"""Policy (2026-05-12): every extra that has a `LAZY_DEPS` entry
|
|
in `tools/lazy_deps.py` must be excluded from [all].
|
|
|
|
The lazy-install system exists so one quarantined PyPI release
|
|
(e.g. mistralai 2.4.6) can't break every fresh install. Putting a
|
|
backend in BOTH [all] and LAZY_DEPS defeats that — fresh installs
|
|
eager-install it and inherit whatever's broken upstream.
|
|
|
|
If you're tempted to add an opt-in backend to [all] for "convenience,"
|
|
add it to `LAZY_DEPS` instead so it installs at first use.
|
|
"""
|
|
optional_dependencies = _load_optional_dependencies()
|
|
|
|
# Hard-coded mirror of the extras that are in LAZY_DEPS as of
|
|
# 2026-05-12. This list intentionally duplicates rather than
|
|
# imports tools/lazy_deps.py so the test stays a contract — if
|
|
# someone adds a new lazy-install backend, they have to update
|
|
# this list AND verify [all] doesn't contain it.
|
|
lazy_covered_extras = {
|
|
"anthropic", "bedrock",
|
|
"exa", "firecrawl", "parallel-web",
|
|
"fal",
|
|
"edge-tts", "tts-premium",
|
|
"voice", # faster-whisper / sounddevice / numpy
|
|
"modal", "daytona", "vercel",
|
|
"messaging", "slack", "matrix", "dingtalk", "feishu",
|
|
"honcho", "hindsight",
|
|
}
|
|
all_extra_specs = optional_dependencies["all"]
|
|
for extra in lazy_covered_extras:
|
|
offending = [
|
|
spec for spec in all_extra_specs
|
|
if f"hermes-agent[{extra}]" in spec
|
|
]
|
|
assert not offending, (
|
|
f"[{extra}] is in [all] but also in LAZY_DEPS. "
|
|
f"Remove it from [all] in pyproject.toml — it lazy-installs "
|
|
f"at first use. Found in [all]: {offending}"
|
|
)
|
|
|
|
|
|
def test_messaging_extra_includes_qrcode_for_weixin_setup():
|
|
optional_dependencies = _load_optional_dependencies()
|
|
|
|
messaging_extra = optional_dependencies["messaging"]
|
|
assert any(dep.startswith("qrcode") for dep in messaging_extra)
|
|
|
|
|
|
def test_dingtalk_extra_includes_qrcode_for_qr_auth():
|
|
"""DingTalk's QR-code device-flow auth (hermes_cli/dingtalk_auth.py)
|
|
needs the qrcode package."""
|
|
optional_dependencies = _load_optional_dependencies()
|
|
|
|
dingtalk_extra = optional_dependencies["dingtalk"]
|
|
assert any(dep.startswith("qrcode") for dep in dingtalk_extra)
|
|
|
|
|
|
def test_feishu_extra_includes_qrcode_for_qr_login():
|
|
"""Feishu's QR login flow (gateway/platforms/feishu.py) needs the
|
|
qrcode package."""
|
|
optional_dependencies = _load_optional_dependencies()
|
|
|
|
feishu_extra = optional_dependencies["feishu"]
|
|
assert any(dep.startswith("qrcode") for dep in feishu_extra)
|