Commit graph

1379 commits

Author SHA1 Message Date
Teknium
3910ab28c0 feat(skills): update simplify-code to track upstream /simplify evolution (4th altitude reviewer, inline fallback)
Claude Code's /simplify was renamed away, community-restored, and rebuilt
since our 3-agent port (v2.1.63 -> v2.1.154+). This brings simplify-code
in line with the current upstream design, re-expressed in our own wording
and layered onto our existing risk-tier/confidence machinery:

- New Reviewer 4 (Altitude): flags band-aid fixes layered on shared
  infrastructure — special cases, symptom patches with unfixed sibling
  sites, workaround stacks — and points at the deeper mechanism fix.
  Matches our AGENTS.md 'fix the class, not the site' rubric.
- Explicit cleanup-vs-bug-hunt boundary: this skill improves working
  code; correctness review stays with requesting-code-review.
- Inline single-pass fallback when delegate_task is unavailable (leaf
  subagents, delegation disabled) — previously the skill just broke;
  now all four angles run sequentially with honest disclosure.
- Finding format gains a concrete-cost field.
- Efficiency reviewer adds closure-capture scope-retention leaks.
- Pitfalls: fan-out cap 3 -> 4, band-aid-vs-deliberate-boundary caveat,
  no-bug-hunting drift guard.

Kept our value-adds upstream lacks: SAFE/CAREFUL/RISKY tiers,
Chesterton's Fence via git blame, dry-run/focus/scope modifiers.
2026-07-23 20:05:44 -07:00
teknium1
df1464ef95 docs(jupyter-notebook): mirror new zmq/xsrf pitfalls to docs pages
Regenerate the auto-gen skill page and translate pitfalls 9-10 into the
zh-Hans mirror, following the cherry-picked pitfall additions.
2026-07-23 20:01:28 -07:00
teknium1
9a4d1a0130 refactor(skills): move yuanbao to optional-skills
The yuanbao skill is platform-specific guidance for Tencent Yuanbao
group chats — niche for the default bundled set. Per the
'when in doubt, optional' rule, ship it as an optional skill.

Install via: hermes skills install official/yuanbao/yuanbao
2026-07-23 19:47:46 -07:00
Teknium
0728f21613 docs(skills): sharpen nano-pdf description to contrast with pdf skill
The nano-pdf description repeated the skill name ('via nano-pdf CLI'),
violating the skill authoring standard, and spent its char budget
restating the name instead of distinguishing the skill from the
structural pdf skill. New description: 'Edit text in existing PDFs via
natural-language prompts.' (56 chars) — no name repetition, and
'text in existing PDFs' contrasts cleanly with pdf's
'Create, merge, split, fill, and secure PDF files.'

Propagated to the auto-generated docs pages and zh-Hans locale
(catalog row + per-skill page) for locale parity.
2026-07-23 19:47:44 -07:00
teknium1
832d691627 refactor(skills): move segment-anything to optional-skills
Per the 'when in doubt, optional' rule — zero-shot image segmentation
via SAM is a niche computer-vision capability with heavy deps
(torch, transformers), not something most users load.

Install via: hermes skills install official/mlops/segment-anything
2026-07-23 19:47:34 -07:00
teknium1
ba9277cc3e refactor(skills): move heartmula + audiocraft to optional-skills/creative
Both are heavy-dep, GPU-bound local music-generation skills (8-16GB VRAM,
torch stacks, manual source patches) that were bundled in two different
categories (media/ and mlops/models/). Per the 'when in doubt, optional'
rule they now sit side by side in optional-skills/creative/, and the
bundled songwriting-and-ai-music skill points at them for local generation.

- git mv skills/media/heartmula -> optional-skills/creative/heartmula
- git mv skills/mlops/models/audiocraft -> optional-skills/creative/audiocraft
- cross-link related_skills both ways + songwriting-and-ai-music
- new section 10 in songwriting-and-ai-music with install commands
- docs: bundled pages -> optional pages (en + zh-Hans), catalogs, sidebar

Install via:
  hermes skills install official/creative/heartmula
  hermes skills install official/creative/audiocraft
2026-07-23 19:47:21 -07:00
Teknium
e3d524b482 refactor(skills): restructure hermes-agent into a hub + absorb themes, desktop-plugins, tui-widgets, petdex
The hermes-agent skill body was a 51KB monolith loaded in full on every
trigger. It is now a lightweight hub (~12KB): identity, quick start, the
tmux orchestration guide (kept in-body — the autonomous-ai-agents category
contract), surface orientation, and hard invariants, with a routing table
into 18 reference files that carry the depth.

