The extra removal left CI, the Docker image and the nix package still
requesting `hindsight`. Once the extra is gone, `--extra hindsight` and
extraDependencyGroups = [ "hindsight" ] ask for something that no longer
exists. Drop them the same way 73c598e319 originally did: remove it from
the CI extras lists, the Docker sealed-venv build and the nix default
groups, and point the nix examples/check at honcho. Also remove the
stray blank line left in the exclude-newer table.
944 lines
47 KiB
TOML
944 lines
47 KiB
TOML
|
|
|
|
|
|
|
|
[project]
|
|
name = "hermes-agent"
|
|
version = "0.0.0"
|
|
description = "The self-improving AI agent — creates skills from experience, improves them during use, and runs anywhere"
|
|
readme = "README.md"
|
|
# 3.14 is the newest python we know we have wheels for.
|
|
# we *only* support 3.14, BUT we need to allow old hermes installs on <3.14
|
|
# to get thru their update step on old python.
|
|
# Shortly after they do, we update to our latest python.
|
|
# See tests/compat/README.md for the updater contract this keeps.
|
|
requires-python = ">=3.11,<3.15"
|
|
authors = [{ name = "Nous Research" }]
|
|
license = "MIT"
|
|
license-files = ["LICENSE"]
|
|
dependencies = [
|
|
# Core — every direct dep is exact-pinned to ==X.Y.Z (no ranges).
|
|
# Rationale: ranges allow PyPI to ship a fresh version of a transitive
|
|
# at any time without a code review on our side. Exact pins mean the
|
|
# only way a new package version reaches a user is via an intentional
|
|
# update on our end (bump the pin in this file, regenerate uv.lock).
|
|
# This was tightened on 2026-05-12 in response to the Mini Shai-Hulud
|
|
# worm hitting mistralai 2.4.6 on PyPI; if that release had been
|
|
# captured by `mistralai>=2.3.0,<3` rather than an exact pin, every
|
|
# install in the hours before the quarantine would have pulled it.
|
|
#
|
|
# After a pin change, run `hermes pm lock` so the transitive resolution
|
|
# stays consistent. Do not introduce ranges
|
|
# without a written justification.
|
|
#
|
|
# Scope rule: only packages used by EVERY hermes session belong here.
|
|
# Anything that's provider-specific (`anthropic`, `firecrawl-py`,
|
|
# `exa-py`, `fal-client`, `edge-tts`, `parallel-web`) belongs in an
|
|
# extra and gets prepared through PM when the
|
|
# user picks that backend. Smaller `dependencies` = smaller blast
|
|
# radius for the next supply-chain attack.
|
|
"openai==2.24.0; python_version >= '3.14'",
|
|
"certifi==2026.5.20; python_version >= '3.14'",
|
|
# TLS trust comes from the OS certificate store (agent/ssl_verify.py), so
|
|
# a corporate/internal root installed on the machine just works. certifi
|
|
# above stays a direct pin because httpx/requests/openai each depend on
|
|
# it regardless; it is no longer OUR trust source.
|
|
# ssl_verify uses truststore's private original SSLContext for explicit CA overrides.
|
|
# Keep this exact until that path has a public-API replacement.
|
|
"truststore==0.10.4; python_version >= '3.14'",
|
|
"python-dotenv==1.2.2; python_version >= '3.14'",
|
|
"fire==0.7.1; python_version >= '3.14'",
|
|
"httpx[socks]==0.28.1; python_version >= '3.14'",
|
|
"rich==14.3.3; python_version >= '3.14'",
|
|
"tenacity==9.1.4; python_version >= '3.14'",
|
|
"tomli-w==1.2.0; python_version >= '3.14'", # Preserve relative dependency paths in staged plugin metadata.
|
|
# 0.18.17 requires ruamel.yaml.clib on 3.14, which has no Windows ARM64 wheel.
|
|
"ruamel.yaml==0.18.16; python_version >= '3.14'",
|
|
"requests==2.33.0; python_version >= '3.14'", # CVE-2026-25645
|
|
"jinja2==3.1.6; python_version >= '3.14'",
|
|
# Document-to-Markdown extraction for read_file (PDF, legacy Office,
|
|
# OpenDocument, RTF, EPUB) + typed NeedsOcrError for scanned pages.
|
|
# Bundled in core by maintainer decision (read_file is a core tool and
|
|
# PDF reads are a common first-session action; the previous lazy-only
|
|
# arrangement dated to the package's uv exclude-newer quarantine, which
|
|
# has long expired). PM's doc-extract extra repairs lean/broken installs
|
|
# from this same dependency declaration.
|
|
"firecrawl-anydoc==0.2.4; python_version >= '3.14'",
|
|
# Bumped from 2.12.5 to 2.13.4 to pull in pydantic-core 2.46.4.
|
|
# pydantic-core 2.41.5 (pulled by 2.12.5) segfaults when the OpenAI SDK's
|
|
# Responses API resource is exercised from a non-main thread, which is the
|
|
# codex_responses dispatch in agent/chat_completion_helpers.py:_call.
|
|
"pydantic==2.13.4; python_version >= '3.14'",
|
|
# Interactive CLI (prompt_toolkit is used directly by cli.py)
|
|
"prompt_toolkit==3.0.52; python_version >= '3.14'",
|
|
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
|
|
"croniter==6.0.0; python_version >= '3.14'",
|
|
# Snowball stemming for tool_search's BM25 (tools/tool_search.py) —
|
|
# official Snowball project package, pure Python, zero transitive deps.
|
|
# Applied at index AND query time so morphological variants match
|
|
# ("issues" finds create_issue).
|
|
"snowballstemmer==3.1.1; python_version >= '3.14'",
|
|
# Requirement and version checks must work in lean installs without
|
|
# relying on another package to bring packaging in transitively.
|
|
"packaging==26.0; python_version >= '3.14'",
|
|
# Markdown -> HTML conversion for rich message delivery (Matrix
|
|
# `formatted_body`, and the `send_message` tool's HTML path). Now on the
|
|
# DEFAULT delivery path, not matrix-specific: without it both
|
|
# gateway/platforms/matrix.py and tools/send_message_tool.py silently fall
|
|
# back to plain text, so cron/agent deliveries render raw `##`/`**`/tables
|
|
# in clients like Element (see #32486). Pure-Python py3-none-any wheel
|
|
# (~108KB, no compiled extensions, no platform constraints), so unlike the
|
|
# matrix extra's `mautrix`/`python-olm` it's safe to ship everywhere — keeps
|
|
# it out of the lazy-install path that exists only for the heavy matrix deps.
|
|
"Markdown==3.10.2; python_version >= '3.14'",
|
|
# Skills Hub (GitHub App JWT auth — optional, only needed for bot identity)
|
|
"PyJWT[crypto]==2.13.0; python_version >= '3.14'", # PYSEC-2026-175/177/178/179
|
|
# urllib3 2.7.0 fixes GHSA-mf9v-mfxr-j63j (decompression-bomb bypass)
|
|
# and GHSA-qccp-gfcp-xxvc (header leak across origins).
|
|
"urllib3>=2.7.0,<3; python_version >= '3.14'",
|
|
# PyJWT[crypto] pulls cryptography in transitively. Pin it here as well, so
|
|
# that the WeCom and Weixin crypto paths cannot fall below the patched
|
|
# version. 50.0.0 is the floor: it fixes CVE-2026-69247, a Bleichenbacher
|
|
# oracle in PKCS#7 EnvelopedData, and 49.0.0 fixed GHSA-m2h6-j472-rp4c,
|
|
# where a wildcard DNS name escapes permittedSubtrees, and
|
|
# GHSA-jwv3-5hgf-82ww, exponential path-building on duplicate self-signed
|
|
# intermediates.
|
|
#
|
|
# A pin here is not sufficient on its own. alibabacloud-tea-openapi caps
|
|
# cryptography<49, so [tool.uv] also holds an override. Read that comment
|
|
# before you move this version.
|
|
"cryptography==50.0.1; python_version >= '3.14'", # CVE-2026-69247, GHSA-m2h6-j472-rp4c, GHSA-jwv3-5hgf-82ww, CVE-2026-39892, CVE-2026-34073, GHSA-537c-gmf6-5ccf
|
|
# Windows has no IANA tzdata shipped with the OS, so Python's ``zoneinfo``
|
|
# (PEP 615) raises ``ZoneInfoNotFoundError`` for every non-UTC timezone
|
|
# out of the box. ``tzdata`` ships the Olson database as a data package
|
|
# Python resolves automatically. No-op on Linux/macOS (which have
|
|
# /usr/share/zoneinfo). Credits: PR #13182 (@sprmn24).
|
|
"tzdata==2025.3; sys_platform == 'win32' and python_version >= '3.14'",
|
|
# Cross-platform process / PID management. `psutil` is the canonical
|
|
# answer for "is this PID alive" and process-tree walking across Linux,
|
|
# macOS and Windows. It replaces POSIX-only idioms like `os.kill(pid, 0)`
|
|
# (which is a silent killer on Windows — see CONTRIBUTING.md) and
|
|
# `os.killpg` (which doesn't exist on Windows).
|
|
"psutil==7.2.2; sys_platform != 'android' and python_version >= '3.14'",
|
|
# Android Python 3.13+ needs upstream #2891 for platform recognition and
|
|
# disk_partitions(). The fix is not in a published psutil release yet.
|
|
# Keep this commit until a release includes it, then remove the source pin.
|
|
"psutil @ git+https://github.com/giampaolo/psutil.git@380bd2b59c67b0e1b04bbf3a90b11744f4f96644 ; sys_platform == 'android' and python_version >= '3.14'",
|
|
# Browser CDP supervisor + browser_dialog import this directly. Keep core
|
|
# so browser tool discovery doesn't fail on lean installs.
|
|
"websockets==15.0.1; python_version >= '3.14'",
|
|
# .gitignore-aware file matching for desktop build stamp.
|
|
"pathspec==1.1.1; python_version >= '3.14'",
|
|
"fastapi>=0.104.0,<1; python_version >= '3.14'",
|
|
# CIDR-aware forwarded_allow_ips requires uvicorn >=0.31.0.
|
|
# uvicorn's [standard] extra bundles uvloop, httptools, and watchfiles.
|
|
# uvloop cannot build on Android/Termux (libuv's ./configure fails), so
|
|
# we declare the working components inline and gate uvloop behind the
|
|
# [uvloop] optional extra — desktop/server profiles pull it via [all].
|
|
"uvicorn>=0.31.0,<1; python_version >= '3.14'",
|
|
"httptools>=0.6.3,<0.9; python_version >= '3.14'",
|
|
"watchfiles>=0.20,<2; python_version >= '3.14'",
|
|
# Streaming multipart uploads for the dashboard file manager (NS-501).
|
|
# FastAPI's UploadFile/Form depend on python-multipart; it is NOT pulled in
|
|
# by fastapi itself, so the dashboard's multipart upload endpoint would 500
|
|
# without an explicit dependency here (and in the `web` extra below).
|
|
"python-multipart>=0.0.9,<1; python_version >= '3.14'",
|
|
"ptyprocess>=0.7.0,<1; sys_platform != 'win32' and python_version >= '3.14'",
|
|
# Python 3.14 requires pywinpty 3.x. Version 3.0.5 includes the native
|
|
# binaries missing from 3.0.4 wheels on both Windows architectures.
|
|
"pywinpty>=3.0.5,<4; sys_platform == 'win32' and python_version >= '3.14'",
|
|
# Desktop SSH's Windows remote runtime (hermes_cli/windows_ssh_runtime.py)
|
|
# imports win32security/win32file/etc. directly — declare pywin32 rather than
|
|
# relying on the concurrent-log-handler → portalocker transitive chain.
|
|
"pywin32>=306,<312; sys_platform == 'win32' and python_version >= '3.14'",
|
|
# App Installer checks use Package from ApplicationModel. Foundation
|
|
# supplies the URI and async result types projected when the checker runs.
|
|
"winrt-windows-applicationmodel>=3.2.1,<4; sys_platform == 'win32' and python_version >= '3.14'",
|
|
"winrt-windows-foundation>=3.2.1,<4; sys_platform == 'win32' and python_version >= '3.14'",
|
|
# Store update queries and request results project collection interfaces.
|
|
"winrt-windows-foundation-collections>=3.2.1,<4; sys_platform == 'win32' and python_version >= '3.14'",
|
|
"winrt-windows-services-store>=3.2.1,<4; sys_platform == 'win32' and python_version >= '3.14'",
|
|
# Image resize recovery for the vision tools. Pillow shrinks oversized images
|
|
# (>5 MB or >8000px) at embed time; without it the byte AND pixel-dimension
|
|
# shrink paths no-op, so an oversized image bakes into immutable history and
|
|
# bricks the session on Anthropic's non-retryable 400. Pure-wheel, no system
|
|
# libs required for the codecs we use, so it's safe to ship in the base
|
|
# install rather than gating it behind an extra + a mid-session lazy install
|
|
# (which deadlocked the CLI under prompt_toolkit — see #40490).
|
|
"Pillow==12.3.0; python_version >= '3.14'",
|
|
# HEIF/HEIC/AVIF decode for the vision tools. iPhone photos and screenshots
|
|
# are HEIC (frequently mislabeled .jpg by upload pipelines); Pillow has no
|
|
# built-in HEIF codec, so without this the vision resolver rejects them as
|
|
# "not a recognized image". pillow-heif registers a Pillow opener so
|
|
# _normalize_to_supported_image can re-encode HEIF/AVIF to PNG before embed.
|
|
# Ships prebuilt wheels bundling libheif for the common platforms (no system
|
|
# libs needed), so it's safe in the base install alongside Pillow.
|
|
"pillow-heif>=1.4.0,<2; python_version >= '3.14'",
|
|
# SVG rendering for the icon generator (scripts/generate_icons.py). Source
|
|
# builds render icons with the runtime interpreter (`hermes desktop`,
|
|
# `hermes update`), so a source install carries its renderer instead of
|
|
# preparing a separate environment at launch. abi3 wheels for every
|
|
# shipped platform, including Windows ARM64 and Android.
|
|
"resvg-py==0.4.0; python_version >= '3.14'",
|
|
# Windows log rotation. Stdlib ``RotatingFileHandler.doRollover()`` uses
|
|
# ``os.rename()`` which fails with ``PermissionError [WinError 32]`` on
|
|
# Windows whenever any other process holds an append-mode handle on
|
|
# ``agent.log`` (always the case in Hermes — TUI, gateway, ``hy_memory``
|
|
# server, MCP servers, and on-demand CLI commands all log from separate
|
|
# processes), pinning ``agent.log`` at the 5 MiB threshold and spamming
|
|
# stderr on every emit (see #44873). ``concurrent-log-handler`` wraps the
|
|
# rename in a cross-process file lock (via ``portalocker``: pywin32 on
|
|
# Windows) so only one process rotates at a time. ``hermes_logging.py``
|
|
# aliases it ONLY on Windows — POSIX renames an open file fine, so stdlib
|
|
# already works there and managed-mode perms depend on its exact lifecycle.
|
|
# Hence the ``sys_platform == 'win32'`` marker: the dep (and its portalocker
|
|
# / pywin32 tree) ships only where it's actually used.
|
|
"concurrent-log-handler==0.9.29; sys_platform == 'win32' and python_version >= '3.14'",
|
|
# First-party lifecycle and shared-metrics runtime. Relay 0.8 is the supported
|
|
# native runtime and provider-codec baseline. Managed calls pass request and
|
|
# response data through this native module in-process; shared metrics installs
|
|
# no network exporter and consumes only its bounded projection. This marker
|
|
# must stay false anywhere no compatible native wheel tag can match; otherwise
|
|
# installing Python dependencies fails instead of falling back to the no-op
|
|
# Relay host (#76469, Termux).
|
|
# Termux Python reports plain linux/aarch64 but runs on Bionic
|
|
# libc, which satisfies neither manylinux nor musllinux, hence the
|
|
# `'android' not in platform_release` guard on the linux arms: Android GKI
|
|
# kernels embed "-androidNN-" in the kernel release string. (Official PEP
|
|
# 738 CPython reports sys_platform == 'android' and never matched.) Pre-GKI
|
|
# devices can still slip through; they get the same resolution failure as
|
|
# before, worked around by installing with `--no-deps` or an older release.
|
|
"nemo-relay>=0.8.3,<0.9; ((sys_platform == 'darwin' and platform_machine == 'arm64') or (sys_platform == 'linux' and platform_machine == 'x86_64' and 'android' not in platform_release) or (sys_platform == 'linux' and platform_machine == 'aarch64' and 'android' not in platform_release) or (sys_platform == 'win32' and platform_machine == 'AMD64') or (sys_platform == 'win32' and platform_machine == 'ARM64')) and python_version >= '3.14'",]
|
|
|
|
[project.optional-dependencies]
|
|
# High-performance event loop — uvloop replaces the stdlib asyncio loop with
|
|
# a libuv-backed implementation for ~2-4x throughput on uvicorn. Gated as an
|
|
# optional extra because libuv's ./configure does not run on Android/Termux
|
|
# (bionic libc, non-FHS layout). Desktop and server installs pull this via
|
|
# [all]; the [termux] / [termux-all] profiles intentionally omit it.
|
|
uvloop = [
|
|
"uvloop>=0.15.1,<0.24; sys_platform != 'win32' and sys_platform != 'cygwin' and sys_platform != 'android' and platform_python_implementation != 'PyPy'",
|
|
]
|
|
# Native Anthropic provider — only needed when provider=anthropic (not via
|
|
# OpenRouter or other aggregators).
|
|
anthropic = ["anthropic==0.87.0"] # CVE-2026-34450, CVE-2026-34452
|
|
# Web search backends — each only loaded when the user picks it as their
|
|
# search provider (configured via `hermes tools` or config.yaml).
|
|
exa = ["exa-py==2.10.2"]
|
|
firecrawl = ["firecrawl-py==4.17.0"]
|
|
parallel-web = ["parallel-web==0.4.2"]
|
|
ddgs = ["ddgs==9.16.0"]
|
|
# Image generation backends
|
|
fal = ["fal-client==0.13.1"]
|
|
# Edge TTS — default TTS provider but still optional (users can pick
|
|
# ElevenLabs / OpenAI / MiniMax instead).
|
|
edge-tts = ["edge-tts==7.2.7"]
|
|
# NeuTTS's upstream Python ceiling excludes the managed interpreter.
|
|
# Keep dependency markers and PM gates together: bundles omit them, explicit
|
|
# setup requests report unavailable rather than succeeding with an empty set.
|
|
neutts = ["neutts[all]==1.4.1; python_version < '3.14'"]
|
|
kittentts = [
|
|
"kittentts @ https://github.com/KittenML/KittenTTS/releases/download/0.8.1/kittentts-0.8.1-py3-none-any.whl ; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')",
|
|
# KittenTTS requires misaki[en]>=0.9.4, and PyPI's misaki 0.9.4 caps Python
|
|
# below 3.13. The NousResearch fork is the same 0.9.4 code with the cap
|
|
# raised to <3.15. Declaring it here makes KittenTTS's transitive
|
|
# requirement resolve to the fork. Drop this line when upstream misaki
|
|
# publishes a release that allows 3.14.
|
|
"misaki[en] @ git+https://github.com/NousResearch/misaki.git@f03fd2be7346952a83d3d4845c217fc7667f322d ; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')",
|
|
"soundfile==0.14.0; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')",
|
|
]
|
|
# onnxruntime has no Intel macOS wheel. Piper has no Windows ARM64 wheel.
|
|
piper = [
|
|
"piper-tts==1.8.0; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')",
|
|
]
|
|
modal = ["modal==1.3.4"]
|
|
daytona = ["daytona==0.155.0"]
|
|
vercel = ["vercel==0.7.2"]
|
|
google-meet = ["playwright==1.62.0", "websockets==15.0.1"]
|
|
messaging = [
|
|
"python-telegram-bot[webhooks]==22.8",
|
|
"discord.py[voice]==2.7.1",
|
|
"aiohttp==3.14.3",
|
|
"brotlicffi==1.2.0.2",
|
|
"slack-bolt==1.30.0",
|
|
"slack-sdk==3.44.1",
|
|
"qrcode==7.4.2",
|
|
] # aiohttp 3.14.3: prior CVEs + GHSA-cq5v-8q36-5273/GHSA-mfx4-hv73-q22v/GHSA-mq44-7p77-q5h7
|
|
cron = [] # croniter is now a core dependency; this extra kept for back-compat
|
|
slack = ["slack-bolt==1.30.0", "slack-sdk==3.44.1", "aiohttp==3.14.3"]
|
|
# Matrix gateway deps. `mautrix[encryption]` pulls `python-olm`, whose
|
|
# vendored libolm (archived 2023) cannot build on Windows or modern macOS:
|
|
# no wheels for those targets AND the sdist fails to compile on current
|
|
# clang/MSVC (olm/list.hh const-iterator error) and on CMake >= 4. It is
|
|
# linux-only — gate the whole extra so `uv sync --all-extras` (the bundle
|
|
# build) doesn't try to build python-olm on the win/darwin lanes. The
|
|
# matrix adapter degrades gracefully without mautrix (import stubs).
|
|
matrix = [
|
|
"mautrix[encryption]==0.21.1; sys_platform == 'linux'",
|
|
"aiosqlite==0.22.1; sys_platform == 'linux'",
|
|
"asyncpg==0.31.0; sys_platform == 'linux'",
|
|
"aiohttp-socks==0.11.0; sys_platform == 'linux'",
|
|
"aiohttp==3.14.3; sys_platform == 'linux'",
|
|
] # aiohttp 3.14.3: prior CVEs + GHSA-cq5v-8q36-5273/GHSA-mfx4-hv73-q22v/GHSA-mq44-7p77-q5h7 (mautrix/aiohttp-socks only cap aiohttp<4 / >=3.10, so pin the patched floor directly)
|
|
|
|
# WeCom callback-mode adapter — parses untrusted XML POST bodies from
|
|
# WeCom-controlled callback endpoints, so we use defusedxml (drop-in
|
|
# replacement for stdlib xml.etree.ElementTree) to block billion-laughs
|
|
# and XXE. aiohttp/httpx are already in [messaging]; defusedxml lands
|
|
# here to keep the dependency local to wecom_callback's threat model.
|
|
wecom = ["defusedxml==0.7.1"]
|
|
tts-premium = ["elevenlabs==1.59.0"]
|
|
voice = [
|
|
# Local STT pulls in wheel-only transitive deps (ctranslate2, onnxruntime).
|
|
# faster-whisper's closure is prebuilt-only and missing pieces on two
|
|
# targets: ctranslate2 has no win_arm64 wheel/sdist (win32-arm64), and
|
|
# onnxruntime has no darwin-x64 wheel/sdist (darwin-x64). Gate the local
|
|
# STT path off both; sounddevice/numpy still ship.
|
|
"faster-whisper==1.2.1; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or platform_system != 'Darwin')",
|
|
"sounddevice==0.5.5",
|
|
"numpy==2.4.3",
|
|
]
|
|
# Wake engines are optional. Bundles include the engines supported by their
|
|
# target. PM installs the selected engine on first use for source installs.
|
|
wake = [
|
|
# pyopen-wakeword includes the TFLite library and shared feature models.
|
|
# Its universal2 wheel contains an ARM64-only library, so Intel Macs cannot
|
|
# load it. There is no native Windows ARM64 wheel either.
|
|
"pyopen-wakeword==1.1.0; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')",
|
|
"sherpa-onnx==1.13.8",
|
|
# uv reads sherpa-onnx metadata from its cp311-linux_armv7l wheel, which
|
|
# omits this pin. Every wheel the bundles install requires it. Without the
|
|
# pin a frozen sync installs sherpa-onnx alone, and desktop dependency
|
|
# preparation fails on `uv pip check`.
|
|
"sherpa-onnx-core==1.13.8",
|
|
# text2token imports both tokenizers even for English phrases.
|
|
"sentencepiece==0.2.2",
|
|
"pypinyin==0.55.0",
|
|
"pvporcupine==4.0.3",
|
|
"sounddevice==0.5.5",
|
|
"numpy==2.4.3",
|
|
]
|
|
honcho = ["honcho-ai==2.2.0"]
|
|
# Platform sub-extras / alias extras — declared so pm.extras.ANCHORS names
|
|
# only real extras (contract: tests/pm/test_extras.py). Each is
|
|
# lazy-installed at first use via pm.ensure_import(name) and therefore
|
|
# deliberately NOT in [all] (see tests/test_project_metadata.py's
|
|
# lazy_covered_extras list — keep that list in sync when adding here).
|
|
telegram = ["python-telegram-bot[webhooks]==22.8"]
|
|
discord = ["discord.py[voice]==2.7.1"]
|
|
# stt-whisper + audio-io compose [voice] (faster-whisper / sounddevice / numpy).
|
|
stt-whisper = [
|
|
"faster-whisper==1.2.1; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or platform_system != 'Darwin')",
|
|
]
|
|
audio-io = ["sounddevice==0.5.5", "numpy==2.4.3"]
|
|
# WeChat voice decode (tools/transcription_tools.py self-heal path).
|
|
silk = ["pilk==0.2.4"]
|
|
# Wake-engine sub-extras — lazy per-engine installs so a user who only wants
|
|
# sherpa-onnx doesn't pull the whole [wake] bundle. Pins match [wake].
|
|
wake-openwakeword = [
|
|
"pyopen-wakeword==1.1.0; (platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')",
|
|
]
|
|
wake-sherpa = [
|
|
"sherpa-onnx==1.13.8",
|
|
# Required by the sherpa-onnx wheel. See [wake] for why it is explicit.
|
|
"sherpa-onnx-core==1.13.8",
|
|
"sentencepiece==0.2.2",
|
|
"pypinyin==0.55.0",
|
|
]
|
|
wake-porcupine = ["pvporcupine==4.0.3"]
|
|
# Google Chat service account push (gateway/platforms/google_chat) — pubsub
|
|
# subscriber for the Chat API's Cloud Pub/Sub delivery.
|
|
# grpcio (pubsub's native transitive) has no win_arm64 wheel, so win32-arm64
|
|
# would build its sdist and fail — gate the whole extra off there like
|
|
# faster-whisper. The adapter's pubsub import is lazy, so this just makes the
|
|
# google-chat platform unavailable on win32-arm64.
|
|
google-chat = [
|
|
"google-cloud-pubsub==2.39.2; platform_machine != 'ARM64' or sys_platform != 'win32'",
|
|
]
|
|
# Document extraction (read_file anydoc converter self-heal) — core already
|
|
# bundles firecrawl-anydoc==0.2.4; the extra exists so a lean/broken install
|
|
# can re-sync exactly the pin from core.
|
|
doc-extract = ["firecrawl-anydoc==0.2.4"]
|
|
trace-upload = ["huggingface-hub==1.24.0"]
|
|
# Cloud memory providers are opt-in PM extras. They stay outside [all],
|
|
# like honcho, so a quarantined release cannot break fresh installs.
|
|
supermemory = ["supermemory==3.50.0"]
|
|
# mem0ai pulls qdrant-client → grpcio, which has no win_arm64 wheel — same
|
|
# win32-arm64 gate as google-chat. mem0 is lazy-installed, so on arm64 the
|
|
# provider is simply unavailable.
|
|
mem0 = [
|
|
"mem0ai==2.0.10; platform_machine != 'ARM64' or sys_platform != 'win32'",
|
|
]
|
|
# Image resize recovery for the vision tools. Pillow is now a CORE dependency
|
|
# (see the main `dependencies` list above) since the byte/pixel shrink paths are on
|
|
# the default vision-embed path and the mid-session lazy install deadlocked the
|
|
# CLI under prompt_toolkit (#40490). This extra is kept as a no-op back-compat
|
|
# alias so existing requests for the `vision` extra resolve.
|
|
vision = []
|
|
# Kept as a no-op back-compat alias — `ptyprocess` and `pywinpty` are now
|
|
# in the main `dependencies` list (with the same platform markers), so
|
|
# any existing requests for the `pty` extra resolve cleanly
|
|
# without pulling in extra packages.
|
|
pty = []
|
|
# CVE-2026-48710 (BadHost): Starlette is pulled transitively by mcp's
|
|
# sse-starlette / HTTP-SSE stack (and by fastapi in the `web` extra). Before
|
|
# 1.0.1, a malformed Host header makes `request.url.path` desync from the path
|
|
# the ASGI router actually dispatched, so middleware/endpoints that gate on
|
|
# `request.url` can be bypassed. We pin a patched Starlette directly in every
|
|
# extra that exposes a Starlette-backed server surface so pip/uv can't resolve
|
|
# a vulnerable pre-1.0.1 transitive. Bump in lockstep with uv.lock.
|
|
#
|
|
# mcp 2.0.0 implements MCP revision 2026-07-28 and moved its own HTTP stack
|
|
# from `httpx` to `httpx2`. httpx2 arrives transitively, but tools/mcp_tool.py
|
|
# and tools/mcp_oauth_manager.py import it by name to build the client objects
|
|
# they hand to the SDK, so it is pinned here explicitly rather than left to
|
|
# resolution. Hermes' own `httpx[socks]==0.28.1` in [dependencies] is
|
|
# unaffected — the two distributions install side by side under different
|
|
# module names.
|
|
mcp = [
|
|
"mcp==2.0.0",
|
|
"httpx2==2.7.0",
|
|
"starlette==1.3.1",
|
|
] # starlette: CVE-2026-48710
|
|
# Backwards-compatible no-op alias. Relay is a core dependency on supported
|
|
# wheel targets and intentionally unavailable on other platforms.
|
|
nemo-relay = []
|
|
homeassistant = ["aiohttp==3.14.3"]
|
|
sms = ["aiohttp==3.14.3"]
|
|
teams = [
|
|
"microsoft-teams-apps==2.0.13.4",
|
|
"aiohttp==3.14.3",
|
|
] # aiohttp 3.14.3: prior CVEs + GHSA-cq5v-8q36-5273/GHSA-mfx4-hv73-q22v/GHSA-mq44-7p77-q5h7
|
|
# Computer use talks to PM's pinned cua-driver over MCP stdio. This extra
|
|
# provides the MCP client, independently of the native driver package.
|
|
computer-use = [
|
|
"mcp==2.0.0",
|
|
"httpx2==2.7.0",
|
|
"starlette==1.3.1",
|
|
] # starlette: CVE-2026-48710
|
|
acp = ["agent-client-protocol==0.9.0"]
|
|
# mistral: Voxtral STT + TTS. Pinned to an exact verified-clean version.
|
|
# The `mistralai` PyPI project was quarantined 2026-05-12 after the malicious
|
|
# 2.4.6 release (Mini Shai-Hulud worm); 2.4.6 was removed from PyPI and the
|
|
# project is serving clean releases again (2.4.7 2026-05-25, 2.4.8 2026-05-28).
|
|
# Like other opt-in TTS/STT backends, this is lazy-installed via
|
|
# pm (stt.mistral / tts.mistral) at first use — deliberately
|
|
# NOT re-added to [all] so a future quarantined release can't break fresh
|
|
# installs (see [all] policy comment below).
|
|
mistral = ["mistralai==2.4.8"]
|
|
# OTLP gateway monitoring export (optional). Provides the OpenTelemetry SDK +
|
|
# OTLP/HTTP exporter for monitoring.gateway_health_export. Lazy-installed via
|
|
# PM on first use; never a core dependency and deliberately
|
|
# NOT in [all].
|
|
otlp = [
|
|
"opentelemetry-sdk==1.39.1",
|
|
"opentelemetry-exporter-otlp-proto-http==1.39.1",
|
|
]
|
|
langfuse = ["langfuse==4.15.2"]
|
|
bedrock = ["boto3==1.42.89"]
|
|
vertex = ["google-auth==2.55.1"]
|
|
azure-identity = ["azure-identity==1.25.3"]
|
|
termux = [
|
|
# Baseline Android / Termux path for reliable fresh installs.
|
|
"python-telegram-bot[webhooks]==22.8",
|
|
"hermes-agent[cron]",
|
|
"hermes-agent[mcp]",
|
|
"hermes-agent[honcho]",
|
|
"hermes-agent[acp]",
|
|
]
|
|
termux-all = [
|
|
# Best-effort "install all" profile for Termux. Same policy as [all]:
|
|
# only includes extras that are not prepared on demand by PM.
|
|
# Backends like telegram/slack/dingtalk/feishu/honcho lazy-install at
|
|
# first use, so they're no longer eager-installed here.
|
|
"hermes-agent[termux]",
|
|
"hermes-agent[google]",
|
|
"hermes-agent[homeassistant]",
|
|
"hermes-agent[sms]",
|
|
"hermes-agent[web]",
|
|
"hermes-agent[pty]",
|
|
]
|
|
dingtalk = [
|
|
"dingtalk-stream==0.24.3",
|
|
"alibabacloud-dingtalk==2.2.42",
|
|
"qrcode==7.4.2",
|
|
]
|
|
feishu = ["lark-oapi==1.6.8", "qrcode==7.4.2"]
|
|
google = [
|
|
# Required by the google-workspace skill (Gmail, Calendar, Drive, Contacts,
|
|
# Sheets, Docs). Declared here so PM environments and independent
|
|
# packagers can include them without mutating an application's runtime.
|
|
"google-api-python-client==2.194.0",
|
|
"google-auth==2.55.1",
|
|
"google-auth-oauthlib==1.3.1",
|
|
"google-auth-httplib2==0.3.1",
|
|
# The Google SDKs permit older vulnerable transitives, so unlocked installs
|
|
# must carry the same fixed floors as uv.lock and the runtime installers:
|
|
# httplib2 0.32.0 (GHSA-j5g9-f88f-gfj3 decompression bomb DoS),
|
|
# pyasn1 0.6.4, google-auth 2.55.1.
|
|
"httplib2==0.32.0",
|
|
"pyasn1==0.6.4",
|
|
]
|
|
youtube = [
|
|
# Required by skills/media/youtube-content and
|
|
# optional-skills/productivity/memento-flashcards (youtube_quiz.py).
|
|
# Without this declaration dependency sync omits the package and both skills fail
|
|
# at first invocation with ModuleNotFoundError (issue #22243).
|
|
"youtube-transcript-api==1.2.4",
|
|
]
|
|
# `hermes dashboard` (localhost SPA + API). Not in core to keep the default install lean.
|
|
# starlette==1.3.1 pinned for CVE-2026-48710 (BadHost) — fastapi pulls Starlette
|
|
# transitively and pre-1.0.1 is the vulnerable range. See the mcp extra above.
|
|
# uvicorn pin without [standard] — httptools / watchfiles are now core deps,
|
|
# and uvloop is gated behind the [uvloop] extra. This keeps [web] safe for
|
|
# termux-all which includes it.
|
|
web = [
|
|
"fastapi==0.133.1",
|
|
"uvicorn==0.41.0",
|
|
"starlette==1.3.1",
|
|
"python-multipart==0.0.32",
|
|
]
|
|
all = [
|
|
# Policy (2026-05-12): `[all]` includes only extras that genuinely
|
|
# CAN'T be prepared on demand by PM — i.e. things every
|
|
# session can use, things needed before the agent loop is alive
|
|
# (terminal/CLI), and skill deps that dev environments need.
|
|
# Anything an opt-in backend (provider, search, TTS, image, memory,
|
|
# messaging platform, terminal sandbox) needs MUST live exclusively in
|
|
# opt-in extras and resolve at first use — otherwise one quarantined PyPI
|
|
# release breaks every fresh install.
|
|
#
|
|
# Removed from [all] on 2026-05-12 (covered by lazy-install):
|
|
# anthropic, exa, firecrawl, parallel-web, fal, edge-tts,
|
|
# modal, daytona, vercel, messaging (telegram/discord/slack),
|
|
# matrix, slack, honcho, voice (faster-whisper),
|
|
# dingtalk, feishu, bedrock, tts-premium (elevenlabs)
|
|
#
|
|
# Why: the matrix extra in particular pulls `mautrix[encryption]`
|
|
# which depends on `python-olm`. python-olm has Linux-only wheels and
|
|
# no native build path on Windows or modern macOS. With matrix in
|
|
# [all], `uv sync --locked` on Windows tried to build it from sdist
|
|
# and failed on `make`. Lazy-install routes that build to first use,
|
|
# where the user is expected to have a toolchain available.
|
|
"hermes-agent[cron]",
|
|
"hermes-agent[pty]",
|
|
"hermes-agent[mcp]",
|
|
"hermes-agent[uvloop]",
|
|
"hermes-agent[homeassistant]",
|
|
"hermes-agent[sms]",
|
|
"hermes-agent[acp]",
|
|
"hermes-agent[google]",
|
|
"hermes-agent[web]",
|
|
"hermes-agent[youtube]",
|
|
]
|
|
|
|
[dependency-groups]
|
|
# Build and test tooling never ships in runtime payloads.
|
|
dev = [
|
|
"debugpy==1.8.20",
|
|
"pytest==9.1.1",
|
|
"pytest-asyncio==1.3.0",
|
|
"mcp==2.0.0",
|
|
"httpx2==2.7.0",
|
|
"starlette==1.3.1",
|
|
"ty==0.0.82",
|
|
"ruff==0.15.10",
|
|
"setuptools==83.0.0",
|
|
] # starlette: CVE-2026-48710; setuptools: 83 (torch >=2.13 requires setuptools 83)
|
|
# Native launcher and provider-catalog E2E dependencies belong in the test
|
|
# environment, not the core runtime. Keep the Anthropic SDK at the same
|
|
# reviewed version as the optional runtime extra above.
|
|
test = [
|
|
"distlib==0.4.3; sys_platform == 'win32'",
|
|
"anthropic==0.87.0",
|
|
]
|
|
|
|
[build-system]
|
|
# setuptools.build_meta + our setup.py bdist_wheel guard import wheel during
|
|
# PEP 517 isolated builds. Without wheel in
|
|
# requires, the isolation sandbox only gets setuptools and the build
|
|
# fails with ModuleNotFoundError: wheel.cli (#96488).
|
|
requires = ["setuptools==83.0.0", "wheel"]
|
|
build-backend = "setuptools.build_meta"
|
|
|
|
[project.scripts]
|
|
hermes = "hermes_cli.main:main"
|
|
hermes-agent = "agent.legacy_cli:main"
|
|
hermes-acp = "acp_adapter.entry:main"
|
|
|
|
[tool.hermes]
|
|
# Extras that only an explicit selection installs (setup, sync_venv). Bundles
|
|
# build with --all-extras and pass --no-extra for each of these, so their
|
|
# closures never ship. KittenTTS pulls torch, transformers and spaCy through
|
|
# misaki[en]: installed when the user picks it, not preinstalled.
|
|
opt-in-extras = ["kittentts"]
|
|
|
|
# Per-extra platform gates (settled 2026-09-02): an extra listed here is
|
|
# only installable on platforms matching the marker. pm consults this
|
|
# table (pm.extras.extra_supported) — ensure_import/sync_venv refuse a
|
|
# gated-off extra with a clear message instead of resolving it to an
|
|
# empty set. The per-package markers inside the extra stay (uv's
|
|
# resolver needs them for --all-extras); this table is the readable
|
|
# single-authority for WHICH extra is supported WHERE.
|
|
|
|
[tool.hermes.extras-platforms]
|
|
# mautrix[encryption] → python-olm: linux-only wheels, no win/darwin build.
|
|
matrix = "sys_platform == 'linux'"
|
|
# grpcio (via google-cloud-pubsub) has no win_arm64 wheel.
|
|
google-chat = "platform_machine != 'ARM64' or sys_platform != 'win32'"
|
|
# qdrant-client → grpcio (via mem0ai) has no win_arm64 wheel.
|
|
mem0 = "platform_machine != 'ARM64' or sys_platform != 'win32'"
|
|
# Match the engine dependency markers so PM refuses an unsupported install.
|
|
wake-openwakeword = "(platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')"
|
|
# faster-whisper's closure is prebuilt-only: ctranslate2 has no win_arm64 wheel and onnxruntime
|
|
# none for darwin-x64. Its ANCHOR is faster_whisper, so an unsupported machine could install the
|
|
# extra, rebuild the whole dependency environment and still fail the anchor on every retry.
|
|
# Gate the extra that carries only faster-whisper; [voice] also has ungated deps (sounddevice,
|
|
# numpy) and stays installable, see test_declared_extra_gates_match_dependency_selection.
|
|
stt-whisper = "(platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or platform_system != 'Darwin')"
|
|
# NeuTTS excludes Python 3.14.
|
|
neutts = "python_version < '3.14'"
|
|
# KittenTTS: onnxruntime has no Intel macOS wheel; misaki[en]'s torch has no
|
|
# Windows ARM64 wheel.
|
|
kittentts = "(platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')"
|
|
piper = "(platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')"
|
|
|
|
[tool.uv]
|
|
# uv otherwise enables the dev group by default, including in payload builds.
|
|
default-groups = []
|
|
# 3.11 is an install bridge for pre-PM updaters, never a runtime, so the lock
|
|
# covers 3.14 only. Without this the lock must resolve every supported version,
|
|
# where extras that only ever coexist on 3.14 (kittentts vs neutts) conflict.
|
|
environments = ["python_version >= '3.14'"]
|
|
override-dependencies = [
|
|
# discord.py's latest published version, 2.7.1, pins pynacl at <1.6. however, pynacl 1.5.0 has known vulnerabilities.
|
|
# discord.py has updated pynacl to 1.6 on `main`, but has not yet published a patch release.
|
|
# so, we force-override this to pynacl 1.6. here.
|
|
# remove this when we update discord.py
|
|
"pynacl>=1.6,<1.7",
|
|
# alibabacloud-tea-openapi caps cryptography<49, and its latest release
|
|
# still does. Without this override, that cap holds cryptography at 48.0.1
|
|
# and its three advisories (see the cryptography pin in
|
|
# [project].dependencies). The package uses cryptography only to sign
|
|
# requests with RSA and AES, and that API did not break in 49 or 50.
|
|
# Keep this exact version equal to the direct dependency. uv overrides
|
|
# replace that requirement too, not only the vendor's upper bound.
|
|
# Remove this line when alibabacloud-tea-openapi lifts the cap.
|
|
"cryptography==50.0.1",
|
|
# misaki[en] (pulled by [kittentts]) lists spacy-curated-transformers.
|
|
# The newest release that fits spacy 3.8 (2.x needs thinc 9), 0.3.1, caps
|
|
# Python below 3.14, so PM's `uv pip check` rejects the venv. Nothing
|
|
# imports it: only transformer spaCy models (trf=True) do, and KittenTTS
|
|
# never builds a misaki G2P. The never-true marker removes it from the lock.
|
|
# Remove this line when misaki drops it or a 3.14-capable release fits.
|
|
"spacy-curated-transformers; sys_platform == 'never'",
|
|
]
|
|
exclude-newer = "14 days"
|
|
# h2: temporary exclude-newer exception for the CVE-2026-71554 (GHSA-6hr6-w5qg-qmwg,
|
|
# request-smuggling) fix in 4.4.1, published 2026-08-03. Remove after 2026-08-17.
|
|
# aiohttp, cryptography: same shape — the advisory fixes are newer than the
|
|
# 14-day window, so the resolver cannot see them without an exception.
|
|
#
|
|
# defusedxml, python-olm, unpaddedbase64: the OPPOSITE shape (#80387, #79434,
|
|
# #78227 family). These are ancient, effectively frozen releases (2021-2023)
|
|
# whose upload dates are frequently absent from mirror indexes and stale uv
|
|
# HTTP caches. uv treats a missing upload date as "newer than the cutoff" and
|
|
# excludes the package, bricking [youtube]/[wecom]/[matrix] resolution
|
|
# ("there are no versions of defusedxml"). Exempting them carries zero aging
|
|
# risk — their newest releases are years old — and unbricks resolvers that
|
|
# cannot see upload dates.
|
|
#
|
|
# setuptools, wheel, pillow, mcp: build/core pin bricks (#78227, #75992, #76020, #96488). uv
|
|
# applies exclude-newer to build-system.requires and core deps too; when a
|
|
# resolver cannot see an upload date (old uv, mirror index, stale HTTP cache)
|
|
# it filters the pinned version and the package cannot even BUILD
|
|
# ("No solution found when resolving: setuptools==83.0.0"). These deps are
|
|
# exact-pinned (==X.Y.Z), so exclude-newer adds zero float protection for
|
|
# them — the version cannot move without a reviewed pin bump — while the
|
|
# cutoff can still brick installs. Exempting exact pins is pure brick-risk
|
|
# removal at no supply-chain cost. Guarded by
|
|
# tests/test_packaging_metadata.py::test_build_system_requires_exempt_from_exclude_newer.
|
|
#
|
|
# firecrawl-anydoc: same exact-pin shape (==0.2.4, hosted-OCR wiring PR).
|
|
# The pin bump WAS the review; exclude-newer adds zero float protection to
|
|
# an exact pin and would only delay the reviewed version 14 days.
|
|
#
|
|
# Every other exact-pinned package below: the release-day brick shape
|
|
# (observed 2026-08-29 updating three long-running installs v0.20.0 ->
|
|
# v0.20.6 — one Termux, two Linux servers). Each release exact-pins at least
|
|
# one dependency to a version published days before the release (v0.20.6
|
|
# pinned snowballstemmer==3.1.1 and psutil==7.2.2 in core, plus fastapi /
|
|
# uvicorn / python-telegram-bot pins across extras). For two weeks after
|
|
# release the cutoff filters those versions out, so any venv that predates
|
|
# the release bricks on `hermes update` ("no version of
|
|
# snowballstemmer==3.1.1") until the window passes. Same zero-float-
|
|
# protection logic as setuptools/pillow/mcp above; enforced for every exact
|
|
# pin by
|
|
# tests/test_packaging_metadata.py::test_exact_pinned_deps_exempt_from_exclude_newer.
|
|
#
|
|
# maturin, setuptools-rust: build-system.requires of the cryptography pin
|
|
# above. cryptography itself is exempted, but on wheel-less platforms
|
|
# (Termux/Android) it must build from sdist, and the isolated build
|
|
# environment resolves under the same cutoff ("Failed to resolve
|
|
# requirements from build-system.requires ... maturin>=1.9,<2").
|
|
#
|
|
# google-cloud-pubsub: same exact-pin shape (==2.39.2, google-chat adapter).
|
|
# The pin bump WAS the review; exclude-newer only filters it because the
|
|
# 14-day window is younger than the reviewed release.
|
|
|
|
[tool.uv.exclude-newer-package]
|
|
agent-client-protocol = false
|
|
aiohttp = false
|
|
aiohttp-socks = false
|
|
aiosqlite = false
|
|
alibabacloud-dingtalk = false
|
|
anthropic = false
|
|
asyncpg = false
|
|
azure-identity = false
|
|
boto3 = false
|
|
brotlicffi = false
|
|
certifi = false
|
|
concurrent-log-handler = false
|
|
croniter = false
|
|
cryptography = false
|
|
daytona = false
|
|
ddgs = "2026-08-26T21:52:34Z"
|
|
debugpy = false
|
|
defusedxml = false
|
|
dingtalk-stream = false
|
|
discord-py = false
|
|
edge-tts = false
|
|
elevenlabs = false
|
|
exa-py = false
|
|
fal-client = false
|
|
fastapi = false
|
|
faster-whisper = false
|
|
fire = false
|
|
firecrawl-anydoc = false
|
|
firecrawl-py = false
|
|
google-api-python-client = false
|
|
google-auth = false
|
|
google-auth-httplib2 = false
|
|
google-auth-oauthlib = false
|
|
google-cloud-pubsub = false
|
|
h2 = false
|
|
honcho-ai = false
|
|
httplib2 = false
|
|
httpx = false
|
|
httpx2 = false
|
|
huggingface_hub = false
|
|
jinja2 = false
|
|
langfuse = "2026-09-09T16:01:26Z"
|
|
lark-oapi = false
|
|
markdown = false
|
|
maturin = false
|
|
mautrix = false
|
|
mcp = false
|
|
mem0ai = false
|
|
microsoft-teams-apps = false
|
|
mistralai = false
|
|
modal = false
|
|
nemo-relay = false
|
|
neutts = "2026-07-22T14:54:23Z"
|
|
numpy = false
|
|
openai = false
|
|
opentelemetry-exporter-otlp-proto-http = false
|
|
opentelemetry-sdk = false
|
|
packaging = false
|
|
parallel-web = false
|
|
pathspec = false
|
|
pillow = false
|
|
piper-tts = "2026-09-04T16:47:32Z"
|
|
prompt-toolkit = false
|
|
psutil = false
|
|
pvporcupine = false
|
|
pyasn1 = false
|
|
pydantic = false
|
|
pyjwt = false
|
|
pytest = false
|
|
pytest-asyncio = false
|
|
python-dotenv = false
|
|
python-multipart = false
|
|
python-olm = false
|
|
python-telegram-bot = false
|
|
pyyaml = false
|
|
qrcode = false
|
|
requests = false
|
|
rich = false
|
|
ruamel-yaml = false
|
|
ruff = false
|
|
sentencepiece = false
|
|
setuptools = false
|
|
setuptools-rust = false
|
|
wheel = false
|
|
sherpa-onnx = "2026-09-10T17:00:04Z"
|
|
# The core is exact-pinned by sherpa-onnx. Admit all wheels of that release.
|
|
sherpa-onnx-core = "2026-09-10T15:58:47Z"
|
|
slack-bolt = false
|
|
slack-sdk = false
|
|
snowballstemmer = false
|
|
sounddevice = false
|
|
soundfile = "2026-06-06T08:58:48Z"
|
|
starlette = false
|
|
supermemory = false
|
|
tenacity = false
|
|
ty = false
|
|
tzdata = false
|
|
unpaddedbase64 = false
|
|
uvicorn = false
|
|
vercel = false
|
|
websockets = false
|
|
youtube-transcript-api = false
|
|
|
|
[tool.setuptools]
|
|
# Root single-file modules are derived by setup.py at build time from the
|
|
# source tree (see the ``_root_py_modules`` helper there). A static
|
|
# ``py-modules`` list here drifted from the tree each time the layout changed,
|
|
# and the drift broke installed wheels. Do not add the list back.
|
|
|
|
[tool.setuptools.packages.find]
|
|
include = [
|
|
"agent",
|
|
"agent.*",
|
|
"tools",
|
|
"tools.*",
|
|
"hermes_cli",
|
|
"hermes_cli.*",
|
|
"gateway",
|
|
"gateway.*",
|
|
"tui_gateway",
|
|
"tui_gateway.*",
|
|
"cron",
|
|
"cron.*",
|
|
"acp_adapter",
|
|
"plugins",
|
|
"plugins.*",
|
|
"providers",
|
|
"providers.*",
|
|
"hermes_platform",
|
|
"hermes_platform.*",
|
|
"pm",
|
|
"pm.*",
|
|
"tools",
|
|
"tools.*",
|
|
]
|
|
|
|
[tool.setuptools.package-data]
|
|
hermes_cli = [
|
|
"observability/schemas/*.json",
|
|
"data/*.json",
|
|
"local_runtime/*.json",
|
|
]
|
|
# gateway/assets/ ships status_phrases.yaml and the Telegram BotFather
|
|
# screenshot. Without this, sealed venvs (uv2nix) silently lose both —
|
|
# status phrases fall back to the tiny hardcoded set and the Telegram
|
|
# topic-setup image disappears. Loaded via Path(__file__).parent / "assets"
|
|
# in gateway/status_phrases.py and gateway/run.py.
|
|
gateway = ["assets/**/*"]
|
|
# Bundled plugin discovery reads these manifests at runtime. Keep them in
|
|
# sealed wheels with the plugin Python modules; without this declaration the
|
|
# wheel contains adapters but discovery finds zero bundled plugins.
|
|
plugins = ["**/plugin.yaml", "**/plugin.yml"]
|
|
# pm/lock.json is the runtime pin table (uv/python/tool versions + sha256s).
|
|
# Without it a sealed wheel installs the pm package but has no pins to
|
|
# realize — the pm store would be empty.
|
|
pm = ["lock.json", "artifact-mirror.json", "pyproject.toml", "uv.lock"]
|
|
# Bot Desktop starts Xvnc + Xfce through a shell launcher and seeds the wallpaper; both are read
|
|
# via Path(__file__).parent in tools/bot_desktop/runtime.py and vanish from sealed wheels otherwise.
|
|
tools = ["bot_desktop/launcher.sh", "bot_desktop/wallpaper.png"]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
markers = [
|
|
"integration: marks tests requiring external services (API keys, Modal, etc.)",
|
|
"live: secrets-gated canaries against REAL LLM provider APIs (spends money; select with -m live)",
|
|
"real_concurrent_gate: opt out of the autouse stub that disables _detect_concurrent_hermes_instances",
|
|
"real_agent_prewarm: opt out of the autouse stub that disables the tui_gateway deferred agent pre-warm timer",
|
|
"real_retry_backoff: opt out of the tests/agent autouse stub that zeroes jittered_backoff",
|
|
"requires_wal: needs the runtime to actually enable SQLite WAL mode (skipped where Hermes falls back to journal_mode=DELETE)",
|
|
"no_isolate: opt out of per-file subprocess isolation (tests share mutable module-level state)",
|
|
"ssh: marks tests requiring a reachable SSH server (skipped in normal CI)",
|
|
"platforms(*specs, arch=None, arch_negate=False): run only on hosts matching at least one spec — linux/macos/windows/posix/any, 'not X' negation, optional arch filter",
|
|
]
|
|
# integration tests take way too long to run in the normal CI environments
|
|
addopts = "-m 'not integration and not live'"
|
|
|
|
[tool.ty.environment]
|
|
python-version = "3.13"
|
|
|
|
[tool.ty.rules]
|
|
unknown-argument = "warn"
|
|
redundant-cast = "ignore"
|
|
|
|
[tool.ruff]
|
|
preview = true # required for PLW1514 (unspecified-encoding) — preview rule
|
|
|
|
[tool.ruff.lint]
|
|
# All other lints are intentionally disabled (see comment history on this
|
|
# file) while we wrangle typechecks — but PLW1514 is too load-bearing to
|
|
# keep off. Bare open()/read_text()/write_text() in text mode defaults to
|
|
# the system locale encoding on Windows (cp1252 on US-locale installs),
|
|
# which silently corrupts any non-ASCII file content. We had three
|
|
# separate Windows sandbox regressions in one debug session before
|
|
# adding the explicit encoding. This rule keeps new code honest.
|
|
#
|
|
# ASYNC210/220/221/251: blocking calls inside `async def` freeze the whole
|
|
# gateway/uvicorn event loop — every adapter, timer, and health check stops
|
|
# until the call returns. Real incidents: a 17-minute getaddrinfo hang took
|
|
# the backend down (#91912); `time.sleep` in start_gateway froze restarts
|
|
# for 10s (#36163). The fix pattern is `await asyncio.to_thread(...)` (or
|
|
# `loop.run_in_executor`), `asyncio.create_subprocess_exec`, and
|
|
# `await asyncio.sleep`. These four are the direct freeze vectors:
|
|
# ASYNC210 — blocking HTTP call in async fn (urllib/requests/httpx-sync)
|
|
# ASYNC220 — subprocess.Popen in async fn
|
|
# ASYNC221 — subprocess.run / os.system in async fn
|
|
# ASYNC251 — time.sleep in async fn
|
|
# ASYNC230 (blocking open()) and ASYNC240 (blocking path methods) are real
|
|
# but ~180 legacy sites deep; they graduate to this list once the backlog
|
|
# is burned down (see per-file-ignores below for the frozen baseline).
|
|
select = ["PLW1514", "ASYNC210", "ASYNC220", "ASYNC221", "ASYNC251", "TID251"]
|
|
|
|
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
"pm.uv".msg = "Use PM's semantic Python operations, not an installer executable."
|
|
"pm.client.uv".msg = "Use PM's semantic Python operations."
|
|
"pm.install.uv".msg = "The raw uv bridge was removed; use PM operations."
|
|
"pm._uv".msg = "Tool acquisition is private to PM."
|
|
"pm.environment".msg = "The environment engine is private; use PM operations."
|
|
"pm.runtime_stage".msg = "PM stages its own worker runtime; consumers call pm operations."
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"pm/**" = ["TID251"]
|
|
# Desktop E2E fixtures stand in for PM's worker/runtime and reach into it like tests do.
|
|
"apps/desktop/electron/fixtures/**" = ["TID251"]
|
|
# Tests can intentionally exercise locale-encoding edge cases.
|
|
"tests/**" = [
|
|
"PLW1514",
|
|
"ASYNC210",
|
|
"ASYNC220",
|
|
"ASYNC221",
|
|
"ASYNC251",
|
|
"TID251",
|
|
]
|
|
# Skills and plugins are partially user-authored — their own conventions.
|
|
"skills/**" = ["PLW1514"]
|
|
"optional-skills/**" = ["PLW1514"]
|
|
# Plugin platform adapters run ON the gateway event loop — the ASYNC gate
|
|
# applies to them with full force; only PLW1514 stays relaxed.
|
|
"plugins/**" = ["PLW1514"]
|
|
# ---------------------------------------------------------------------------
|
|
# ASYNC ratchet baseline — legacy blocking sites that predate the gate.
|
|
# Each entry is an EXISTING violation being fixed in its own PR; do NOT add
|
|
# new files here. Remove the entry when the file's sites are fixed.
|
|
# ---------------------------------------------------------------------------
|
|
# Detached restart watchers: Popen of a fully-detached, fire-and-forget
|
|
# process (no wait), an accepted momentary spawn cost pending a dedicated
|
|
# async-subprocess sweep.
|
|
"gateway/run.py" = ["ASYNC220"]
|
|
"gateway/run_shutdown.py" = ["ASYNC220"]
|
|
"gateway/slash_commands.py" = ["ASYNC220"]
|
|
# Off-loop sweep for these routers is in flight (PR #84376).
|
|
"hermes_cli/web_routers/profiles.py" = ["ASYNC220", "ASYNC221"]
|
|
# Legacy blocking spawn sites in platform adapters, pending their own fixes.
|
|
"plugins/platforms/whatsapp/adapter.py" = ["ASYNC220", "ASYNC221"]
|
|
"plugins/platforms/photon/adapter.py" = ["ASYNC220"]
|