Commit Graph

176 Commits

Author SHA1 Message Date
kshitij 0d32607c62 fix(gateway): split check_fn (passive probe) from ensure_deps_fn (active installer)
PlatformEntry.check_fn served three contradictory roles: adapter-creation
gate, config auto-enablement gate, and status display. Plugins had to pick
one function for all three:

- Active installer as check_fn (discord/slack/telegram/matrix/dingtalk/
  feishu): every status display could pip-install SDKs as a side effect
  (the desktop 94% boot-loop class).
- Passive probe as check_fn (teams, wecom_callback): create_adapter()
  returned None before connect() could lazy-install, so the SDK never
  installed (#79812 deadlock; wecom_callback's platform.wecom_callback
  LAZY_DEPS entry was dead code).

The split makes both call sites correct by construction:

- check_fn is now contractually PASSIVE (probe only, never installs).
- New optional PlatformEntry.ensure_deps_fn is the ACTIVE installer;
  create_adapter() runs it exactly when check_fn is False — the platform
  is enabled+configured and the gateway is about to connect it.
- Config enablement keeps a configured platform whose deps are missing
  but installable; the install itself is deferred to create_adapter().
- Status surfaces (_platform_status, hermes status) read only the
  passive probe and can never trigger pip.

Migrated all lazy-installable platform plugins to the split; platforms
with no optional deps (irc/ntfy/buzz/simplex/line/a2a/...) are unchanged
— no ensure_deps_fn means a False check_fn stays a hard block.
wecom_callback gains a working installer for the first time.

Builds on @xxxigm's #79812 (both commits cherry-picked with authorship
preserved), reworking the check_fn swap into the two-field split so the
Teams fix doesn't reintroduce install-on-status.
2026-08-07 13:28:43 +05:30
SmokeDev 60e1f7517c fix(delegation): surface a child's undelivered steer instead of dropping it
The turn finalizer already hands back steer text that queued after the
final tool batch — result["pending_steer"], with the comment "hand it
back to the caller so it can be delivered as the next user turn instead
of being silently lost." Every interactive surface honors that contract
(cli.py, gateway/run.py, tui_gateway/server.py all requeue it). The
delegation layer doesn't: _run_single_child never reads it, so a steer
queued into a delegated child that finishes first vanishes with no trace
in the completion entry. There is also no sanctioned sender: the registry
has interrupt_subagent() but no redirection-side mirror, and session.steer
cannot reach children (lazy watch sessions have agent=None, so it 4010s).

Complete the contract for delegated children — both halves:

- steer_subagent(subagent_id, text): redirection-side mirror of
  interrupt_subagent(). Resolves the live child in _active_subagents and
  queues text via AIAgent.steer(). True means queued, not delivered.
- missed_steer retention: when the child's result carries pending_steer,
  _run_single_child names it on the completion entry (missed_steer field
  plus a summary note) so the parent can re-issue the guidance instead of
  trusting it landed. This is what makes adding a sender safe: without it
  the finish-before-drain race silently loses the text — the exact loss
  the finalizer contract exists to prevent.
- subagent.steer gateway RPC beside subagent.interrupt so programmatic
  hosts (dashboard, voice layers, ACP bridges) get an in-tree caller;
  catalogued in programmatic-integration.md.
- docs: "Steering a Running Subagent" section in delegation.md covering
  the queued-vs-delivered semantics.

Tests: registry-level steer coverage (delivery, unknown id, empty text,
dead record, raising agent), the finish-before-drain race retaining
missed_steer, and the RPC contract (4000/4002 validation, queued and
rejected envelopes).
2026-08-06 09:40:27 -07:00
Brooklyn Nicholson e8ccb4a2ea feat(desktop): ctx.os — the curated OS door for plugins
Fold ctx.notifyNative into a ctx.os namespace so every way a plugin
reaches outside the app window lives behind one attributed door instead
of accreting one top-level ctx method per capability:

- ctx.os.notify — the native-notification door from the previous commit,
  unchanged semantics (plugin kind pref, away-gating, per-plugin throttle).
- ctx.os.openExternal / ctx.os.revealPath / ctx.os.writeClipboard — the
  existing window.hermesDesktop bridge capabilities, now sanctioned and
  result-shaped: each resolves false (never throws) when the bridge or
  member is missing, so a plugin branches on the result instead of
  sniffing the preload surface or crashing on an older shell.

No new Electron surface: everything routes through bridge members the
app already ships; the notification path keeps every existing gate.
2026-08-04 11:33:25 -06:00
seref 5d24594ab3 feat(desktop): expose native OS notifications to plugins via ctx.notifyNative
Desktop plugins can toast in-app (host.notify) but have no sanctioned way to
reach the OS notification pipeline the app's own approval/turn alerts use, so
a plugin surfacing a genuinely notable background event (e.g. a discovery
plugin finding a match) stays invisible once the user steps away from Hermes.

Add a curated per-plugin door instead of exporting the raw dispatcher:

- ctx.notifyNative({ title, body?, silent? }) on PluginContext — attributed
  to the plugin id, routed through dispatchNativeNotification so every
  existing gate applies (master + per-kind prefs, post-connect baseline,
  away-from-app gating, throttle).
- New 'plugin' native-notification kind with its own Settings ▸ Notifications
  toggle (default on), so users silence plugins without losing app alerts.
- New optional `tag` discriminator on the notify payload keys the renderer
  throttle and main-process cross-window dedupe per plugin, so two plugins
  can't collapse each other's session-less notifications.