Absorbed four Hermes-specific skills as first-class references so their
content gains a discoverable home and room to grow without bloating any
primary body:

- skills/hermes-themes            -> references/themes.md + templates/skin.yaml
- skills/hermes-desktop-plugins   -> references/desktop-plugins.md + templates/plugin.js
- skills/productivity/tui-widgets -> references/tui-widgets.md + templates/clock.mjs
- skills/productivity/petdex      -> references/petdex.md

New references extracted from the old body: cli-reference, slash-commands,
providers-and-models, configuration, project-context-files,
security-privacy, background-systems, windows-quirks, troubleshooting,
contributor-guide (native-mcp and webhooks already existed). Also commits
delegate-task-concurrency-diagnosis and portal-auth-for-third-party-apps,
which the old body referenced but the repo never shipped.

Description updated to cover the widened scope:
'Use, configure, theme, extend, and orchestrate Hermes Agent.' (60 chars)
2026-07-23 19:46:02 -07:00
Teknium
d4b6165018 fix(skills): move dogfood skill into the software-development category
skills/dogfood/SKILL.md sat at the root of the bundled skills tree,
making it one of the few uncategorized skills (Discord /skill
autocomplete listed it under 'uncategorized'; hermes_cli/commands.py
cited it as the example). Move it to
skills/software-development/dogfood/ alongside the other QA/testing
skills (test-driven-development, systematic-debugging,
requesting-code-review).

- git mv skills/dogfood -> skills/software-development/dogfood
  (history preserved)
- fix test fixture path in tests/tools/test_browser_console.py
- update root-level-skill docstring examples (commands.py,
  generate-skill-docs.py) to cite computer-use, which is still root-level
- drop 'dogfood' from the category list in hermes-agent-skill-authoring
  SKILL.md (en + zh-Hans docs mirrors)
- docs: regenerate/move the bundled page to
  software-development-dogfood (en + zh-Hans), update sidebars.ts,
  skills-catalog.md, and the adversarial-ux-test related-skills link

