* docs(code-execution): document HERMES_* env narrowing + passthrough workaround
The execute_code sandbox-child env scrub (108397726, #27303) deliberately
dropped the broad HERMES_ prefix passthrough, keeping only an operational
4-var allowlist (HERMES_HOME/PROFILE/CONFIG/ENV). A script that relied on a
non-secret HERMES_* var (HERMES_BASE_URL, HERMES_KANBAN_DB, HERMES_*_WEBHOOK,
or a plugin-defined one) now sees it unset in the child.
Document the behavior change and the two recovery routes (terminal.env_passthrough
in config.yaml, or required_environment_variables in skill frontmatter), plus
the debug log line that surfaces the drop for diagnosis.
* fix(stt,tts): restore mistralai — 2.4.8 is clean, ban lifted
PyPI quarantined mistralai on 2026-05-12 after the malicious 2.4.6
release (Mini Shai-Hulud worm). 2.4.6 has since been removed from the
registry and clean releases resumed (2.4.7 2026-05-25, 2.4.8 2026-05-28).
This rolls back the blanket runtime ban so Voxtral STT + TTS work again,
following the restoration checklist the repo left in pyproject.toml.
Verified against the real SDK: 2.4.8 keeps the import path the code uses
(from mistralai.client import Mistral) and the audio.transcriptions.complete
/ audio.speech.complete surfaces.
Changes:
- pyproject.toml: re-add mistral extra pinned to mistralai==2.4.8; left
OUT of [all] per the 2026-05-12 lazy-install policy (one quarantined
release must not break fresh installs). uv.lock regenerated.
- tools/lazy_deps.py: add stt.mistral / tts.mistral entries so the SDK
lazy-installs on first use (matches edge / elevenlabs).
- tools/transcription_tools.py: restore explicit-provider gate
(_HAS_MISTRAL + key) and auto-detect entry (local>groq>openai>mistral>xai);
_transcribe_mistral lazy-installs before import.
- tools/tts_tool.py: dispatcher routes back to _generate_mistral_tts;
_import_mistral_client lazy-installs the SDK.
- hermes_cli/tools_config.py, hermes_cli/web_server.py: un-hide Mistral
from the TTS provider picker and dashboard STT options.
- hermes_cli/security_advisories.py: KEEP the shai-hulud-2026-05 advisory
(module policy forbids removal) — it is scoped to 2.4.6 only, so it
still warns anyone with the poisoned build cached and never fires on
2.4.8. Summary note updated to reflect the un-quarantine.
- tests: revert the disabled-behavior assertions added by the ban commit
back to routing/positive expectations; add mistral to the
lazy-installable-extras-excluded-from-[all] contract.
Reported by @SkYNewZ (#34503).
Validation: 189 targeted STT/TTS/lazy_deps/metadata tests pass; E2E with
the real mistralai 2.4.8 SDK routes both STT and TTS to mistral.
126 lines
4.9 KiB
Python
126 lines
4.9 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 _load_package_data():
|
|
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
|
with pyproject_path.open("rb") as handle:
|
|
tool = tomllib.load(handle)["tool"]
|
|
return tool["setuptools"]["package-data"]
|
|
|
|
|
|
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",
|
|
"messaging", "slack", "matrix", "dingtalk", "feishu",
|
|
"honcho", "hindsight",
|
|
"mistral", # mistralai — Voxtral STT/TTS, lazy-installed (stt.mistral / tts.mistral)
|
|
}
|
|
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)
|
|
|
|
|
|
def test_dashboard_plugin_manifests_and_assets_are_packaged():
|
|
"""Bundled dashboard plugins need their manifests and built assets in
|
|
wheel installs so /api/dashboard/plugins can discover them outside a
|
|
source checkout."""
|
|
package_data = _load_package_data()
|
|
plugin_data = package_data["plugins"]
|
|
|
|
assert "*/dashboard/manifest.json" in plugin_data
|
|
assert "*/dashboard/dist/*" in plugin_data
|
|
assert "*/dashboard/dist/**/*" in plugin_data
|