* feat(wisdom): add trusted publish and install foundation
* feat(wisdom): add private contribution loop
* feat(wisdom): add managed consumption workflows
* fix(wisdom): close cross-repository safety gaps
* fix(wisdom): align local package and lifecycle policy
* fix(wisdom): require explicit profile setup
* docs(wisdom): repin reconciled gateway head
* fix(wisdom): fence content downloads and approval receipts
* docs(wisdom): record generation-fenced downloads
* docs(wisdom): record unified delivery PR
* fix(ci): stop passing invalid classifier inputs
* docs(wisdom): remove internal requirements ledger
* feat(wisdom): localize dashboard and desktop copy
* feat(wisdom): complete local contribution and consumption UX
* style(wisdom): satisfy desktop lint
* chore(wisdom): refresh requirements pin
* test(dashboard): allow formatted profile copy
* test(wisdom): stabilize desktop interaction coverage
* fix(wisdom): surface dashboard action failures
* fix(wisdom): add repeatable Portal demo login
* feat(wisdom): add actionable skill notifications
* feat(wisdom): add notification install and update actions
* fix(wisdom): make Telegram skill alerts actionable
* fix(wisdom): always refresh demo Agent login
* feat(wisdom): embed Telegram notification actions
* fix(wisdom): preserve Telegram notifications after actions
* fix(wisdom): keep Telegram notification cards readable
* feat(wisdom): add Telegram candidate approval flow
* feat(wisdom): explain Telegram qualification reasons
* fix(wisdom): reconcile cross-surface candidate actions
* feat(telegram): add Collective Wisdom management command
* chore(wisdom): refresh Gateway contract pin
* chore(wisdom): advance Gateway contract pin
* feat(wisdom): align command UX across clients
* feat(slack): add Collective Wisdom management parity
* feat(wisdom): add security and professionalism reviews
* feat(wisdom): add first-time qualification guidance
* feat(wisdom): simplify qualification sharing choices
* feat(skills): add optional editorial metadata
* feat(wisdom): enrich legacy skill presentation
* fix(wisdom): harden review and update boundaries
* fix(wisdom): emit canonical review timestamps
* fix(wisdom): align with merged gateway and main
* wisdom: add agent-led sharing core (policy, evidence, schemas, templates, delivery, weekly job, share/install flows)
- hermes_wisdom/agent_led/: policy resolution (server > local > defaults),
7-day evidence builder that excludes bundled/hub/managed skills and
dismissed/handled/recently-suggested content hashes, strict pydantic
schemas for agent output with repair-or-reject, fixed copy templates
(Share / Teammate / Published / Update / Mute), idempotent retried
delivery ledger with stale-action resolution, weekly review job,
resumable Share and Install flows.
- prompts/: candidate review, recipient recommendation, share packaging.
- tests/wisdom/test_agent_led.py: 30 tests.
* wisdom: agent-led renderers and button action dispatcher
- render.py: Telegram HTML, Slack blocks, Desktop payload; editorial name
is the emphasized line, product label stays separate.
- actions.py: resolve opaque wa:<action>:<dedup> targets via the delivery
ledger; Not now -> dismissal, Mute -> fixed options, Share -> resumable
packaging flow, Install/Update -> plan command. Never publishes/installs.
* wisdom: CLI verbs, agent_led config default, conversational catalog skill
- hermes wisdom browse/review-week/act/share/dismiss/mute (all --json).
- wisdom.agent_led config block, default enabled.
- SKILL.md rewritten so natural-language catalog questions map to the CLI
verbs, share/install flows and fixed notification templates.
* wisdom: wire agent-led weekly review into gateway tick and Telegram buttons
- gateway housekeeping tick calls maybe_run_weekly_review with a home
channel sender when a Telegram adapter is available.
- Telegram: wa: callbacks resolved through the ledger (stale-safe), mute
duration keyboard, send_wisdom_agent_recommendation rich card + fallback.
* fix(wisdom): integrate local mediation and harden model and setup boundaries
* fix(wisdom): honor authoritative recommendation policy and defer on failure
* fix(wisdom): synchronize opaque suppression and recheck delivery preferences
* feat(wisdom): route weekly selection through the session-owned assessment queue
* fix(wisdom): prepare and submit the reviewed generated share package
* feat(wisdom): separate native Share preparation from publication consent
* feat(wisdom): sync native mute choices through a leased preference outbox
* feat(wisdom): bind native mute controls to durable preference choices
* feat(wisdom): add scoped desktop and dashboard notification settings
* fix(wisdom): revalidate feed recommendations before assessment and delivery
* fix(wisdom): persist validated delivery receipts before completing notices
* feat(wisdom): add private notification claim and receipt client
* Persist Wisdom send reservations and recover delivery acknowledgements
* Route legacy Wisdom controls through current native review
* Add typed private Wisdom operation outcome client
* fix(wisdom): make agent-led advice usable in the local demo
* fix(wisdom): keep requested consent outside proactive limits
* fix(wisdom): distinguish unavailable assessments and preserve digest text
* fix(wisdom): assess ongoing usefulness beyond the current task
* fix(wisdom): restore immediate qualification sharing controls
* fix(wisdom): separate qualification review from installation advice
* fix(wisdom): collapse review checklists and simplify sharing copy
* fix(wisdom): show compact sharing progress and publication receipts
* fix(wisdom): require credential prefixes rather than matching skill names
* fix(wisdom): finish package checks before presenting sharing consent
* fix(wisdom): scan local skills before qualification cards
* fix(wisdom): update moderation results on existing sharing cards
* fix(wisdom): keep sharing review accessible from receipt cards
* fix(wisdom): align mediated review cards and collapsible checks
* fix(wisdom): clarify clean security summary wording
* fix(wisdom): normalize consent plans and add explicit recheck
* fix(wisdom): keep install and update receipts concise
* fix(wisdom): collapse assessments and deduplicate operation cards
* fix(wisdom): restore private Portal review from native cards
* fix(wisdom): sync Portal publication to original consent card
* fix(wisdom): show local skill version on sharing cards
* fix(wisdom): skip agent recommendations for self-published versions
* fix(wisdom): simplify candidate notices and local-edit recovery copy
* feat(wisdom): submit locally reviewed packages with one confirmation
* feat(wisdom): expose safe receipt and outcome sync recovery
* wisdom: onboarding notice says detect and share, names the user's own skill
Copy review from the product owner on the first and returning
qualification notices (fixed delivery mode):
- the feature blurb now says the org enabled detection *and sharing*
- both notices say the detected skill is one the user created
- both close with an exclamation mark
Applied identically to hermes_wisdom.notice, the desktop and web i18n
strings, and the tests that assert the sentences.
* wisdom: one opener, no approval line, ask to share after the skill is shown
Product owner review of the candidate card.
- The Hermes written card now opens with the same sentence as the fixed card
("Your organisation has enabled Collective Wisdom, a feature designed to
automatically detect and share useful skills across all team members.")
instead of its own blurb, so there is one first time message.
- "Nothing is shared without your approval." removed from Telegram, Slack
and Desktop. The buttons already make the permission explicit.
- "Would you like to share?" no longer appears before the skill is named.
It is now the last line, after the skill name, description, why suggested
and the checks, and reads "Would you like to share it?" (matching the
agent led template wording).
Tests updated for the new order; proposalNotice removed from all desktop locales.
* wisdom: American spelling, organization
Product owner decision: user facing copy uses American spelling.
Changes "Your organisation" to "Your organization" in the chat notice,
the Hermes written card opener, the desktop and web strings, and the
tests that assert them. Identifiers such as nas_organisation:* and the
German and French locales are untouched.
* wisdom: candidate card copy round 4 (owner review)
Apply the product owner's round 4 copy decisions to the Hermes Collective
Wisdom candidate card on Telegram, Slack, Desktop and the shared views:
1. Hermes-written cards are titled "Hermes Collective Wisdom" instead of
the bare "Collective Wisdom".
2. The "Reusable skill ready to review" line is gone from the candidate
card (Telegram rich card and plain fallback, legacy agent-led share
template).
3. The skill name and description are labelled: "Skill name: <name>" and
"What it does: <description>" (Telegram, Slack, Desktop).
4. "Why suggested:" is now "Why others might benefit:".
5. A passing professionalism review reads "Safe to share at work ✓ (no
inappropriate content found)" with no per-check bullets and no "Pass";
a failed review reads "Needs a look before sharing at work (possible
inappropriate content)" and lists only the checks that flagged
something. Pending/unavailable wording is unchanged.
6. Telegram button toasts: "Will ask later...", "Preparing more
details...", "Sharing...".
7. Qualification reasons: "You used this skill consistently across many
days." and "You've really refined this skill."
8. prompts/wisdom_candidate_review.md asks for a compelling
editorial_name, a simple one_line_description and a compelling
why_coworkers_benefit under 300 characters; "Be concise and
convincing." becomes "Be concise and compelling: the goal is that the
user wants to share it."
Tests updated for the new strings; review_text() gains direct coverage.
* wisdom: re-apply owner copy after rebase
- Native share cards (advice_view/interaction_view): drop the approval line, ask "Would you like to share it?" as the last line after the checks
- Hermes-written completion card titled "Hermes Collective Wisdom"
- Qualification reasons use the owner wording (consistently across many days / really refined)
- American spelling (organization) in remaining English copy
- Desktop test asserts the current Share button; web test matches the returning notice
* fix(wisdom): pin reconciled Gateway and verify Unicode hash vectors
Pin Gateway 60cd2d6b613ae3cd4a6e65155d1142006d907e78 and byte-identical producer artifacts. Verify every content-order case and package-manifest binding. Validation: 186 focused Python tests, Ruff and contract verifier.
* fix(wisdom): reconcile optional SDK tests and frontend lint
* fix(wisdom): default to agent-written notification summaries
* fix(wisdom): restore deferred install review and browse controls
* feat(wisdom): inspect installed setup with exact package provenance
* feat(wisdom): run native-approved installed setup steps with durable evidence
* fix(wisdom): recover interrupted setup with explicit native consent
* feat(wisdom): hand native installs into guided setup review
* fix(wisdom): continue requested setup with fixed notification copy
* fix(wisdom): preserve setup while waiting for a session model
* fix(wisdom): expose canonical setup review controls on desktop
* fix(wisdom): resume setup after recorded automatic updates
* fix(wisdom): make missing setup prerequisites recheckable
* chore(wisdom): align Agent with verified Gateway contract
* fix(wisdom): stop guessing team slugs in portal links
* fix(wisdom): retire pending advice on account sign-out
* fix(wisdom): cancel advice after terminal account revocation
* fix(wisdom): fence feed responses across account sign-out
* fix(wisdom): checkpoint signed-out feed before reactivation
* fix(wisdom): link proactive advice to scoped notification settings
* fix(wisdom): coalesce queued publication recommendations by version
* fix(wisdom): keep package review navigation local and deferable
* fix(wisdom): reflect installed state in discovery controls
* fix(wisdom): show exact checks before command confirmation
* chore(wisdom): pin bounded analytics privacy contract
* chore(wisdom): pin retired legacy notification contract
* feat(wisdom): review publisher usage with exact sharing copy
* fix(wisdom): align discovery and review check summaries
* fix(wisdom): show expired consent before confirmation
* fix(wisdom): require fresh review for legacy install controls
* fix(wisdom): preserve review expiry across check toggles
* fix(wisdom): retain update policy in native install reviews
* fix(wisdom): surface failed native card edits
* fix(wisdom): persist local command approval reviews
* fix(wisdom): use saved approvals for messaging commands
* test(wisdom): provide scan result in setup handoff fixture
* test(wisdom): exercise Telegram approvals with saved review state
* fix(wisdom): retain suppression policy for offline deferral
* fix(wisdom): reconsider candidates after deferred suppression expires
* fix(wisdom): bind review checks and report verified readiness separately
* fix(wisdom): persist accepted publication intent and recover exact outcomes
* fix(sync): pin UTF-8 tree ordering across writers
* chore(wisdom): pin organisation-scoped Gateway authorization
* fix(wisdom): restrict consent delivery to user-facing sessions
* chore(wisdom): refresh reviewed Gateway contract pin
* fix(wisdom): preserve kept tools in Blank Slate exclusions
* test(auth): reset anonymous fixture with a profile-scoped cache
* fix(wisdom): gate local surfaces and work on current profile entitlement
* fix(wisdom): invalidate quiet tool cache on entitlement changes
* test(wisdom): authorize local consent gateway fixtures
* fix(wisdom): keep entitlement decoding free of native crypto imports
* test(wisdom): provide local entitlement to demo CLI subprocess
* ci: leave upstream workflow unchanged in Wisdom PR
* fix(wisdom): ship package and contracts in Nix wheels
---------
Co-authored-by: hbizi <36184542+hbizi@users.noreply.github.com>
659 lines
33 KiB
TOML
659 lines
33 KiB
TOML
|
|
|
|
[project]
|
|
name = "hermes-agent"
|
|
version = "0.21.1"
|
|
description = "The self-improving AI agent — creates skills from experience, improves them during use, and runs anywhere"
|
|
readme = "README.md"
|
|
# Upper bound is load-bearing, not cosmetic. uv resolves the project's
|
|
# Python from `requires-python`, and an inherited `UV_PYTHON` env var (or a
|
|
# fresh distro whose newest interpreter uv auto-picks) will otherwise select
|
|
# 3.14, where Rust-backed transitives (e.g. pydantic-core) have no cp314
|
|
# wheel yet and fall back to a maturin source build that fails. Capping at
|
|
# <3.14 makes uv refuse 3.14 with a clear error instead of attempting that
|
|
# build. Raise the ceiling once our Rust transitives ship cp314 wheels.
|
|
requires-python = ">=3.11,<3.14"
|
|
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.
|
|
#
|
|
# When updating: bump the version below AND regenerate uv.lock with
|
|
# `uv lock` so the transitive resolution stays consistent. Don't
|
|
# introduce ranges back 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 lazy-installed via `tools/lazy_deps.py` when the
|
|
# user picks that backend. Smaller `dependencies` = smaller blast
|
|
# radius for the next supply-chain attack.
|
|
"openai==2.24.0",
|
|
"certifi==2026.5.20",
|
|
"python-dotenv==1.2.2",
|
|
"fire==0.7.1",
|
|
"httpx[socks]==0.28.1",
|
|
"rich==14.3.3",
|
|
"tenacity==9.1.4",
|
|
"pyyaml==6.0.3",
|
|
"ruamel.yaml==0.18.17",
|
|
"requests==2.33.0", # CVE-2026-25645
|
|
"jinja2==3.1.6",
|
|
# 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). tools/lazy_deps.py `tool.doc_extract` remains the
|
|
# self-heal path for lean/broken installs — keep the pin below and the
|
|
# lazy pin in lockstep.
|
|
"firecrawl-anydoc==0.2.4",
|
|
# 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",
|
|
# Interactive CLI (prompt_toolkit is used directly by cli.py)
|
|
"prompt_toolkit==3.0.52",
|
|
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
|
|
"croniter==6.0.0",
|
|
# 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",
|
|
# ``packaging`` is imported directly on three production paths but was never
|
|
# declared, so it only reached users transitively (pip/uv pull it for other
|
|
# tools). The slim official Docker image ships without it, where the
|
|
# try/except-ImportError fallbacks silently degrade: Hindsight's
|
|
# ``_meets_minimum_version`` disables update_mode='append' (#40503),
|
|
# tools/lazy_deps.py treats every version constraint as satisfied, and
|
|
# hermes_cli/main.py drops to naive requirement parsing. Pure-Python
|
|
# py3-none-any wheel, no compiled extensions — safe to ship everywhere.
|
|
# Pinned to the version already resolved in uv.lock (no resolution churn).
|
|
"packaging==26.0",
|
|
# 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",
|
|
# Skills Hub (GitHub App JWT auth — optional, only needed for bot identity)
|
|
"PyJWT[crypto]==2.13.0", # 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",
|
|
# 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.0", # 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'",
|
|
# 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",
|
|
# Browser CDP supervisor + browser_dialog import this directly. Keep core
|
|
# so browser tool discovery doesn't fail on lean installs.
|
|
"websockets==15.0.1",
|
|
# .gitignore-aware file matching for desktop build stamp.
|
|
"pathspec==1.1.1",
|
|
"fastapi>=0.104.0,<1",
|
|
# CIDR-aware forwarded_allow_ips requires uvicorn >=0.31.0.
|
|
"uvicorn[standard]>=0.31.0,<1",
|
|
# 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",
|
|
"ptyprocess>=0.7.0,<1; sys_platform != 'win32'",
|
|
"pywinpty>=2.0.0,<3; sys_platform == 'win32'",
|
|
# 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'",
|
|
# 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",
|
|
# 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'",
|
|
# 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')",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
# 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"]
|
|
# 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"]
|
|
modal = ["modal==1.3.4"]
|
|
daytona = ["daytona==0.155.0"]
|
|
vercel = ["vercel==0.7.2"]
|
|
hindsight = ["hindsight-client==0.6.1"]
|
|
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.21", "ruff==0.15.10", "setuptools==83.0.0"] # starlette: CVE-2026-48710; setuptools: 83 (torch >=2.13 requires setuptools 83)
|
|
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.43.0", "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.43.0", "aiohttp==3.14.3"]
|
|
matrix = ["mautrix[encryption]==0.21.1", "aiosqlite==0.22.1", "asyncpg==0.31.0", "aiohttp-socks==0.11.0", "aiohttp==3.14.3"] # 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==1.2.1",
|
|
"sounddevice==0.5.5",
|
|
"numpy==2.4.3",
|
|
]
|
|
# "Hey Hermes" wake word — on-device hotword detection. All engines are
|
|
# optional; openWakeWord (ONNX) is the free default, sherpa-onnx adds
|
|
# open-vocabulary phrases (any typed phrase, zero training), Porcupine is
|
|
# the premium alternative. Desktop installs ([--include-desktop]) eager-install
|
|
# [wake]+[voice] so the ear works instantly; CLI-only installs lazy-install on
|
|
# first /wake; mirrored in tools/lazy_deps.py.
|
|
wake = [
|
|
"openwakeword==0.6.0",
|
|
"onnxruntime==1.27.0",
|
|
"sherpa-onnx==1.13.4",
|
|
"sentencepiece==0.2.2",
|
|
"pvporcupine==4.0.3",
|
|
"sounddevice==0.5.5",
|
|
"numpy==2.4.3",
|
|
# openWakeWord's onnx embedding model scores near-zero on macOS ARM64
|
|
# (dscripka/openWakeWord#336), so the wake word runs on tflite there.
|
|
# Upstream declares tflite-runtime for Linux only; ai-edge-litert is the
|
|
# macOS equivalent, bridged in tools/wake_word.py.
|
|
"ai-edge-litert==2.1.6; platform_system == 'Darwin'",
|
|
]
|
|
honcho = ["honcho-ai==2.2.0"]
|
|
# Cloud memory providers — opt-in, lazy-installed via tools/lazy_deps.py
|
|
# (memory.supermemory / memory.mem0) at first use. Exact pins MUST match the
|
|
# LAZY_DEPS pins (enforced by tests/test_project_metadata.py). Deliberately
|
|
# excluded from [all] like honcho/hindsight so a quarantined upstream release
|
|
# can't break fresh installs.
|
|
supermemory = ["supermemory==3.50.0"]
|
|
mem0 = ["mem0ai==2.0.10"]
|
|
# 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 — macOS background desktop control via cua-driver (MCP stdio).
|
|
# The cua-driver binary itself is installed via `hermes tools` post-setup
|
|
# (curl install script); this extra just pins the MCP client used to talk
|
|
# to it, which is already provided by the `mcp` extra.
|
|
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
|
|
# tools/lazy_deps.py (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
|
|
# tools/lazy_deps.py 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"]
|
|
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 aren't covered by `tools/lazy_deps.py`.
|
|
# 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 dev environments (`uv sync --extra google`)
|
|
# and packagers ship them without hitting runtime `pip install` paths that
|
|
# fail in environments without pip (e.g. Nix-managed Python).
|
|
"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 uv 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.
|
|
web = ["fastapi==0.133.1", "uvicorn[standard]==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 lazy-installed via `tools/lazy_deps.py` — 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
|
|
# `LAZY_DEPS` 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[homeassistant]",
|
|
"hermes-agent[sms]",
|
|
"hermes-agent[acp]",
|
|
"hermes-agent[google]",
|
|
"hermes-agent[web]",
|
|
"hermes-agent[youtube]",
|
|
]
|
|
|
|
[build-system]
|
|
# setuptools.build_meta + our setup.py bdist_wheel guard import wheel during
|
|
# PEP 517 isolated builds (uv sync / uv pip install -e .). Without wheel in
|
|
# requires, the isolation sandbox only gets setuptools and Windows installer
|
|
# 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 = "run_agent:main"
|
|
hermes-acp = "acp_adapter.entry:main"
|
|
|
|
[tool.uv]
|
|
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.
|
|
# Remove this line when alibabacloud-tea-openapi lifts the cap.
|
|
"cryptography>=50,<51",
|
|
]
|
|
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").
|
|
[tool.uv.exclude-newer-package]
|
|
agent-client-protocol = false
|
|
ai-edge-litert = 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
|
|
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
|
|
h2 = false
|
|
hindsight-client = false
|
|
honcho-ai = false
|
|
httplib2 = false
|
|
httpx = false
|
|
httpx2 = false
|
|
huggingface_hub = false
|
|
jinja2 = false
|
|
lark-oapi = false
|
|
markdown = false
|
|
maturin = false
|
|
mautrix = false
|
|
mcp = false
|
|
mem0ai = false
|
|
microsoft-teams-apps = false
|
|
mistralai = false
|
|
modal = false
|
|
nemo-relay = false
|
|
numpy = false
|
|
onnxruntime = false
|
|
openai = false
|
|
opentelemetry-exporter-otlp-proto-http = false
|
|
opentelemetry-sdk = false
|
|
openwakeword = false
|
|
packaging = false
|
|
parallel-web = false
|
|
pathspec = false
|
|
pillow = false
|
|
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 = false
|
|
slack-bolt = false
|
|
slack-sdk = false
|
|
snowballstemmer = false
|
|
sounddevice = false
|
|
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.*", "hermes_wisdom", "hermes_wisdom.*", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "cron.*", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*"]
|
|
|
|
[tool.setuptools.package-data]
|
|
hermes_cli = ["observability/schemas/*.json", "data/*.json", "local_runtime/*.json"]
|
|
hermes_wisdom = ["contracts/*.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"]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
markers = [
|
|
"integration: marks tests requiring external services (API keys, Modal, etc.)",
|
|
"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",
|
|
"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)",
|
|
"linux_only: exercises Linux-specific behaviour; skipped on other hosts",
|
|
"macos_only: exercises macOS-specific behaviour; skipped on other hosts",
|
|
"windows_only: exercises native-Windows behaviour; skipped on other hosts",
|
|
]
|
|
# integration tests take way too long to run in the normal CI environments
|
|
addopts = "-m 'not integration'"
|
|
|
|
[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"]
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
# Tests can intentionally exercise locale-encoding edge cases.
|
|
"tests/**" = ["PLW1514", "ASYNC210", "ASYNC220", "ASYNC221", "ASYNC251"]
|
|
# 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"]
|