E2E validated: fresh sync_skills() copies to the new nested path;
existing installs with the old flat copy keep it untouched (manifest is
name-keyed, hash unchanged -> skipped, no duplicate);
_get_category_from_path resolves 'software-development'; docusaurus
build green.
2026-07-23 19:45:54 -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
4025329ac4
feat(gateway): opt-in compression progress notices via compression.progress_notices (#52995) (#70457)
Routine automatic compression stays silent-by-design on chat platforms
(default unchanged, byte-identical). New opt-in config key
compression.progress_notices (bool, default false) opens a gate on the
gateway noise filter (_prepare_gateway_status_message) that lets ROUTINE
compression progress statuses through to chat surfaces.

- Membership is derived from the #69550 status template constants in
  agent/conversation_compression.py (compiled to a literal-escaped regex,
  never re-inlined wording), so unrelated noisy statuses (aux failures,
  provider retry/rate-limit chatter) stay suppressed even when enabled.
- The compaction completion notice (COMPACTION_DONE_STATUS, #69546
  lifecycle 'compacted' edge) already flows through the status path and
  passes the filter — no new emit site needed.
- Config wired everywhere: hermes_cli/config.py DEFAULT_CONFIG,
  cli-config.yaml.example, gateway raw-YAML read (live, mtime-cached),
  gateway hot-reload cache-busting key list, website configuration docs.
- Failure notices and manual /compress feedback remain always-visible;
  VISIBLE_COMPRESSION_MESSAGES and emit sites untouched.

Design by @havok-training (issue #52995).
2026-07-23 19:44:19 -07:00
teknium1
2487dea9ce refactor(skills): move jupyter-live-kernel to optional-skills as jupyter-notebook
Per the 'when in doubt, optional' rule — the live-kernel workflow needs
uv + JupyterLab + a running server + a cloned hamelnb repo, a niche
setup that shouldn't ship active by default.

Renamed jupyter-live-kernel -> jupyter-notebook (skill name, page slugs,
catalogs, sidebar, zh-Hans mirror, darwinian-evolver related_skills).

Install via: hermes skills install official/data-science/jupyter-notebook
2026-07-23 18:36:49 -07:00
Teknium
850f576f3d feat(moa): add every_n fanout cadence with cached-guidance reuse
Extends the fanout enum with 'every_n:<N>' (N >= 2): advisors run on the
first iteration of each user turn and every Nth tool iteration after it;
off-cadence iterations REUSE the cached guidance from the last on-cadence
run via the same cache mechanism the user_turn fanout uses, so the
aggregator still gets advice on every step. The cadence counter is scoped
per user turn (resets on a new user message) and only advances when the
advisory state actually changes, so streaming retries never consume a
cadence slot. Mapping form {mode: every_n, n: N} normalizes to the
canonical string. Unknown/degenerate values fall back to per_iteration.

Addresses issue #63393 (advisor fan-out multiplies turn latency/cost by
the tool-iteration count). Redesigned from PR #63448: the submitted shape
skipped references entirely on off-cadence iterations (aggregator ran
advice-less); this version keeps the last advice in play, credited for
the idea and cadence framing.

Config-gated, default-off (default fanout remains per_iteration).

Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
2026-07-23 17:50:40 -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
Kolektori
cb481e2f2b feat(compression): proactive tool-result pruning for large-window models
The phase-1 tool-result prune only runs inside compress(), which fires
near 50% of the context window, so it never triggers on large-window
models; old tool outputs then ride in history and are re-sent every turn.

Add prune_tool_results_only(): the same no-LLM prune on a separate, low
proactive_prune_tokens trigger, run as an elif to the compression branch.
Opt-in (default 0), protects the recent tail by message count.

Add the method to the ContextEngine base as a no-op default so pluggable
engines inherit it safely (the post-tool-call path never AttributeErrors on
a non-built-in engine); the built-in compressor supplies the real prune.
Register both keys under the top-level compression config with defaults and
document them.
2026-07-23 16:44:12 -07:00
2001Y
0a5d8a16fc feat(slack): support long app descriptions in the manifest generator
Add --long-description / --long-description-file to `hermes slack
manifest` so the generated app manifest can carry Slack's
display_information.long_description (175–4,000 characters), with
validation of the length bounds, mutual-exclusion with --slashes-only,
and UTF-8 file input. Also propagate the manifest command's exit status
through cmd_slack so validation failures reach the shell.

Squash of the two commits from PR #65256 — one commit per contributor
on this salvage branch.

Salvaged from #65256
2026-07-23 12:01:24 -07:00
Teknium
6ced760a3d test(gateway): cover gateway.api_server YAML discovery + extra bridge; docs: document config.yaml support
Follow-up for salvaged PR #66633 (fixes #66630):
- 5 tests: nested gateway.api_server discovery, port/key/host/model_name
  bridged into extra, explicit extra wins over top-level key, non-platform
  gateway keys (streaming/timeout) not misparsed, gateway.platforms.api_server
  path regression-guarded.
- docs: api-server.md (en + zh-Hans) now documents the gateway.api_server
  YAML section instead of 'not yet supported'.
2026-07-23 11:54:32 -07:00
Teknium
558dab0e3e feat(slack): opt-in reaction triggers, removed events, hooks, channel handoff
Build the full reaction pipeline on top of the #29916 base:

- Opt-in gate: slack.reaction_triggers (default OFF — reaction events
  stay acked-and-dropped so busy channels don't wake the agent on every
  emoji). 'true' routes reactions on the bot's OWN messages; an explicit
  emoji-name list routes those emojis from any message (handoff flows).
- reaction_removed events now route too, distinguished by the
  cross-platform text convention reaction:added:<emoji> /
  reaction:removed:<emoji> (matches the Feishu and Photon adapters, so
  agents and skills see one shape everywhere).
- Authorization: the reactor becomes the synthesized message's user, so
  the early _is_user_authorized gate and allowed_channels whitelist
  apply exactly as for typed messages. _hermes_force_process only skips
  the mention requirement (a reaction on the bot's own message is
  definitionally addressed to the bot), mirroring Feishu/Photon.
- Gateway hooks (#33111 by @johnkattenhorn): every human reaction on a
  message item fires reaction:added / reaction:removed through the new
  BasePlatformAdapter.set_reaction_handler → GatewayRunner
  ._handle_reaction_event → HookRegistry.emit, independent of the
  routing opt-in. Documented in hooks.md.
- Channel handoff (#45265 by @Kev-fs): slack.reaction_trigger_target
  routes the reaction turn to a configured channel (top-level via
  _hermes_no_thread_response + reply-anchor suppression in
  gateway/platforms/base.py) or C123:<ts> thread.
- Manifest: reaction_removed event subscription added alongside
  reaction_added/reactions:read.
- Docs: slack.md Reaction Triggers section; hooks.md event table rows.

Also credits #44508 by @harrisonmedmedmetrics (inbound reaction_added
handling — same plumbing class, superseded by this consolidated shape).

Co-authored-by: johnkattenhorn <john.kattenhorn.personal@gmail.com>
Co-authored-by: Kev-fs <kevin@fleetsmarts.net>
Co-authored-by: harrisonmedmedmetrics <harrison@medmetricsrx.com>
2026-07-23 11:50:08 -07:00
Teknium
6d4f7f4049 docs(slack): gap pass — mention-gating decision table, allow_bots deep dive, clarify buttons, ephemeral slash replies, cron/DM targeting
Documents user-facing wave-1+2 Slack behavior that had no docs coverage:
- decision table for require_mention / free_response_channels /
  require_mention_channels / thread_require_mention / strict_mention /
  ignore_other_user_mentions and how they compose
- 'Accepting messages from other bots' section with the post-#69483
  semantics: allow_bots=mentions requires a CURRENT mention from
  peer bots (text or Block Kit blocks); thread state never admits them
- clarify one-tap buttons (choice buttons + Other free-text mode,
  in-place resolution, double-click guard, expiry message)
- slash replies are ephemeral: replace-ack, chunking, 5-post cap with
  explicit truncation notice, postEphemeral fallback, never-public rule
- cron deliver targeting (slack -> home channel, slack:C... channel,
  slack:U... resolved to DM) incl. standalone sender + MEDIA uploads
- send_message media + bare-user-ID DM resolution and caption behavior

Refs #26184.
2026-07-23 11:31:30 -07:00
navahc09
2b226be8e3 docs(messaging): document the '!' prefix for Slack thread commands
Salvaged from #45765 by @navahc09 — kept the PR's callout structure
and placement, rewrote the content to match current behavior:
Slack blocks native slash commands in threads and never delivers them,
so Hermes recognises a leading '!' as an alternate command prefix.
Post-C3 command fixes the bang form also works behind a mention
(@Hermes !cmd) and with leading whitespace; unknown '!' tokens pass
through to the agent unchanged. Cross-linked the detailed
slack.md section.
2026-07-23 11:31:30 -07:00
haran2001
4f7109551a docs(slack): document bot message handling 2026-07-23 11:31:30 -07:00
trkim
47bbc12e18 fix(kanban): isolate delegated children from parent task 2026-07-23 07:33:36 -07:00
kshitij
ca9c30c7f0 fix(gateway): bound hygiene compression failures 2026-07-23 07:26:27 -07:00
Teknium
f31c10a0da test/docs(slack): force-clear new gating env vars; document new options
- Add SLACK_THREAD_REQUIRE_MENTION, SLACK_IGNORE_OTHER_USER_MENTIONS and
  SLACK_REQUIRE_MENTION_CHANNELS to the conftest behavioral-env force-clear
  list so config-loader side effects can't leak between tests (same class
  of leak the existing SLACK_* entries guard against).
- Document thread_require_mention and require_mention_channels in the
  Slack messaging guide next to the other mention-gating options.
2026-07-22 21:14:44 -07:00
LeonSGP43
0bbf679e4c test(slack): add peer-agent smoke target 2026-07-22 21:14:44 -07:00
Ben Phillips
f50e1e9c51 docs(slack): document ignore_other_user_mentions
Adds the option to the Mention & Trigger Behavior section: leading-
mention semantics, opt-in default, env var, and DM/MPIM scope (1:1 DMs
unaffected; MPIMs apply it like channels).

Claude-Session: https://claude.ai/code/session_01TKsNdptNdo9CqT2u7JMdkH
2026-07-22 21:14:44 -07:00
brooklyn!
9024835bf2
Merge pull request #69602 from NousResearch/bb/voice-interrupt-note
feat(voice): tell the model when the user interrupts its spoken reply
2026-07-22 18:13:19 -05:00
brooklyn!
0ac07fdafd
Merge pull request #69511 from NousResearch/bb/voice-streaming
feat(voice): streaming, conversational TTS with barge-in across all surfaces
2026-07-22 18:13:08 -05:00
Brooklyn Nicholson
177a57f70c feat(voice): desktop flags interrupted submits
markVoicePlaybackInterrupted() / takeVoicePlaybackInterrupted() mirror
the backend latch in the renderer (the barge happens client-side, where
the audio plays). VAD barges and typing over playback mark it; the next
prompt.submit carries interrupted:true, which the TUI gateway latches
into the model note.
2026-07-22 17:53:06 -05:00
Brooklyn Nicholson
83456d0bc6 docs(voice): streaming TTS + barge-in across surfaces 2026-07-22 17:47:25 -05: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
Brooklyn Nicholson
79b047820f docs: describe active-turn redirect busy-input behavior
Update the CLI and messaging guides so the default `interrupt` mode reflects
the new behavior: a follow-up redirects the active turn (preserving displayed
reasoning and completed work, letting running tools finish at a safe boundary)
rather than hard-stopping it, with `/stop` still the explicit hard stop.
2026-07-22 12:10:58 -05: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
DanielMaly
e5078e3152 feat(compression): add absolute token threshold via compression.threshold_tokens
Add compression.threshold_tokens config option that sets an absolute
token cap for auto-compaction. When configured alongside the existing
ratio-based threshold, the effective trigger point is the lower of the
two, so compression never fires later than the user's preferred token
count regardless of which model is active.

This solves the problem where switching between models with different
context windows (e.g. 1M → 400K) shifts the absolute trigger point,
causing premature or delayed compression.

Rework from PR #24279 addressing sweeper feedback:
- The cap is now a first-class compressor configuration value
  (threshold_tokens_cap parameter on ContextCompressor.__init__),
  not a post-construction patch on the live instance.
- Applied in both __init__ and update_model() so it survives model
  switches and fallback activations (the old approach was undone by
  update_model() restoring _configured_threshold_percent).
- Clamped to the model's context length so a cap above the window is
  a no-op (ratio-based threshold wins).
- Works with max_tokens output-token reservations.
- Added 9 tests covering cap-vs-ratio selection, model switch survival,
  context-length clamping, max_tokens interaction, and invalid values.
- Updated user-facing configuration docs.
- Removed unrelated background-review/curator/Honcho changes (main
  already contains background-review memory isolation in 973f27e95).

Config example:
  compression:
    threshold: 0.50
    threshold_tokens: 200000   # never compress later than 200K tokens
2026-07-22 08:12:14 -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
295e20358c
feat(delegation): let subagents use execute_code (#69325)
Children inherit the parent's env, repo, and toolsets but were denied
execute_code ('children should reason step-by-step, not write scripts').
That forces subagents doing mechanical multi-step work (batch file
reads, fetch-N-pages loops, filter-before-context reductions) to burn
reasoning iterations one tool call at a time.

- Remove execute_code from DELEGATE_BLOCKED_TOOLS
- Stop stripping the code_execution toolset from child bundles
- No recursion risk: the sandbox bridges only the 7 SANDBOX_ALLOWED_TOOLS
  (web/file/terminal) — delegate_task and execute_code itself are not
  reachable from inside a sandbox script
- Update schema text, AGENTS.md, and delegation docs
- Tests: blocked-constant, strip, and child-assembly tests updated;
  new test pins execute_code as intentionally unblocked
2026-07-22 06:58:45 -07:00
Ben Barclay
7857d8737c feat(dashboard-auth): RFC 8252 native desktop sign-in (system browser + PKCE, no webview/cookies)
The Desktop app can now sign in to a gated gateway using the user's SYSTEM
browser and OAuth 2.0 for Native Apps (RFC 8252) instead of an embedded
Electron BrowserWindow, and authenticates with bearer tokens it holds itself
instead of relying on HttpOnly browser session cookies.

Why brokered: the upstream IDP (Nous Portal) binds client_id to the gateway
instance and only permits redirect_uris on the gateway's own origin, so a
desktop loopback redirect can't be a direct Portal client. The gateway
therefore acts as the authorization server TO the desktop and an OAuth client
TO the Portal, reusing the existing PKCE start_login/complete_login provider
path unchanged.

Server (Ben's dashboard-auth lane):
- native_flow.py: in-memory broker — binds the desktop's PKCE challenge to a
  completed Session, mints a single-use, short-TTL, PKCE-verified gateway
  authorization code. Constant-time compare, single-use (consumed before the
  PKCE check so a wrong verifier can't be retried), capacity-bounded.
- routes.py: GET /auth/native/authorize (starts the brokered PKCE login,
  loopback-only redirect_uri, S256-only), POST /auth/native/token (loopback
  code + verifier -> tokens in the JSON body, never Set-Cookie), POST
  /auth/native/refresh (desktop-held RT rotation). /auth/callback branches to
  mint a loopback code + 302 to 127.0.0.1 when a broker_state rides the PKCE
  cookie; the cookie/SPA path is untouched.
- middleware.py: the gate accepts Authorization: Bearer <access_token>,
  verified via the same verify_session provider stack (no cookie set/read),
  with the same "provider unreachable -> 503, not logout" semantics.
- web_server.py /api/status: advertise auth_flows (["cookie","native_pkce"])
  so clients can detect the capability; native_pkce only when a brokerable
  OAuth provider is registered.

Desktop (Ben's lane):
- native-oauth.ts: pure PKCE/capability/URL/callback/token helpers.
- native-oauth-login.ts: loopback-listener orchestration (system browser via
  openExternal, ephemeral 127.0.0.1 listener, state/PKCE verification), all
  I/O injected for testability.
- main.ts: capability-gated oauth-login IPC — native flow when advertised,
  automatic fallback to the existing embedded-webview cookie flow otherwise;
  tokens stored encrypted (safeStorage/OS keychain), REST + ws-ticket
  authenticated by bearer, transparent refresh, logout clears both shapes.

Tests: 18 server pytest (broker unit + full authorize->callback->token E2E +
cookieless bearer auth of a gated route + ws-ticket mint + capability
advertisement + refresh); desktop node --test/vitest for both pure modules
(PKCE, capability detection, callback CSRF, loopback round trip, timeout,
browser-open failure). Electron project typechecks clean.

Docs: website/docs/guides/desktop-native-signin.md.
2026-07-22 06:50:50 -07:00
Andy
e89216e7f5 fix(secrets): harden encrypted Bitwarden cache 2026-07-22 04:40:07 -07:00
Teknium
4f0ee4d3ff feat(secrets): rework command source as a registered SecretSource — no provider selector
Reworks the salvaged command module into a CommandSource(SecretSource)
registered as the third bundled source, composing with Bitwarden and
1Password through the apply_all() orchestrator — enable any combination
simultaneously.  The original PR's secrets.provider single-selector is
deliberately dropped: multi-source is first-class and a mutually
exclusive provider switch would regress that.

- fetch() only fetches; precedence/override/conflicts/environ writes stay
  in the orchestrator.  ErrorKind classification + remediation hints.
- apply_command_secrets() kept as a legacy shim (parser/security helpers
  unchanged: HERMES_SECRET_KEY data-only key passing, cross-key misroute
  guard, base64-padding disambiguation, timeout + output cap, structured-
  fields-only failure logging, stderr discarded).
- Dispatch tests rewritten for the registry path incl. an explicit
  two-sources-compose test; selector tests removed with the selector.
- cli-config.yaml.example + docs page (command.md), secrets index entry.
- contributors mapping for mvalentin@valensys.net -> 0xr00tf3rr3t.
2026-07-22 04:39:28 -07:00
Teknium
86fb046383
feat(secrets): orchestrator-level preserve_existing + profile aliasing (#69058)
Fixes the profile-clobber bug cluster at the apply_all() chokepoint so
every secret source — bundled and plugin — gets both behaviors for free:

- secrets.preserve_existing (#58073): env var names whose existing .env /
  shell value always wins, even against a source with
  override_existing: true.  Escape hatch for per-profile platform
  secrets while everything else rotates centrally.
- Profile aliasing (#51447): under a named profile, an applied
  FOO_<PROFILE> var (credential-shaped suffixes only) also hydrates the
  canonical FOO, so adapters/plugins that read fixed env names see the
  profile's value.  Direct supply beats alias; protected/claimed/
  override guards all apply; secrets.profile_alias: false disables.

Reimplements the intent of PR #58085 (tianma-if, preserve_existing on the
legacy Bitwarden apply shim) and PR #51616 (LeonSGP43, profile aliasing
inside the Bitwarden backend) on the SecretSource orchestrator that
superseded those code paths.

Fixes #58073.  Fixes #51447.

Co-authored-by: tianma-if <5895871+tianma-if@users.noreply.github.com>
Co-authored-by: LeonSGP43 <154585401+LeonSGP43@users.noreply.github.com>
2026-07-22 03:20:45 -07:00
izumi0uu
8e089db689 fix(secrets): validate bitwarden status token
Keep the env-presence row, but add a real Bitwarden probe so revoked or malformed tokens no longer look healthy in hermes secrets bitwarden status.

Also document the new status behavior and lock it in with a dedicated regression test.

Refs: NousResearch/hermes-agent#40275
Tested: ./scripts/run_tests.sh tests/hermes_cli/test_bitwarden_status.py tests/test_bitwarden_secrets.py
Tested: .venv/bin/python -m ruff check hermes_cli/secrets_cli.py tests/hermes_cli/test_bitwarden_status.py
2026-07-22 03:20:09 -07:00
Naveen Fernando
f7c198dec8 docs: clarify OpenViking local setup
(cherry picked from commit a6807170f1)
(cherry picked from commit 6fb4e9aa8a)
2026-07-22 14:05:28 +05:30
Brooklyn Nicholson
625687f334 feat(status-bar): add /battery toggle for a color-coded battery read-out
Add an opt-in battery indicator to the CLI and TUI status bars, shown as
the first element and colour-coded by charge (green/yellow/orange/red, or
green while charging). Off by default and a no-op on machines without a
battery.

- agent/battery.py: shared psutil-backed reader with a short TTL cache,
  category bucketing, and a compact 🔋/ label. Fails open to
  "unavailable" everywhere.
- CLI: /battery [on|off|status] toggle persisted to display.battery,
  rendered first in every status-bar width tier.
- TUI: /battery slash command, config sync, a system.battery RPC polled
  while enabled, and a pinned first segment in StatusRule.
2026-07-21 13:54:11 -05:00
brooklyn!
11ae6bf0e3
Merge pull request #68725 from SHL0MS/fix/desktop-stop-parks-queue
Some checks failed
CI / Detect affected areas (push) Waiting to run
CI / Python tests (push) Blocked by required conditions
CI / Python lints (push) Blocked by required conditions
CI / JS & TS checks (push) Blocked by required conditions
CI / Desktop E2E (push) Blocked by required conditions
CI / Docs Site (push) Blocked by required conditions
CI / Deny unrelated histories (push) Blocked by required conditions
CI / Check contributors (push) Blocked by required conditions
CI / Check uv.lock (push) Blocked by required conditions
CI / package-lock.json diff (push) Blocked by required conditions
CI / Lint Docker scripts (push) Blocked by required conditions
CI / Build&Test Docker image (push) Blocked by required conditions
CI / Supply-chain scan (push) Blocked by required conditions
CI / Review label gate (push) Blocked by required conditions
CI / OSV scan (push) Waiting to run
CI / CI review comment (live) (push) Blocked by required conditions
CI / All required checks pass (push) Blocked by required conditions
CI / CI timing report (push) Blocked by required conditions
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
auto-fix lint issues & formatting / Generate eslint --fix patch (push) Waiting to run
auto-fix lint issues & formatting / Apply patch (push) Blocked by required conditions
Build Skills Index / build-index (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
fix(desktop): Stop parks the queue instead of firing the next queued prompt
2026-07-21 10:54:58 -05:00
Austin Pickett
d355e0e71d
feat(desktop): configure repository discovery (supersedes #67630) (#68642)
* feat(desktop): configure repository discovery

* fix(config): preserve additive default migration

* fix(desktop): stabilize session-actions-menu gateway mock for repo-scan subscribe

projects.ts now runs $gateway.subscribe(syncReposScanning) at module load, and
nanostores fires the subscriber synchronously. session-actions-menu.test.ts
reaches projects.ts transitively via the session store but mocked
@/store/gateway without $gateway, crashing the whole desktop vitest suite
("No \ export is defined"). Simply adding $gateway: atom(null) exposed a
second issue: the synchronous subscriber calls the mock's activeGateway()
during the transitive import, before the module-level const initializes (TDZ).

Hoist the mock fns via vi.hoisted() so activeGateway is defined before the
hoisted vi.mock factory runs, and add $gateway: atom(null) to the mock. Mirrors
the self-contained mock pattern already used in projects.test.ts. Also maps the
PR author's commit email for attribution.

Supersedes #67630; incorporates review feedback from that PR.

Co-authored-by: Rudimar Ronsoni <rudimar@outlook.com>

---------

Co-authored-by: Rudimar Ronsoni <rudimar@outlook.com>
Co-authored-by: Austin Pickett <austinpickett@users.noreply.github.com>
2026-07-21 10:09:31 -04: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
Teknium
a31a31826c
fix(approval): raise gateway approval timeout to 300s, honest stale-tap UX, offer Always on mixed prompts (#68597)
Three related messaging-approval fixes:

1. approvals.timeout default 60 -> 300. PR #63501 collapsed the gateway
   wait onto the canonical approvals.timeout (previously
   gateway_timeout=300), silently shrinking messaging approval windows
   to 60s. Push-notification approvals routinely arrive later than a
   minute; taps landed after the wait had already failed closed.

2. Stale-tap honesty: adapters resolved the approval AFTER rendering
   '<checkmark> Approved by <user>' (Telegram/Discord/Slack), or ignored a zero
   resolve count (WhatsApp Cloud/Feishu). A tap on an expired prompt
   claimed approval while the command had already been denied. All
   button paths now resolve first and render 'Approval expired -
   command was not run' when nothing was waiting.

3. Mixed-warning prompts (dangerous pattern + tirith finding) now offer
   Always: the persistence layer already permanently allowlists the
   pattern key and downgrades the tirith key to session scope, but the
   UI hid Always whenever ANY tirith warning was present. Pure-tirith
   prompts still withhold Always (content findings are session-max by
   design), and Smart-DENY overrides remain once-only.
2026-07-21 05:41:41 -07:00
Teknium
afb7bf6a5a
feat(skills): bundle docx, xlsx, and pdf office skills; refresh powerpoint (#68595)
Non-technical users asking for Word docs, spreadsheets, or PDF work had
no bundled skill coverage — docx/xlsx creation required discovering and
installing hub skills, and PDF manipulation had no skill at all beyond
OCR extraction and nano-pdf edits.

- skills/productivity/docx: create (docx-js), edit (unzip -> XML -> zip),
  tracked changes, comments, validation. Adapted from anthropics/skills.
- skills/productivity/xlsx: openpyxl creation/editing, mandatory
  LibreOffice recalc gate, formula-compatibility rules, financial-model
  conventions. Points at optional excel-author for finance-grade work.
- skills/productivity/pdf: merge/split/rotate/watermark/encrypt, form
  filling (AcroForm + flat overlay scripts), text/table extraction,
  reportlab creation, forms.md + reference.md companions.
- skills/productivity/powerpoint: synced to current upstream pptx skill —
  richer pptxgenjs corruption footguns, template workflow, validate.py +
  validators + thumbnail.py, font-substitution QA guidance; drops the
  stale pack.py/editing.md/pptxgenjs.md workflow files.
- Cross-linked ocr-and-documents, nano-pdf, excel-author via
  related_skills so each office skill routes to its siblings.
- deliverable-mode docs mention the new skills; regenerated per-skill
  docs pages, catalogs, and sidebar.
- tests/skills/test_office_document_skills.py: frontmatter contracts,
  referenced-script existence, schema-map integrity, cross-link
  resolution, script compilation.

E2E validated: docx create->render->edit->validate, xlsx recalc
(SUM + _xlfn.TEXTJOIN evaluate correctly), pdf create->merge->extract,
pptx generate->validate->thumbnail.
2026-07-21 05:39:23 -07:00
Siddharth Balyan
b0da653ac8
fix(billing): rename user-facing "terminal billing" copy to Remote Spending (#68355)
* fix(billing): rename user-facing "terminal billing" copy to Remote Spending

The capability was renamed Remote Spending on the portal (consent CTA:
"Allow Remote Spending"; per-terminal states Granted/Stopped), but the
terminal, desktop, and docs still said "terminal billing" everywhere.

- Feature name: Remote Spending in titles/labels, lowercase mid-sentence.
- Step-up action verb is now "allow", matching the portal consent CTA.
- Kill-switch-off recovery copy points at the actual control ("a billing
  admin can turn it on from the portal's Hermes Agent page") instead of
  the dead-end "manage it on the portal".
- Per-terminal revoke copy uses the portal vocabulary ("stopped").
- Wire identifiers (cli_billing_enabled, cli_billing_disabled, ...) are
  unchanged; copy, comments, docs, and test expectations only.

* fix(billing): correct the post-step-up denial diagnosis + finish the desktop rename

Adversarial review findings: (1) a repeated insufficient_scope after a
successful step-up is a per-terminal authorization failure, but the copy
blamed the org kill-switch and pointed at the wrong recovery control —
now: "Remote Spending still isn't active for this terminal — the
authorization didn't take. Retry, or make this change on the portal."
(2) the desktop step-up flow started in Remote Spending vocabulary but
finished in "billing management access" — renamed both end states.
(3) prettier formatting on the touched files (matches the post-merge
fmt bot).
2026-07-21 12:20:25 +05:30