Consumer: the Index Network desktop plugin wants background opportunity
alerts; anything in ~/.hermes/desktop-plugins gets the same door.
2026-08-04 11:28:20 -06:00
Teknium 971d81f892 fix(agent): isolate the pooled compression worker from the live transcript (review F3)
The pooled worker closure captured the caller's live `messages` list and
compress_context explicitly supports plugin/legacy context engines that
mutate that list in place — so after a host timeout, a late engine could
rewrite the live conversation (roles, ordering, persisted content)
concurrently with the resumed turn.

The worker now deep-snapshots the transcript on the worker thread before
any engine code runs; the caller's list object is never handed to pooled
code. Results reach caller-visible state only through the returned value
of an ADMITTED commit (the host discards results on timeout/cancel), and
durable SessionDB mutation was already gated behind the commit fence.
No-op passes map the unchanged snapshot back to the caller's original
list so identity-based no-op detection and flush dedup keep working.

Document the thread-safety contract for context-engine and
memory-provider extension points (they now run on pooled threads) in the
module docstring and the context-engine plugin guide.

Regression: an in-place-mutating engine plus host timeout proves the
caller's live transcript is byte-identical WHILE the worker is still
blocked inside the engine (released only after the assertions).

PR #76354 review, blocking finding 3 / merge gate 3.
2026-08-02 16:16:36 -07:00
spfcraze 8f91e249e4 perf(state): add messages(session_id, id) index for window/ordering queries
Every ORDER BY id query on the messages table sorted or scanned the
whole session: get_messages_around's window seek, latest_message_row_id
(LIMIT 1), and get_messages' full-load ordering all paid O(session
history) per call — hot mid-turn via session_search and reactions.
messages.id is an original column (INTEGER PRIMARY KEY AUTOINCREMENT),
so the index lives in SCHEMA_SQL next to idx_messages_session — no
legacy-column migration hazard (the kanban lesson from #28776 does not
apply).

Measured (real schema, one 20k-message session, median of 30):
get_messages_around 7.08 -> 0.22 ms (32x), latest_message_row_id
3.37 -> 0.011 ms (307x), get_messages full load 111.6 -> 98.6 ms
(1.13x — remaining cost is row deserialization, not the sort).
Window results byte-identical at probe points across the session.

Tests: VM-step pin (get_messages_around bounded work, calibrated
~12 vs ~855 handler calls, threshold 300 — fails without the index)
and window parity with/without the index. No EXPLAIN/plan text
(behavior contracts, AGENTS.md).
2026-08-02 21:16:17 +05:30
ethernet 713a983e4a feat(runtime)!: require Node 26 across all installers, heal, and upgrade paths
Hermes now pins its toolchain to Node 26 everywhere. Every path that
installs, accepts, heals, or upgrades a Node runtime moves from the old
22-default / `^20.19 || >=22.12` floor to a single rule: Node >=26.

Installers:
- scripts/install.sh — NODE_VERSION=26; node_satisfies_build() collapses
  the two-branch Vite floor to `major >= 26`; user-facing messages updated.
- scripts/install.ps1 — $NodeVersion=26; Test-NodeVersionOk likewise;
  winget fallback switches OpenJS.NodeJS.LTS -> OpenJS.NodeJS (26 is
  Current, not LTS — the LTS manifest would reinstall a too-old Node).
- Dockerfile — node_source stage node:22-bookworm-slim -> node:26 (digest
  pinned, amd64 sha256:9e6f...bf73).
- nix/ was already on nodejs_26 (lib.nix, npm-12-0-2.nix); the checks.nix
  wrapper check ratchets from `>= 20` to `>= 26`.

Heal/upgrade paths:
- scripts/lib/node-bootstrap.sh — HERMES_NODE_TARGET_MAJOR default 22->26
  and HERMES_NODE_MIN_VERSION default 20->26, so heal_managed_node,
  _nb_install_bundled_node, and the fnm/proto/nvm/brew rungs all target 26
  and stop accepting an on-PATH Node below it. Both remain env-overridable.
- hermes_constants.py — _HERMES_NODE_TARGET_MAJOR fallback 22->26, which
  drives the Windows heal path's latest-v26.x download.

Version gates:
- package.json engines.node >=20 -> >=26; apps/desktop engines
  `^20.19.0 || >=22.12.0` -> `>=26.0.0`.
- CI setup-node: all five workflows 22 -> 26.
- Docs describing Hermes's own toolchain updated (windows-native, docker,
  acp, nix-setup, contributing). Skill docs describing third-party tools'
  own requirements are untouched.

Termux still installs via `pkg install nodejs` best-effort (nodejs.org
ships no Android tarballs); that path was never version-gated.

Verified: bash -n on both shell scripts, PowerShell AST parse of
install.ps1, latest-v26.x index resolves (node-v26.5.1), and the install
test suite — 18 tests across the 5 install/runtime test files — passes.
2026-08-01 21:17:51 -04:00
konsisumer 8e1debd5ed docs: purge stale xdist/_enforce_test_timeout test-runner references repo-wide
The test runner moved to per-file subprocess isolation via
scripts/run_tests_parallel.py (hermetic `env -i`, worker count auto-scaled
from CPU count, FLAKY-retry policy) — no pytest-xdist, no SIGALRM per-test
timeout fixture. Docs still described the old runner in many places:

- AGENTS.md: "-n auto xdist workers, in-tree subprocess-isolation plugin"
  clause replaced with the current per-file-subprocess description; the
  `::test_x` single-test example now shows file + -k (runner is
  file-granular).
- CONTRIBUTING.md: "hermetic env, 4 xdist workers" comment corrected;
  `tests/conftest.py::_enforce_test_timeout` reference redirected to the
  win32 timeout-method shim in `tests/conftest.py::pytest_configure`.
