Commit Graph

2485 Commits

Author SHA1 Message Date
Teknium
52dd479214 Merge pull request #2361 from NousResearch/hermes/hermes-5d6932ba
feat(gateway): cache AIAgent per session for prompt caching
2026-03-21 16:53:21 -07:00
Teknium
c57d5cbdde fix(update): prompt before resetting working tree on stash conflicts (#2390)
When 'hermes update' stashes local changes and the restore hits
conflicts, the previous behavior silently ran 'git reset --hard HEAD'
to clean up. This could surprise users who didn't realize their
working tree was being nuked.

Now the conflict handler:
- Lists the specific conflicted files
- Reassures the user their stash is preserved
- Asks before resetting (interactive mode)
- Auto-resets in non-interactive mode (prompt_user=False)
- If declined, leaves the working tree as-is with guidance
2026-03-21 16:49:19 -07:00
Teknium
525caadd8c fix: prevent Anthropic token leaking to third-party anthropic_messages providers (salvage #2383) (#2389)
* fix: prevent Anthropic token fallback leaking to third-party anthropic_messages providers

When provider is minimax/alibaba/etc and MINIMAX_API_KEY is not set,
the code fell back to resolve_anthropic_token() sending Anthropic OAuth
credentials to third-party endpoints, causing 401 errors.

Now only provider=="anthropic" triggers the fallback. Generalizes the
Alibaba-specific guard from #1739 to all non-Anthropic providers.

* fix: set provider='anthropic' in credential refresh tests

Follow-up for cherry-picked PR #2383 — existing tests didn't set
agent.provider, which the new guard requires to allow Anthropic
token refresh.

---------

Co-authored-by: 0xbyt4 <35742124+0xbyt4@users.noreply.github.com>
2026-03-21 16:42:46 -07:00
Teknium
f9fa7421cb feat: bioinformatics gateway skill — index to 400+ bio skills
feat: bioinformatics gateway skill — index to 400+ bio skills
2026-03-21 16:38:43 -07:00
Teknium
342096b4bd feat(gateway): cache AIAgent per session for prompt caching
The gateway created a fresh AIAgent per message, rebuilding the system
prompt (including memory, skills, context files) every turn. This broke
prompt prefix caching — providers like Anthropic charge ~10x more for
uncached prefixes.

Now caches AIAgent instances per session_key with a config signature.
The cached agent is reused across messages in the same session,
preserving the frozen system prompt and tool schemas. Cache is
invalidated when:
- Config changes (model, provider, toolsets, reasoning, ephemeral
  prompt) — detected via signature mismatch
- /new, /reset, /clear — explicit session reset
- /model — global model change clears all cached agents
- /reasoning — global reasoning change clears all cached agents

Per-message state (callbacks, stream consumers, progress queues) is
set on the agent instance before each run_conversation() call.

This matches CLI behavior where a single AIAgent lives across all turns
in a session, with _cached_system_prompt built once and reused.
2026-03-21 16:21:06 -07:00
Teknium
55510cbad2 Merge pull request #2388 from NousResearch/hermes/hermes-31d7db3b
fix(provider): prevent Anthropic fallback from inheriting non-Anthropic base_url + fix(update): reset on stash conflict
2026-03-21 16:20:08 -07:00
Teknium
3ab50376b0 fix(update): reset working tree when stash restore leaves conflict markers
When `hermes update` stashes local changes and the subsequent
`git stash apply` fails or leaves unmerged files, the conflict markers
(<<<<<<< etc.) were left in the working tree, making Hermes unrunnable
until manually cleaned up.

Now the update command runs `git reset --hard HEAD` to restore a clean
working tree before exiting, and also detects unmerged files even when
git stash apply reports success.

Closes #2348
2026-03-21 16:16:35 -07:00
Teknium
f8fb61d4ad fix(provider): prevent Anthropic fallback from inheriting non-Anthropic base_url
Only honor config.model.base_url for Anthropic resolution when
config.model.provider is actually "anthropic". This prevents a Codex
(or other provider) base_url from leaking into Anthropic runtime and
auxiliary client paths, which would send  requests to the wrong
endpoint.

Closes #2384
2026-03-21 16:16:17 -07:00
Teknium
0d68446323 feat: add bioinformatics gateway skill
Meta-skill that indexes 400+ bioinformatics skills from two open-source
repos (GPTomics/bioSkills and ClawBio/ClawBio) and fetches domain-specific
reference material on demand. Covers genomics, transcriptomics, single-cell,
variant calling, pharmacogenomics, metagenomics, structural biology, and
20+ other computational biology domains.

No dependencies bundled — the skill clones the relevant repo when needed
and reads the domain-specific guides as reference material.
2026-03-21 16:15:24 -07:00
Teknium
81dbf4309a fix(telegram): escape bare parentheses/braces in MarkdownV2 output (#2386)
fix(telegram): escape bare parentheses/braces in MarkdownV2 output
2026-03-21 16:13:34 -07:00
Teknium
febfe1c268 fix(telegram): escape bare parentheses/braces in MarkdownV2 output
The MarkdownV2 format_message conversion left unescaped ( ) { }
in edge cases where placeholder processing didn't cover them (e.g.
partial link matches, URLs with parens). This caused Telegram to
reject the message with 'character ( is reserved and must be escaped'
and fall back to plain text — losing all formatting.

Added a safety-net pass (step 12) after placeholder restoration that
escapes any remaining bare ( ) { } outside code blocks and valid
MarkdownV2 link syntax.
2026-03-21 16:13:13 -07:00
Teknium
2a5f86ed6d Merge pull request #2343 from NousResearch/hermes/hermes-31d7db3b
feat: @ context references + Honcho config fixes
2026-03-21 16:10:19 -07:00
Tenzin Jampa
d3659c8ca0 fix(gateway): /title command fails when session doesn't exist in SQLite yet (#2379)
The /title command would fail with 'Session not found in database.' when
used as the first command in a new session. This happened because:

1. Gateway creates session in session_store (in-memory)
2. But SQLite _session_db only gets sessions when agent flushes messages
3. set_session_title() does UPDATE which fails if row doesn't exist

Now we check if session exists in SQLite and create it if needed before
attempting to set the title.

Fixes: Session not found in database. error on /title in new chats
2026-03-21 16:04:53 -07:00
Teknium
f7f75de7c3 fix(gateway): deliver MEDIA: files after streaming responses (#2382)
fix(gateway): deliver MEDIA: files after streaming responses
2026-03-21 16:01:47 -07:00
Teknium
f58902818d fix(gateway): deliver MEDIA: files after streaming responses
When streaming is enabled, text chunks are sent to the user in
real-time including raw MEDIA: tags. The normal post-processing in
_process_message_background is skipped when already_sent=True, so
MEDIA: files were never extracted or delivered — the user just saw
the raw MEDIA:/path/to/file text.

Fix: after streaming completes, extract MEDIA: tags and local file
paths from the response and deliver them via the platform adapter.
The text is already sent (with the raw tag visible in the stream),
but the actual files now get delivered as attachments.
2026-03-21 16:01:25 -07:00
Teknium
8da410ed95 feat(plugins): add slash command registration for plugins (#2359)
Plugins can now register slash commands via ctx.register_command()
in their register() function. Commands automatically appear in:
- /help and COMMANDS_BY_CATEGORY (under 'Plugins' category)
- Tab autocomplete in CLI
- Telegram bot menu
- Slack subcommand mapping
- Gateway dispatch

Handler signature: handler(args: str) -> str | None
Async handlers are supported in gateway context.

Changes:
- commands.py: add register_plugin_command() and rebuild_lookups()
- plugins.py: add register_command() to PluginContext, track in
  PluginManager._plugin_commands and LoadedPlugin.commands_registered
- cli.py: dispatch plugin commands in process_command()
- gateway/run.py: dispatch plugin commands before skill commands
- tests: 5 new tests for registration, help, tracking, handler, gateway
- docs: update plugins feature page and build guide
2026-03-21 16:00:30 -07:00
Teknium
da44c196b6 feat: @ context references — inline file, folder, diff, git, and URL injection
Add @file:path, @folder:dir, @diff, @staged, @git:N, and @url:
references that expand inline before the message reaches the LLM.
Supports line ranges (@file:main.py:10-50), token budget enforcement
(soft warn at 25%, hard block at 50%), and path sandboxing for gateway.

Core module from PR #2090 by @kshitijk4poor. CLI and gateway wiring
rewritten against current main. Fixed asyncio.run() crash when called
from inside a running event loop (gateway).

Closes #682.
2026-03-21 15:57:13 -07:00
Teknium
36079c6646 fix(tools): fix resource leak and double socket close in code_execution_tool (#2381)
Two fixes:
1. Use a single open(os.devnull) handle for both stdout and stderr
   suppression, preventing a file handle leak if the second open() fails.
2. Set server_sock = None after closing it in the try block to prevent
   the finally block from closing it again (causing an OSError).

Closes #2136

Co-authored-by: dieutx <dangtc94@gmail.com>
2026-03-21 15:55:25 -07:00
Teknium
135448f513 fix: ignore placeholder provider keys in provider activation checks (salvage #2121)
fix: ignore placeholder provider keys in provider activation checks (salvage #2121)
2026-03-21 15:54:59 -07:00
Teknium
2e143fd15c fix(acp): preserve session provider when switching models (#2380)
fix(acp): preserve session provider when switching models
2026-03-21 15:54:42 -07:00
Gutslabs
0b9526b476 fix(acp): preserve session provider when switching models 2026-03-21 15:54:10 -07:00
aashizpoudel
f304bc63b8 fix: ignore placeholder provider keys in provider activation checks
Add has_usable_secret() to reject empty, short (<4 char), and common
placeholder API key values (changeme, your_api_key, placeholder, etc.)
throughout the auth/runtime resolution chain.

Update list_available_providers() to use provider-specific auth status
via get_auth_status() instead of resolve_runtime_provider(), preventing
cross-provider key fallback from making providers appear available when
they aren't actually configured.

Preserve keyless custom endpoint support by checking via base URL.

Cherry-picked from PR #2121 by aashizpoudel.
2026-03-21 12:55:42 -07:00
Teknium
decc7851f2 fix(cli): pass conversation_history in quiet mode with --resume (#2357)
fix(cli): pass conversation_history in quiet mode with --resume
2026-03-21 12:51:56 -07:00
christopher-kapic
97108db038 fix(cli): pass conversation_history in quiet mode with --resume
hermes chat -q 'msg' --resume SESSION_ID loaded the session history
but never passed it to run_conversation(), so the model responded
without prior context. The interactive mode already does this correctly.

Based on work by christopher-kapic in PR #2081. Fixes #2106.
2026-03-21 12:51:34 -07:00
Teknium
1f1fa71d0c feat(skill): meme-generation — real image generator with Pillow (#2344)
* feat: add meme-generation skill

* Reduce meme skill prompt cost with tighter selection rules

* feat(skill): overhaul meme-generation into real image generator

Move from skills/creative/ to optional-skills/creative/ (niche skill,
not needed by default). Replace prompt-only meme concept brainstormer
with actual meme image generation:

- Python script using Pillow to overlay text on template images
- 10 curated templates with hand-tuned text positioning
- Dynamic access to ~100 popular imgflip templates via public API
- Custom image mode (--image): use AI-generated or any image as base
- Two text modes: overlay (white+outline on image) or bars (black bars)
- Vision verification workflow: use vision_analyze to QA the result
- Auto-scaling font with pixel-accurate word wrapping
- Template search via --search
- No API keys required

Original skill concept by adanaleycio (PR #1771), overhauled with
image generation and custom image support.

---------

Co-authored-by: adanaleycio <atillababa767@gmail.com>
2026-03-21 12:48:57 -07:00
Teknium
2988334fe5 fix: case-insensitive model family matching + compressor init logging (#2350)
fix: case-insensitive model family matching + compressor init logging
2026-03-21 10:48:08 -07:00
Teknium
292d12bed4 fix: case-insensitive model family matching + compressor init logging
Two fixes for local model context detection:

1. Hardcoded DEFAULT_CONTEXT_LENGTHS matching was case-sensitive.
   'qwen' didn't match 'Qwen3.5-9B-Q4_K_M.gguf' because of the
   capital Q. Now uses model.lower() for comparison.

2. Added compressor initialization logging showing the detected
   context_length, threshold, model, provider, and base_url.
   This makes turn-1 compression bugs diagnosable from logs —
   previously there was no log of what context length was detected.
2026-03-21 10:47:44 -07:00
Teknium
509cff6e5c revert: remove Shift+Enter keybindings that crash prompt_toolkit (#2349)
revert: remove Shift+Enter keybindings that crash prompt_toolkit
2026-03-21 10:41:24 -07:00
Teknium
29520df44f revert: remove Shift+Enter keybindings that crash prompt_toolkit
Reverts the s-enter and Kitty CSI keybindings from PR #2345/#2346.
The s-enter key notation causes 'Invalid key: s-enter' crash on
some prompt_toolkit versions, breaking hermes startup entirely.
2026-03-21 10:41:07 -07:00
Teknium
9be42e49f9 fix: resolve merge conflict markers in cli.py breaking hermes startup (#2347)
fix: resolve merge conflict markers in cli.py breaking hermes startup
2026-03-21 10:34:40 -07:00
Teknium
42cef9c282 fix: resolve merge conflict markers in cli.py breaking hermes startup
PR #2346 was merged with unresolved git conflict markers (<<<<<<,
=======, >>>>>>>) in cli.py at line 6047, causing SyntaxError on
startup. Resolved by keeping both the Shift+Enter keybindings and
the tab handler.
2026-03-21 10:34:21 -07:00
Teknium
3a71099dac fix(cli): handle Kitty keyboard protocol Shift+Enter for Ghostty/WezTerm (#2345)
fix(cli): handle Kitty keyboard protocol Shift+Enter for Ghostty/WezTerm
2026-03-21 10:04:19 -07:00
ygd58
356122e990 fix(cli): handle Kitty keyboard protocol Shift+Enter for Ghostty/WezTerm
Kitty-protocol terminals (Ghostty, WezTerm) encode Shift+Enter as
CSI 13;2u instead of plain Enter. Without this binding, raw escape
characters appear in the input buffer. Adds s-enter and the Kitty
escape sequence as newline-insert bindings.

Based on work by ygd58 in PR #1798. Fixes #1795.
Registry.py apostrophe sanitization change excluded (unrelated scope).
2026-03-21 10:03:55 -07:00
Teknium
aefcdd6f7f fix: return JSON parse error to model instead of dispatching with empty args (#2342)
When the model produces malformed JSON in tool call arguments, the agent
loop was setting args={} and dispatching the tool anyway, wasting an
iteration and producing a confusing downstream error. Now the error is
returned directly as the tool result so the model can retry with valid JSON.

Co-authored-by: alireza78a <alireza78.crypto@gmail.com>
2026-03-21 09:56:44 -07:00
Teknium
3835a8d5df fix: whitespace-only env vars bypass web backend detection + clearer Firecrawl error (#2341)
fix: whitespace-only env vars bypass web backend detection + clearer Firecrawl error
2026-03-21 09:55:03 -07:00
JackTheGit
e8188a56c7 Fix backend detection when environment variables contain only whitespace 2026-03-21 09:53:06 -07:00
JackTheGit
c42a18e9e5 Improve Firecrawl configuration error message and add logging 2026-03-21 09:53:06 -07:00
Teknium
b73d221324 fix: Alibaba/DashScope: preserve model dots, fix 401 auth, fix dead provider check (salvage #1748 + fix #2314)
fix: Alibaba/DashScope: preserve model dots, fix 401 auth, fix dead provider check (salvage #1748 + fix #2314)
2026-03-21 09:51:40 -07:00
Teknium
cc51ffdb57 Merge pull request #2340 from NousResearch/feat/streaming-default
feat: enable streaming by default in CLI
2026-03-21 09:50:54 -07:00
Teknium
c8971db435 fix(gateway): pass message_thread_id in send_image_file, send_document, send_video (#2339)
fix(gateway): pass message_thread_id in send_image_file, send_document, send_video
2026-03-21 09:50:09 -07:00
Teknium
c4e787d47b feat: enable streaming by default in CLI
Streaming provides a better UX — tokens appear as they arrive instead
of waiting for the full response. show_reasoning remains false so
thinking blocks are not streamed to the user.
2026-03-21 09:49:47 -07:00
unmodeled-tyler
fb48b8f0c5 fix(gateway): pass message_thread_id in send_image_file, send_document, send_video
Fixes #1803. send_image_file, send_document, and send_video were missing
message_thread_id forwarding, causing them to fail in Telegram forum/supergroups
where thread_id is required. send_voice already handled this correctly. Adds
metadata parameter + message_thread_id to all three methods, and adds tests
covering the thread_id forwarding path.
2026-03-21 09:49:33 -07:00
Teknium
67600d0a0b feat(cli): add hermes plugins install/remove/list command (#2337)
feat(cli): add hermes plugins install/remove/list command
2026-03-21 09:47:59 -07:00
Angello Picasso
5a9ab09bc3 feat(cli): add hermes plugins install/remove/list command
Plugin management via git repos:
- hermes plugins install <git-url|owner/repo>
- hermes plugins update <name>
- hermes plugins remove <name> (aliases: rm, uninstall)
- hermes plugins list (alias: ls)

Security: path traversal protection, no shell injection, manifest
version guard, insecure URL warnings.

42 tests covering security, dispatch, helpers, and commands.

Based on work by Angello Picasso in PR #1785. Closes #1789.
2026-03-21 09:47:33 -07:00
Teknium
2c06ec5f51 fix: correct provider check for Alibaba model identity injection
PR #2314 checked for provider names 'alibaba-coding-plan' and
'alibaba-coding-plan-anthropic' which don't exist in the provider
registry. The provider is always 'alibaba' — the condition was dead
code. Fixed to check self.provider == 'alibaba'.
2026-03-21 09:46:26 -07:00
Teknium
d70e07fc45 refactor(cli): add protected TUI extension hooks for wrapper CLIs
Based on PR #1749 by @erosika (reimplemented on current main).

Extracts three protected methods from run() so wrapper CLIs can extend
the TUI without overriding the entire method:

- _get_extra_tui_widgets(): inject widgets between spacer and status bar
- _register_extra_tui_keybindings(kb, input_area): add keybindings
- _build_tui_layout_children(**widgets): full control over ordering

Default implementations reproduce existing layout exactly. The inline
HSplit in run() now delegates to _build_tui_layout_children().

5 tests covering defaults, widget insertion position, and keybinding
registration.
2026-03-21 09:42:07 -07:00
Teknium
fff7203049 fix(mistral-parser): handle nested JSON in fallback extraction (#2335)
fix(mistral-parser): handle nested JSON in fallback extraction
2026-03-21 09:41:45 -07:00
Himess
5663980015 fix(mistral-parser): handle nested JSON in fallback extraction 2026-03-21 09:41:17 -07:00
Teknium
8304a7716d fix(gateway): restart on whatsapp bridge child exit (#2334)
Co-authored-by: Frederico Ribeiro <fr@tecompanytea.com>
2026-03-21 09:38:52 -07:00
crazywriter1
523d8c38f9 fix: Alibaba/DashScope: preserve model dots (qwen3.5-plus) and fix 401 auth
When using Alibaba (DashScope) with an anthropic-compatible endpoint,
model names like qwen3.5-plus were being normalized to qwen3-5-plus.
Alibaba's API expects the dot. Added preserve_dots parameter to
normalize_model_name() and build_anthropic_kwargs().

Also fixed 401 auth: when provider is alibaba or base_url contains
dashscope/aliyuncs, use only the resolved API key (DASHSCOPE_API_KEY).
Never fall back to resolve_anthropic_token(), and skip Anthropic
credential refresh for DashScope endpoints.

Cherry-picked from PR #1748 by crazywriter1. Fixes #1739.
2026-03-21 09:38:04 -07:00