- skills/autonomous-ai-agents/hermes-agent/references/contributor-guide.md
  and windows-quirks.md: same corrections (the bundled skill mirrors the
  contributor docs); Windows workaround no longer installs pytest-xdist
  or passes -n 0.
- website/docs + zh-Hans i18n mirrors: same fixes in adding-providers.md
  and the bundled-skill doc pages.
- skills/software-development/python-debugpy/SKILL.md (+ zh-Hans mirror):
  "-p no:xdist"/"-n 0" pdb advice rewritten for the captured per-file
  subprocess runner.
- skills/creative/comfyui/tests/README.md: parent-repo "-n auto by
  default" rationale updated to past tense.

Combined salvage of PR #38295 (konsisumer), PR #51354 (TutkuEroglu,
redirected to the current conftest truth and the relocated
references/contributor-guide.md), and PR #54956 (waroffchange).

Co-authored-by: TutkuEroglu <rrandqua@gmail.com>
Co-authored-by: waroffchange <116298975+waroffchange@users.noreply.github.com>
2026-07-29 23:16:18 -07:00
Teknium ad12df6ba4 Revert "remove Vercel AI Gateway and Vercel Sandbox (#33067)"
This reverts commit febc4cfec0.
2026-07-29 19:48:37 -07:00
virtuadex 782d0219a0 docs: sync overnight sweep with registry, gateway, curator, cron
Salvaged from #72422 by @virtuadex (conflicts resolved against current
main; superseded slash-command hunks dropped):

- SECURITY.md + SECURITY.es.md: gateway adapters live under
  plugins/platforms/<name>/, registry in gateway/platform_registry.py
- gateway-internals.md (EN + zh-Hans): key-files table rows for
  platform_registry.py and plugins/platforms/, deferred-loading section
- slash-commands.md: /reasoning full level list (max/ultra) + --global;
  CLI-only notes list gains /prompt, /pet, /hatch, /timestamps
- cron.md + cron-script-only.md: script runner accuracy — bash resolved
  from PATH with /bin/bash fallback, script paths confined to
  ~/.hermes/scripts/, provider credentials stripped via
  _sanitize_subprocess_env
- curator.md: cron-referenced skills protected from auto-archive,
  never-used grace floor
2026-07-29 09:45:11 -07:00
Teknium c6f98b0cba docs: teach providers: dict as the canonical custom-provider format
Fixes #67278. Since config v12 (see #8776), custom_providers: list is
legacy — the migration converts it to the providers: dict and the
resolver reads the dict first (runtime_provider.py). Docs still taught
the legacy list as primary.

- integrations/providers.md: all 8 YAML examples converted to
  providers: dict (field mapping code-verified: api, default_model,
  transport), one consolidated legacy-format note
- configuring-models.md, configuration.md, credential-pools.md,
  migrate-from-openclaw.md, provider-runtime.md, faq.md: examples and
  prose flipped to dict-first; legacy list noted as still-read
- adding-providers.md untouched (sole mention is a literal test
  filename)
2026-07-29 09:44:59 -07:00
Teknium 43874d1a96 docs: accuracy sweep + coverage for 2 months of shipped features
Accuracy pass (all 373 pages audited against code, 13 parallel audits):
- configuration.md: 12 stale defaults/keys (file-sync rewrite, clarify
  timeout, streaming knobs, iteration budget, TTS/STT enums)
- reference/: commands/env-vars/toolsets/tools synced with
  COMMAND_REGISTRY, argparse tree, OPTIONAL_ENV_VARS, TOOLSETS
  (28 env vars added, 3 phantom removed, mcp__ naming, webhook
  platform restricted toolset)
- features/, messaging/, developer-guide/, guides/: ~60 factual fixes
  (web_extract truncation, dashboard auth fail-closed, delegation
  blocked tools, adapter signatures, session schema v23, phantom
  Matrix env vars, hermes setup tts, auth spotify, webhook --skills)
- zh-Hans: explicit heading IDs fix 2 broken WSL2 anchors

New coverage for features shipped in the last 2 months (verified
against code before writing):
- compression.in_place, verify-on-stop (+v31/v32 migration reality),
  ${env:VAR} SecretRef, display.timestamp_format, session:compress
  hook + thread_id/chat_type fields
- /journey learning timeline, per-channel model/system-prompt
  overrides, /sessions search, clarify multi-select, -z --usage-file,
  uninstall --dry-run, config get/unset
- MCP elicitation, extra_headers, discover_models, api-server run cap,
  Bedrock cachePoint, Discord reasoning_style, Google Chat clarify
  cards, vibe reactions, resume cwd restore, Yuanbao forwarded
  messages, api_content sidecar, roaming pet, tool_progress log mode,
  WhatsApp polls/locations, kanban per-task model + lifecycle hooks
2026-07-29 08:48:05 -07:00
Eugeniusz Gilewski b76acacbb9 fix(tools): execute local STT templates without a shell
HERMES_LOCAL_STT_COMMAND rendered quoted placeholders into a
user-configured template and passed the result to shell=True. Shell
metacharacters in the template therefore remained executable syntax even
though the placeholder values themselves were quoted.

Tokenize the rendered template and invoke it as an argv list while
preserving the existing timeout, closed stdin, and Windows creation flags.
Lock the invocation contract with metacharacter regression coverage and
document explicit shell wrapping for trusted templates that need it.

Salvages #32694

Co-authored-by: Ernest Hysa <takis312@hotmail.com>
2026-07-28 18:12:26 -07:00
Tony Simons f60abd6e37 fix subagent lifecycle ownership invariants 2026-07-26 23:27:30 -07:00
Tony Simons 1865fb5fcd feat(plugins): add public subagent lifecycle API 2026-07-26 23:27:30 -07:00
teknium1 1b081e4891 feat: raise default tool-calling iteration limit from 90 to 500
The default max_iterations/agent.max_turns budget was set when long
agentic runs were rare; complex tasks now routinely exceed 90 tool
calls. Raise the default to 500 across every surface that hardcodes
the fallback: AIAgent constructor, DEFAULT_CONFIG, CLI resolution
chain, gateway env bridge, cron scheduler, and TUI gateway. Explicit
user config values are unaffected (deep-merge preserves them; no
_config_version bump needed).

Docs (en + zh-Hans), CLI help text, tips, and pinned tests updated
to match.
2026-07-26 12:54:45 -07:00
abundantbeing 4a9447c726 fix(api-server): expose model options inventory
Add authenticated GET /api/model/options to the gateway API server,
sharing the dashboard/TUI picker payload builder so external clients
can sync to the user's configured Hermes provider catalog instead of
scraping the single OpenAI-compatible /v1/models alias.

- new shared hermes_cli.inventory.build_model_options_payload() wraps
  build_models_payload with the stable picker shape and safe
  custom-provider probe policy (probe current only on normal open,
  probe all + cache bust on explicit refresh)
- dashboard web_server and TUI gateway model.options refactored onto
  the shared builder; dashboard build moved off the event loop via
  run_in_threadpool
- capabilities endpoint advertises model_options
- docs for both API server and programmatic integration

Salvaged from PR #54689 by @abundantbeing.
2026-07-24 11:20:07 -07:00
teknium1 397e9fc1e4 Reapply "Merge pull request #30179 from NousResearch/feat/iron-proxy"
This reverts commit c6dc7c03c3.
2026-07-24 09:49:00 -07:00
Teknium b55bb2cd10 docs(context-engine): scope select_context/on_turn_complete — replace-needs only, MemoryProvider for observation-only, cache-stability guidance
Maintainer scoping decision for the #51226 salvage: document that
select_context() is for engines that must REPLACE per-request context
(retrieval/routing) — pre_llm_call is inject-only by documented cache
design; that observation-only plugins should implement a MemoryProvider
(sync_turn) rather than a context engine, with on_turn_complete scoped
as the observation mirror for engines that already select; and that a
non-no-op select_context naturally changes the prompt-cache prefix on
turns where the selection changes — engines should return stable
selections when nothing changed.
2026-07-23 19:44:35 -07:00
xue xinglong 56e00f4ca1 docs+test(context-engine): sync public guide coverage note; pin finalization-seam observation contract
- website guide: on_turn_complete() now carries the same best-effort coverage
  caveat as the ABC docstring (fires from the finalization seam; abnormal
  early-return paths bypass it) — removes the doc/code inconsistency.
- test: finalization seam emits on_turn_complete with usage=None + the
  interrupted flag for an interrupted finalized turn. Docstring records that
  the negative early-return-bypass half is best-effort and deferred to a
  shared-seam follow-up rather than pinned via a full run_conversation harness.
2026-07-23 19:44:35 -07:00
xue xinglong 915942935d fix(context-engine): fail open on empty select_context() result + doc public hooks
- _apply_context_engine_selection: reject an empty list. all([]) is True, so
  a [] returned by a failing/buggy engine previously replaced a valid request
  with an empty message list the downstream sanitizers can't restore; now it
  falls open to the unmodified request (honors the fail-open contract).
  Thanks @johnnykor82 for catching this on #41918's review.
- test: empty list keeps the original request (fail-open regression).
- docs: document select_context()/on_turn_complete() in the public
  context-engine plugin guide (were still describing only the old contract).
2026-07-23 19:44:35 -07:00
Teknium d43cc2ca80 fix(compress): gate N-user tail guarantee to actionable turns, behavior-preserving default
Follow-up fixes on top of the salvaged #22566 mechanism:

- N-collector now counts only REAL actionable user turns via
  _is_actionable_user_turn + _is_synthetic_compression_user_turn —
  the same filter pair _find_last_user_message_idx uses post-#69291.
  The contributor's bare role=='user' + _is_context_summary_content
  check let blank platform echoes and continuation/todo rows consume
  N slots, silently degrading the guarantee.
- Default flipped 3 -> 1 (behavior-preserving): a default of 3 was
  measured to change the tail cut on transcripts whose budget covers
  only the last turn. min_tail_user_messages=1 delegates to the
  existing single-user anchor; N>1 is opt-in, and the call site is
  gated so the default path is byte-identical to main.
- Hardened config parse in agent_init (bool rejected, fractional
  floats rejected, floor 1) matching the max_attempts parser shape.
- Wired the recurring external-PR config gaps: hermes_cli/config.py
  DEFAULT_CONFIG + cli-config.yaml.example (PR only had cli.py).
- Regression tests: blank echoes / synthetic rows don't count toward
  N; tool-call/result pairs never split by the N-boundary (no-orphan
  both directions); N-guarantee wins over tail_token_budget and the
  _MAX_TAIL_MESSAGE_FLOOR (floor is a minimum, not a cap); default
  parity pin; DEFAULT_CONFIG pin.
2026-07-23 17:03:49 -07:00
ethernet d84e11af4d
rip out brew + pip/PyPI wheel support (#68217)
Removes Homebrew and PyPI wheel/sdist as Hermes distribution paths while
preserving the supported source, Docker, and Nix workflows.

Changes:
- Removes the Homebrew formula, PyPI publish workflow, sdist manifest
  (MANIFEST.in), and wheel/sdist release-attachment logic from scripts/release.py.
- Keeps setuptools metadata and entry points required by editable installs
  and Docker/Nix builds, but adds a setup.py guard that rejects wheel/sdist
  builds outside a sealed Nix derivation (HERMES_NIX_BUILD=1).
- Removes pip/Homebrew install detection, PyPI update checks, the pip
  self-update path, the deprecation-banner state, the postinstall subcommand,
  wheel data-directory fallbacks in agent/i18n.py and hermes_constants.py,
  and the ACP Registry manifest/version-lockstep release logic.
- Adds /nix/store/ path detection so `nix run` / `nix profile install`
  installs (which don't set HERMES_MANAGED) are correctly identified as
  "nix" rather than falling through to "git"/"unknown".
- Retired install-method values ("pip", "homebrew") in existing
  .install_method stamps (both code-scoped and home-scoped) are ignored by
  the allowlist reader and fall through to "unknown" instead of resurrecting
  a retired enum value.
- Updates Nix packaging to ship bare runtime data (locales, optional-mcps)
  through store symlinks and wrapper env vars instead of wheel data-files.
- Removes the ACP Registry manifest/icon and their version-lockstep tests.
- Deletes or rewrites packaging, pip-update, Homebrew, and ACP Registry
  tests; adds parametrized coverage for the packaging build guard covering
  BOTH sdist and wheel paths (the guards live in separate cmdclass entries
  — a passing sdist test proves nothing about the wheel path).
- Updates installation/platform documentation and related user-facing copy.
- Adjusts the supply-chain scan so deleted install-hook files do not trigger
  a finding, while additions or modifications still require the existing
  ci-reviewed label gate.

Supported installation paths (unchanged):
- git installer (install.sh)
- Docker
- Nix/NixOS
- editable development installs (uv sync, uv pip install -e ., pip install -e .)
2026-07-22 16:51:01 -04:00
Teknium 1efe7094ad feat(compression): harden idle compaction — lock/guard interplay, config + docs
Follow-up for the salvaged #55800 idle-compaction commit:

- turn_context.py: treat a skipped _compress_context (per-session
  compression lock held by another path, failure cooldown, anti-thrash
  breaker, codex-native routing) as a strict no-op — only re-baseline
  conversation_history and re-anchor current_turn_user_idx after a REAL
  compaction. Also re-anchor the user-message index after idle compaction
  (the PR predates the reanchor helper).
- hermes_cli/config.py: add idle_compact_after_seconds: 0 to
  DEFAULT_CONFIG's compression block (the PR only had
  cli-config.yaml.example).
- gateway/run.py: add the idle-compaction status wording to
  _TELEGRAM_NOISY_STATUS_RE so the new 💤 message stays out of
  human-facing chat surfaces (routine compaction is silent by design);
  pin it in tests/gateway/test_telegram_noise_filter.py.
- docs: idle_compact_after_seconds in user-guide/configuration.md and
  developer-guide/context-compression-and-caching.md parameter table.
- tests/agent/test_idle_compaction_lock_and_guards.py: end-to-end
  coverage with a real AIAgent + SessionDB proving the idle path honors
  the per-session compression lock (added after the PR), the persisted
  failure cooldown, and the anti-thrash breaker, and that the lock is
  released after an idle-triggered compaction.

Salvaged from #55800 by @iso2kx. Implements #27579.
2026-07-22 09:14:11 -07:00
Teknium f944e84858 fix: close review gaps for per-model threshold overrides (#63020)
Follow-up to the salvaged contributor commit, closing the three gaps
flagged in the sweeper review:

1. Init ordering: assign compression.model_thresholds to a selected
   plugin context engine BEFORE the initial update_model() call in
   agent_init.py, so the initial model's override applies from init
   (previously it only took effect after the first /model switch).
   Base-class ContextEngine.update_model() now snapshots the
   pre-override percent once so repeated switches fall back to the
   engine's configured threshold, not a previous model's override.
2. DEFAULT_CONFIG: add compression.model_thresholds (empty map) to
   hermes_cli/config.py — additive key, no _config_version bump.
3. Docs: document the key in
   website/docs/developer-guide/context-compression-and-caching.md
   (yaml example, parameter table, dedicated section) and update the
   plugin-boundary note in context-engine-plugin.md to state the
   explicit context-engine contract for model_thresholds.

Adds tests/run_agent/test_per_model_threshold_init_ordering.py:
plugin-engine AIAgent init regression (override applies at init,
empty map unchanged), DEFAULT_CONFIG key presence, floor interaction
on the model-switch path (override below the small-context floor is
raised to the floor; above the floor wins), and base-class config
snapshot across repeated switches. Also maps @bennybuoy in
contributors/emails/.
2026-07-22 07:00:27 -07:00
Teknium d3b0e61429
feat(secrets): one-command token rotation + actionable startup errors for all secret sources (#68605)
* feat(secrets): one-command token rotation + actionable startup errors for all secret sources

When a Bitwarden machine-account token expired, users saw a raw Rust
error dump (invalid_client + Location: + backtrace hints) and the only
fix was manually editing .env or re-running the whole setup wizard.

- New `hermes secrets bitwarden token` / `hermes secrets onepassword
  token`: paste a new token (masked prompt or flag), the command probes
  the backend BEFORE persisting — a rejected token changes nothing; a
  good one is written to .env and the fetch caches are cleared.
- New optional SecretSource.remediation(kind, cfg) hook: startup
  warnings now print a '→ Run `hermes secrets <name> token`…' fix-it
  line after any fetch error, for bundled AND plugin sources (generic
  per-ErrorKind defaults in the ABC).
- bws stderr is summarized to its cause line (Location:/backtrace noise
  dropped) and invalid_client/invalid_grant/400 identity rejects are
  now classified AUTH_FAILED (was INTERNAL) with a plain-English
  explanation naming the token env var.
- op whoami probe accepts a candidate token so rotation validates the
  NEW credential, not the ambient one.

Additive hook with defaults — no SECRET_SOURCE_API_VERSION bump.

* docs: fix MDX parse error in secret-source-plugin hook table

Escaped backticks around a <name> placeholder made MDX parse it as an
unclosed JSX tag, breaking the docs-site build.  Use a plain code span
instead.
2026-07-21 06:41:04 -07:00
brooklyn! 360b07794b
docs(desktop): document the desktop plugin SDK (@hermes/plugin-sdk) (#67301)
Add an end-to-end developer guide for extending the native Hermes Desktop
app introduced in #60638: the HermesPlugin contract, PluginContext, every
contribution area (panes, routes, sidebar nav, status/title bar, palette,
keybinds, themes, composer, mount-scoped Contribute), the host API, the
React Query + nanostores data layer, the UI kit + theme variables, the
scoped ctx.rest/ctx.socket backend (plugin_api.py under /api/plugins/<id>)
and its separate enable gate, Settings/defaultEnabled/storage, bundled
plugins, the security model, pitfalls, and a full reference.

- Register the page in the sidebar under Extending -> Plugins.
- Disambiguate from the unrelated web-dashboard plugin SDK from both
  directions, and add a map-table row + a desktop user-guide pointer.
- Fill the gaps in the agent-facing hermes-desktop-plugins skill
  (ctx.rest/socket + backend, React Query, defaultEnabled, Contribute)
  and point it at the new reference so agents know the SDK when writing
  addons.
2026-07-19 04:02:17 +00:00
brooklyn! 629aeeebea
docs(developer-guide): document htui/hgui worktree UI dev helpers (#64783)
Add a developer-guide page for running the Ink TUI and Electron desktop
app from a git worktree without a full npm install per checkout, via the
htui/hgui shell helpers that share node_modules from a canonical deps
checkout by symlink (falling back to a local npm ci when the lockfile
diverges). Registers it in the sidebar, cross-links from the TUI and git
-worktrees pages, and documents the previously-undocumented
HERMES_DESKTOP_PYTHON / HERMES_DESKTOP_DEV_SERVER env vars the desktop
backend reads.
2026-07-16 22:51:23 -04:00
Teknium 176a98c39a docs: refresh salvaged audit fixes against current main
PR #36051's values went stale since May 31: session-store SCHEMA_VERSION
is now 21 (PR said 14), and the dashboard ships 8 built-in themes
(PR said 7). Also document the v16/v18/v20 data migrations added since.
2026-07-16 04:47:40 -07:00
kocaemre a710becd6c docs: fix 25 documentation/code inconsistencies (audit round 3)
Cross-checked website/docs against the source at main HEAD and corrected
documented commands, env vars, config keys, headers, and default values
that don't match the code. Docs-only; no behavioral changes.

Refs #36048

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 04:47:40 -07:00
brooklyn! 04c7d104a7
Merge pull request #61885 from embwl0x/agent/tui-secret-overlay-expiry
fix(tui): dismiss expired sensitive prompts
2026-07-12 05:50:24 -05:00
ethernet 5e849942c3 feat(dev): add isolated sandbox script for local dev
scripts/desktop-sandbox.sh runs a Hermes desktop instance in an isolated
sandbox — separate HERMES_HOME, separate Electron userData, and a
distinct
app name (HERMES_DESKTOP_APP_NAME) so it doesn't compete with the main
desktop instance's single-instance lock.

Two modes:
- Ephemeral (default): temp dir, cleaned up on exit
- --persistent: stored under .hermes-sandbox/ in the worktree git root,
  survives restarts for repeat testing

In the Nix devShell the script is available as 'sandbox'.

Also makes APP_NAME overridable via HERMES_DESKTOP_APP_NAME in main.ts —
app.setName() runs before requestSingleInstanceLock(), so the overridden
name changes the lock key. collectRelaunchEnv already preserves
HERMES_DESKTOP_* vars through self-update relaunches; test updated to
cover the new env var.
2026-07-10 18:51:49 -04:00
embwl0x f618984c64 fix(tui): dismiss expired sensitive prompts 2026-07-10 14:45:43 -04:00
hmirin d1c8c03416 feat(agent): add Codex-native compaction paths 2026-07-07 02:39:54 -07:00
Sanidhya Singh fff2408961 fix(agent): dedupe Codex gpt-5.5 autoraise notice across agent inits
The Codex gpt-5.5 compaction-threshold autoraise notice re-fired on every
agent init. Because the gateway rebuilds the agent per inbound message, the
notice spammed long-running Discord/Telegram/etc. sessions, and the only
documented remedy (`compression.codex_gpt55_autoraise false`) disables the
useful autoraise behavior itself.

Gate both emission surfaces — the CLI startup print and the gateway
`_compression_warning` replay — on a persisted per-profile marker under
`$HERMES_HOME` (`.codex_gpt55_autoraise_notice`), keyed on the from→to
percentages the notice displays. The notice now shows at most once per
profile; the autoraise still fires and `codex_gpt55_autoraise: false` still
disables it; and a later change to the raised threshold re-notifies once.
Docs updated to match.
2026-07-06 12:46:20 -07:00
Teknium d4bcd93bb9
docs: browser provider plugin guide + complete the plugin routing map (#59817)
- New developer-guide/browser-provider-plugin.md: BrowserProvider ABC
  (session lifecycle, CDP contract, bb_session_id back-compat key,
  raise/never-raise split between create and close/cleanup),
  get_setup_schema() hermes-tools integration, discovery, checklist.
  Closes the one gap in the provider-plugin family — the ABC and
  ctx.register_browser_provider() existed with zero docs.
- Register the page in the Plugins sidebar subcategory.
- Extend the routing map on the Plugins landing page (both locales)
  with the previously missing rows: web-search, browser, secret-source,
  and dashboard-auth surfaces.
2026-07-06 12:43:03 -07:00
Teknium b24ff550c3
docs: Plugins subcategory under Extending + secret-source plugin guide + 1Password sidebar fix (#59613)
* docs(secrets): secret-source plugin developer guide + sidebar registration for 1Password page

- New developer-guide/secret-source-plugin.md: SecretSource contract
  (never raises/prompts, fetch-only, timeout budget), framework-vs-plugin
  ownership table, mapped-vs-bulk shape guidance, run_secret_cli()
  subprocess-safety, registration + timing note, conformance kit usage,
  ErrorKind reference.
- Register user-guide/secrets/onepassword in the sidebar (page shipped
  in #59498 but was not listed, so it was unreachable from nav).
- Cross-link the user-guide plugin section to the new dev guide.

* docs: group all plugin guides under a Plugins subcategory in Extending

- Move guides/build-a-hermes-plugin.md -> developer-guide/plugins/index.md
  (both locales) and make it the category landing page (slug pinned to
  /developer-guide/plugins).
- New sidebar subcategory Developer Guide > Extending > Plugins holding
  the general guide + all 8 provider-plugin docs (llm-access, memory,
  context-engine, secret-source, model, image-gen, video-gen, web-search);
  provider-doc URLs unchanged.
- Client redirect /guides/build-a-hermes-plugin -> /developer-guide/plugins.
- Update 30 cross-links across both locales.
2026-07-06 12:11:31 -07:00
Teknium d84a2af3d4
docs: correct Python support to 3.11-3.13 (#59572)
requires-python is >=3.11,<3.14, so '3.11+' was misleading (implied
3.14+ works). Fixed in CONTRIBUTING.md and the docs-site mirror.
2026-07-06 04:38:07 -07:00
Teknium 1c156736dc
docs: warn that mid-session model switches break prompt caching (#58747)
/model switches, primary-model fallback, and credential-pool key
rotation all change the prompt-cache key (model and/or account), so
the next turn re-reads the entire conversation at full input price.
Add cost warnings everywhere docs recommend or describe these paths:

- reference/slash-commands.md: cost note on both /model rows
- user-guide/features/fallback-providers.md: warning admonition
- user-guide/features/credential-pools.md: warning admonition
- user-guide/configuring-models.md: mid-session switch warning
- guides/tips.md: expand cache tip + /model tip
- reference/faq.md: warning on the switch-back-and-forth example
- user-guide/desktop.md: composer picker bullet
- developer-guide/context-compression-and-caching.md: new
  cache-aware design pattern (model identity is part of the key)
2026-07-05 04:34:05 -07:00
teknium1 c6dc7c03c3
Revert "Merge pull request #30179 from NousResearch/feat/iron-proxy"
This reverts commit 8790adc4c6, reversing
changes made to fe5054bccf.
2026-07-04 13:38:59 -07:00
Teknium 8790adc4c6
Merge pull request #30179 from NousResearch/feat/iron-proxy
feat(egress): iron-proxy credential-injection firewall for sandboxes
2026-07-04 13:29:23 -07:00
Shashwat Gokhe 86a0c5553e feat: allow suppressing Codex gpt-5.5 autoraise notice 2026-07-04 18:55:27 +05:30
teknium1 14cbbd541e
Merge remote-tracking branch 'origin/main' into iron-proxy-followups
# Conflicts:
#	hermes_cli/config.py
#	hermes_cli/main.py
#	website/docs/reference/cli-commands.md
2026-07-04 03:09:43 -07:00
teknium1 86fcb2fe5f
feat(egress): first-class x-api-key providers + hot reload via management API
Both wired against features the iron-proxy author (@mslipper) confirmed on
PR #30179 — and both verified present in the pinned v0.39.0 source.

Header-auth providers (match_headers):
- New _HEADER_AUTH_PROVIDERS: Anthropic native (x-api-key), Azure OpenAI
  (api-key on *.openai.azure.com / *.cognitiveservices / *.services.ai),
  Gemini (x-goog-api-key + ?key= query param via match_query).
- TokenMapping grows match_headers + alias_env_names; per-provider header
  sets flow into the secrets rules; mappings.json roundtrips them
  (legacy files load with the Authorization default).
- GEMINI_API_KEY / GOOGLE_API_KEY collapse into ONE mapping (two
  require-rules on the same host would reject each other); the sandbox
  gets the token under both names, and the proxy child env mirrors the
  alias into the canonical name when only the alias is set.
- Docker backend injects alias env names alongside canonical ones.
- The fail-closed tier is now empty, so fail_on_uncovered_providers and
  discover_blocked_providers are deleted (dead toggle otherwise);
  _NON_BEARER_PROVIDERS shrinks to genuinely-unswappable signature auth
  (AWS SigV4, GCP service-account OAuth) — warn-only, as before.

Management API (hot reload):
- Generated proxy.yaml enables the v0.39 management listener: loopback
  only at tunnel_port+2, bearer key from HERMES_IRON_PROXY_MGMT_KEY.
- Key minted at setup (management.token, 0600); start_proxy injects it
  (v0.39 refuses to start when api_key_env is empty).
- hermes egress reload -> POST /v1/reload: re-reads proxy.yaml and
  atomically swaps the pipeline; 422 leaves the running ruleset
  untouched; actionable errors for not-running / pre-management config /
  key mismatch. Secrets changes still require restart (daemon env is
  read at spawn) — the CLI says so.

Validation: 218/218 unit+CLI+docker tests; 3/3 gated live E2E against the
real v0.39.0 binary (Authorization swap, x-api-key swap, live reload with
token rotation on the same pid). Docs updated.
2026-07-04 02:49:31 -07:00
Teknium 8d78be5460
revert: back out prompt_caching.enabled toggle (#56105) for re-evaluation (#56126)
* Revert "fix(caching): honor prompt_caching.enabled across model switch + fallback"

This reverts commit 36f9f50145.

* Revert "fix: allow disabling prompt caching"

This reverts commit c1c1a12fe6.
2026-07-01 00:20:32 -07:00
teknium1 36f9f50145 fix(caching): honor prompt_caching.enabled across model switch + fallback
@janrenz's PR #35862 added prompt_caching.enabled=false at init only. But
_anthropic_prompt_cache_policy re-derives _use_prompt_caching on every /model
switch (agent_runtime_helpers) and fallback-model swap (chat_completion_helpers),
which re-enabled markers and re-broke the strict proxy the toggle was meant to fix.

Move the kill switch into anthropic_prompt_cache_policy so it returns (False, False)
on every path. Drop the now-redundant init-time override (kept @janrenz's isinstance
hardening on the cache_ttl read). Add policy-level tests + docs for the toggle.

Follow-up to salvaged PR #35862.
2026-07-01 00:10:42 -07:00
Teknium 643b0dc678
fix(cron): raise default pre-run script timeout from 120s to 1h (#55489)
Cron pre-run scripts were capped at 120s by default, which surprised
users running long data-collection scripts on crons (the whole point of
crons being to offload long work). Raise _DEFAULT_SCRIPT_TIMEOUT to 3600s
(1 hour).

This bounds the script only — skill/agent jobs already run on a separate
inactivity budget (HERMES_CRON_TIMEOUT, default 600s idle, 0=unlimited),
not a wall-clock cap. Scripts dispatch to a persistent thread pool and do
not hold the tick lock, so a long script doesn't starve other due jobs.

Docs clarified to make the script-vs-agent timeout distinction explicit.

env/config overrides (HERMES_CRON_SCRIPT_TIMEOUT,
cron.script_timeout_seconds) unchanged and still take precedence.
2026-06-30 01:00:39 -07:00
Teknium c6c1fd8b6b
docs: create dev venv outside the source tree (root-cause fix for #7779) (#54862)
A manually-installed venv inside the cloned repo can be destroyed by the
agent running a relative-path command against its own checkout (rm -rf venv,
uv venv venv, etc.), silently wiping the running runtime mid-session. Moving
the canonical manual-install venv to ~/.hermes/venvs/hermes-dev means no
relative path from the agent's workspace resolves to its own runtime, making
the bug class impossible without any command-detection code.

Closes the root cause of #7779. The managed install.sh layout is unchanged.
2026-06-29 10:00:37 -07:00
Ben Barclay e1f4098b9f
docs(cron): document explicit per-channel delivery targets for all platforms (#54630)
The cron delivery table only showed Discord/Telegram with explicit
target syntax and described Slack and every other platform as
home-channel-only. In fact the generic platform:<target> routing in
_resolve_single_delivery_target resolves explicit targets for every
platform: Slack (#channel / channel ID / channel:thread_ts), Matrix
(room/user IDs), Feishu (chat:thread), WhatsApp (JID / E.164), Signal
(group / E.164), SMS, Email, and Weixin all have dedicated explicit-
target branches in _parse_target_ref; the remaining platforms accept a
generic platform:<chat_id> passthrough.

Update the Delivery Model table (en + zh-Hans) to show the real
per-platform syntax, document #channel name resolution via the channel
directory, and note the Slack thread_ts nuance. Docs-only.
2026-06-29 15:23:16 +10:00
Teknium b31b0b9d95
docs: reconcile docs with code across last 3 releases (#54254)
Audited the last 3 releases (v2026.5.28..main) against the docs site and
fixed code-vs-docs drift:

- slash-commands: add /moa, /prompt, /pet, /hatch, /timestamps
- cli-commands: add hermes pets / project / desktop / whatsapp-cloud +
  dashboard register; correct --insecure (now a deprecated no-op);
  add gateway migrate-legacy + enroll --wake-url + dashboard --skip-build
- environment-variables: document the remaining ~48 env vars (SimpleX,
  Photon, Teams adapter, per-platform *_ALLOW_ALL_USERS, home-channel vars,
  IRC, Brave/Krea/Notion/Linear/Airtable/Tenor keys, QQ_SANDBOX) — full
  OPTIONAL_ENV_VARS (265) now covered
- configuration: document tool_loop_guardrails, goals, prompt_caching,
  network, onboarding, dashboard config blocks
- toolsets/tools-reference + tools.md: add coding/project toolsets and
  read_terminal/project_* tools; remove the stale messaging toolset and
  send_message agent tool (removed in #47856); drop stale RL-training prose
- messaging: new IRC channel page (adapter shipped without docs) + index
  row + sidebar + env vars
- pets: document the /hatch AI generation pipeline + Nous/OpenRouter image
  backend
- web-dashboard: document the bearer-token / TokenPrincipal service auth path
- purge agent-callable send_message references across guides/features and
  the research-paper-writing skill (tool removed in #47856)

Verified: docusaurus build succeeds; all authored internal links resolve.
2026-06-28 12:47:50 -07:00