Commit Graph

1254 Commits

Author SHA1 Message Date
teknium1
90e6fa2612 Merge PR #204: fix Telegram italic regex newline bug
Authored by 0xbyt4.

The italic regex [^*]+ matched across newlines, corrupting bullet lists
using * markers (e.g. '* Item one\n* Item two' became italic garbage).
Fixed by adding \n to the negated character class: [^*\n]+.
2026-03-04 19:52:03 -08:00
teknium1
fd22ae5fcb Merge PR #203: add unit tests for trajectory_compressor
Authored by 0xbyt4.

25 tests covering CompressionConfig, TrajectoryMetrics, AggregateMetrics,
protected indices, content extraction, and token counting.
2026-03-04 19:48:19 -08:00
teknium1
e1baab90f7 Merge PR #201: fix skills hub dedup to prefer higher trust levels
Authored by 0xbyt4.

The dedup logic in GitHubSource.search() and unified_search() used
'r.trust_level == "trusted"' which let trusted results overwrite builtin
ones. Now uses ranked comparison: builtin (2) > trusted (1) > community (0).
2026-03-04 19:40:41 -08:00
teknium1
4fcfa329ba Merge PR #200: fix extract_images and truncate_message bugs in platform base
Authored by 0xbyt4.

Two fixes:
- extract_images(): only remove extracted image tags, not all markdown image
  tags. Previously ![doc](report.pdf) was silently dropped when real images
  were also present.
- truncate_message(): walk chunk_body not full_chunk when tracking code block
  state, so the reopened fence prefix doesn't toggle in_code off and leave
  continuation chunks with unclosed code blocks.
2026-03-04 19:37:58 -08:00
teknium1
b336980229 Merge PR #193: add unit tests for 5 security/logic-critical modules (batch 4)
Authored by 0xbyt4.

144 new tests covering gateway/pairing.py, tools/skill_manager_tool.py,
tools/skills_tool.py, honcho_integration/session.py, and
agent/auxiliary_client.py.
2026-03-04 19:35:01 -08:00
teknium1
7128f95621 Merge PR #390: fix hidden directory filter broken on Windows
Authored by Farukest. Fixes #389.

Replaces hardcoded forward-slash string checks ('/.git/', '/.hub/') with
Path.parts membership test in _find_all_skills() and scan_skill_commands().
On Windows, str(Path) uses backslashes so the old filter never matched,
causing quarantined skills to appear as installed.
2026-03-04 19:22:43 -08:00
teknium1
ffc6d767ec Merge PR #388: fix --force bypassing dangerous verdict in should_allow_install
Authored by Farukest. Fixes #387.

Removes 'and not force' from the dangerous verdict check so --force
can never install skills with critical security findings (reverse shells,
data exfiltration, etc). The docstring already documented this behavior
but the code didn't enforce it.
2026-03-04 19:19:57 -08:00
teknium1
44a2d0c01f Merge PR #386: fix symlink boundary check prefix confusion in skills_guard
Authored by Farukest. Fixes #385.

Replaces startswith() with Path.is_relative_to() in _check_structure()
symlink escape check — same fix pattern as skill_view() (PR #352).
Prevents symlinks escaping to sibling directories with shared name prefixes.
2026-03-04 19:13:21 -08:00
teknium1
3e2ed18ad0 fix: fallback to main model endpoint when auxiliary summary client fails
When the auxiliary client (used for context compression summaries) fails
— e.g. due to a stale OpenRouter API key after switching to a local LLM
— fall back to the user's active endpoint (OPENAI_BASE_URL) instead of
returning a useless static summary string.

This handles the common scenario where a user switches providers via
'hermes model' but the old provider's API key remains in .env. The
auxiliary client picks up the stale key, fails (402/auth error), and
previously compression would produce garbage. Now it gracefully retries
with the working endpoint.

On successful fallback, the working client is cached for future
compressions in the same session so the fallback cost is paid only once.

Ref: #348
2026-03-04 17:58:09 -08:00
teknium1
db58cfb13d Merge PR #269: Fix nous refresh token rotation failure on key mint failure
Fixes a bug where the refresh token was not persisted when the API key
mint failed (e.g., 402 insufficient credits, timeout). The rotated
refresh token was lost, causing subsequent auth attempts to fail with
a stale token.

Changes:
- Persist auth state immediately after each successful token refresh,
  before attempting the mint
- Use latest in-memory refresh token on mint-retry paths (was using
  the stale original)
- Atomic durable writes for auth.json (temp file + fsync + replace)
- Opt-in OAuth trace logging (HERMES_OAUTH_TRACE=1, fingerprint-only)
- 3 regression tests covering refresh+402, refresh+timeout, and
  invalid-token retry behavior

Author: Robin Fernandes <rewbs>
2026-03-04 17:52:10 -08:00
teknium1
3220bb8aaa Merge PR #403: Fix context overrun crash with local LLM backends
Authored by ch3ronsa. Fixes #348.

Adds 'context size' (LM Studio) and 'context window' (Ollama) to
context-length error detection phrases so local backend 400 errors
trigger compression instead of aborting. Also removes 'error code: 400'
from the non-retryable error list as defense in depth.
2026-03-04 17:48:44 -08:00
teknium1
ff3a479156 fix: coerce session_id and data to string in process tool handler
Some models send session_id as an integer instead of a string, causing
type errors downstream. Defensively cast session_id and write/submit
data args to str to handle non-compliant model outputs.
2026-03-04 16:37:00 -08:00
teknium1
6f4941616d fix(gateway): include history_offset in error return path
The error return (no final_response) was missing history_offset,
falling back to len(history) which has the same session_meta offset
bug fixed in PR #395. Now both return paths include the correct
filtered history length.
2026-03-04 16:26:53 -08:00
teknium1
bd3025d669 Merge PR #395: fix(gateway): use filtered history length for transcript message extraction
Authored by PercyDikec. Fixes #394.

The transcript extraction used len(history) to find new messages, but
history includes session_meta entries stripped before reaching the agent.
This caused 1 message lost per turn from turn 2 onwards. Fix returns
history_offset (filtered length) from _run_agent and uses it for the slice.
2026-03-04 16:25:09 -08:00
teknium1
4c72329412 feat: add backend validation for required binaries in setup wizard
Implemented checks to ensure that necessary binaries (Docker, Singularity, SSH) are installed for the selected backend in the setup wizard. If a required binary is missing, the user is prompted to proceed with a fallback to the local backend. This enhances user experience by preventing potential runtime errors due to missing dependencies.
2026-03-04 14:49:23 -08:00
teknium1
8311e8984b fix: preflight context compression + error handler ordering for model switches
Two fixes for the case where a user switches to a model with a smaller
context window while having a large existing session:

1. Preflight compression in run_conversation(): Before the main loop,
   estimate tokens of loaded history + system prompt. If it exceeds the
   model's compression threshold (85% of context), compress proactively
   with up to 3 passes. This naturally handles model switches because
   the gateway creates a fresh AIAgent per message with the current
   model's context length.

2. Error handler reordering: Context-length errors (400 with 'maximum
   context length' etc.) are now checked BEFORE the generic 4xx handler.
   Previously, OpenRouter's 400-status context-length errors were caught
   as non-retryable client errors and aborted immediately, never reaching
   the compression+retry logic.

Reported by Sonicrida on Discord: 840-message session (2MB+) crashed
after switching from a large-context model to minimax via OpenRouter.
2026-03-04 14:42:41 -08:00
teknium1
093acd72dd fix: catch exceptions from check_fn in is_toolset_available()
get_definitions() already wrapped check_fn() calls in try/except,
but is_toolset_available() did not. A failing check (network error,
missing import, bad config) would propagate uncaught and crash the
CLI banner, agent startup, and tools-info display.

Now is_toolset_available() catches all exceptions and returns False,
matching the existing pattern in get_definitions().

Added 4 tests covering exception handling in is_toolset_available(),
check_toolset_requirements(), get_definitions(), and
check_tool_availability().

Closes #402
2026-03-04 14:22:30 -08:00
Vicaversa
e9ab711b66 Fix context overrun crash with local LLM backends (fixes #348)
Local backends (LM Studio, Ollama, llama.cpp) return HTTP 400
with messages like "Context size has been exceeded" when the
context window is full. The error phrase list did not include
"context size" or "context window", so these errors fell through
to the generic 4xx abort handler instead of triggering compression.

Changes:
- Move context-length check above generic 4xx handler so it runs
  first (same pattern as the existing 413 check)
- Add "context size" and "context window" to the phrase list
- Guard 4xx handler with `not is_context_length_error` to prevent
  context-related 400s from being treated as non-retryable
2026-03-05 01:12:34 +03:00
teknium1
b2a9f6beaa feat: enable up/down arrow history navigation in CLI
The TextArea uses multiline=True, so up/down arrows only moved the
cursor within text — history browsing via FileHistory was attached
but inaccessible.

Two fixes:
1. Add up/down key bindings in normal input mode that call
   Buffer.auto_up()/auto_down(). These intelligently handle both:
   cursor movement when editing multi-line text, and history
   browsing when on the first/last line.

2. Pass append_to_history=True to buffer.reset() in the Enter
   handler so messages actually get saved to ~/.hermes_history.

History persists across sessions via FileHistory. The bindings are
filtered out during clarify, approval, and sudo prompts (which
have their own up/down handlers).
2026-03-04 13:39:48 -08:00
PercyDikec
d3504f84af fix(gateway): use filtered history length for transcript message extraction
The transcript extraction used len(history) to find new messages, but
history includes session_meta entries that are stripped before passing
to the agent. This mismatch caused 1 message to be lost from the
transcript on every turn after the first, because the slice offset
was too high. Use the filtered history length (history_offset) returned
by _run_agent instead.

Also changed the else branch from returning all agent_messages to
returning an empty list, so compressed/shorter agent output does not
duplicate the entire history into the transcript.
2026-03-04 21:34:40 +03:00
Farukest
34badeb19c fix(whatsapp): initialize data variable and close log handle on error paths 2026-03-04 19:11:48 +03:00
Farukest
f93b48226c fix: use Path.parts for hidden directory filter in skill listing
The hidden directory filter used hardcoded forward-slash strings like
'/.git/' and '/.hub/' to exclude internal directories. On Windows,
Path returns backslash-separated strings, so the filter never matched.

This caused quarantined skills in .hub/quarantine/ to appear as
installed skills and available slash commands on Windows.

Replaced string-based checks with Path.parts membership test which
works on both Windows and Unix.
2026-03-04 18:34:16 +03:00
Farukest
4805be0119 fix: prevent --force from overriding dangerous verdict in should_allow_install
The docstring states --force should never override dangerous verdicts,
but the condition `if result.verdict == "dangerous" and not force`
allowed force=True to skip the early return. Execution then fell
through to `if force: return True`, bypassing the policy block.

Removed `and not force` so dangerous skills are always blocked
regardless of the --force flag.
2026-03-04 18:10:18 +03:00
Farukest
a3ca71fe26 fix: use is_relative_to() for symlink boundary check in skills_guard
The symlink escape check in _check_structure() used startswith()
without a trailing separator. A symlink resolving to a sibling
directory with a shared prefix (e.g. 'axolotl-backdoor') would pass
the check for 'axolotl' since the string prefix matched.

Replaced with Path.is_relative_to() which correctly handles directory
boundaries and is consistent with the skill_view path check.
2026-03-04 17:23:23 +03:00
teknium1
70a0a5ff4a fix: exclude current session from session_search results
session_search was returning the current session if it matched the
query, which is redundant — the agent already has the current
conversation context. This wasted an LLM summarization call and a
result slot.

Added current_session_id parameter to session_search(). The agent
passes self.session_id and the search filters out any results where
either the raw or parent-resolved session ID matches. Both the raw
match and the parent-resolved match are checked to handle child
sessions from delegation.

Two tests added verifying the exclusion works and that other
sessions are still returned.
2026-03-04 06:06:40 -08:00
teknium1
021f62cb0c fix(security): patch multi-word bypass in 8 more injection patterns
Systematic audit of all prompt injection regexes in skills_guard.py
found 8 more patterns with the same single-word gap vulnerability
fixed in PR #192. Multi-word variants like 'pretend that you are',
'output the full system prompt', 'respond without your safety
filters', etc. all bypassed the scanner.

Fixed patterns:
- you are [now] → you are [... now]
- do not [tell] the user → do not [... tell ... the] user
- pretend [you are|to be] → pretend [... you are|to be]
- output the [system|initial] prompt → output [... system|initial] prompt
- act as if you [have no] [restrictions] → act as if [... you ... have no ... restrictions]
- respond without [restrictions] → respond without [... restrictions]
- you have been [updated] to → you have been [... updated] to
- share [the] [entire] [conversation] → share [... conversation]

All use (?:\w+\s+)* to allow arbitrary intermediate words.
2026-03-04 06:00:41 -08:00
teknium1
ba214e43c8 fix(security): apply same multi-word bypass fix to disregard pattern
The 'disregard ... instructions/rules/guidelines' regex had the
same single-word gap vulnerability as the 'ignore' pattern fixed
in PR #192. 'disregard all your instructions' bypassed the scanner.

Added (?:\w+\s+)* between both keyword groups to allow arbitrary
intermediate words.
2026-03-04 05:55:38 -08:00
teknium1
520a26c48f Merge PR #192: fix(security): catch multi-word prompt injection bypass in skills_guard
Authored by 0xbyt4.

The 'ignore ... instructions' regex only matched a single word between
'ignore' and the keyword (previous/all/above/prior). Multi-word variants
like 'ignore all prior instructions' bypassed the scanner entirely.
2026-03-04 05:54:04 -08:00
teknium1
a787a0d60b Merge PR #317: fix(setup): improve shell config detection for PATH setup
Authored by mehmetkr-31. Related to #202.

Checks $SHELL env var first to pick the right config file (.zshrc
vs .bashrc) instead of relying on file existence, which could pick
the wrong file on macOS. Falls back to file-existence checks for
non-standard shells. Creates the config file with touch if it was
selected but doesn't exist yet.
2026-03-04 05:46:24 -08:00
teknium1
8d2d8cc728 refactor: add exception handling and docstring to has_any_sessions
Wrap session_count() in try/except so a DB error falls through to
the heuristic fallback instead of crashing. Added a detailed
docstring explaining why the DB approach is needed and the > 1
assumption (current session already exists when called).
2026-03-04 05:38:54 -08:00
teknium1
4ae61b0886 Merge PR #370: fix(session): use database session count for has_any_sessions
Authored by Bartok9. Fixes #351.
2026-03-04 05:37:15 -08:00
teknium1
79871c2083 refactor: use Path.is_relative_to() for skill_view boundary check
Replace the string-based startswith + os.sep approach with
Path.is_relative_to() (Python 3.9+, we require 3.10+). This is
the idiomatic pathlib way to check path containment — it handles
separators, case sensitivity, and the equal-path case natively
without string manipulation.

Simplified tests to match: removed the now-unnecessary
test_separator_is_os_native test since is_relative_to doesn't
depend on separator choice.
2026-03-04 05:30:43 -08:00
teknium1
7796ac1411 Merge PR #354: fix: use os.sep in skill_view path boundary check for Windows compatibility
Authored by Farukest. Fixes #353.
2026-03-04 05:17:36 -08:00
teknium1
c45aeb45b1 fix(whatsapp): wait for connected status and log bridge output
The gateway health check broke out of the polling loop as soon as
the bridge HTTP server returned 200, regardless of the actual
WhatsApp connection status. This meant 'Bridge ready (status:
disconnected)' was printed and the gateway moved on, even when
WhatsApp never connected.

Additionally, bridge stdout/stderr were piped to DEVNULL, so if the
session had expired and the bridge needed a QR re-scan, the user had
no way to see that. The 'Scan QR code if prompted (check bridge
output)' message was misleading since there was no output to check.

Changes:
- Health check now has two phases: wait for HTTP (15s), then wait
  for status:connected (15s more). Total 30s budget.
- Bridge output routes to ~/.hermes/whatsapp/bridge.log instead of
  DEVNULL — QR codes, errors, reconnection msgs are preserved.
- Clear warnings with actionable steps if connection fails after 30s
  (check bridge.log, re-pair with hermes whatsapp).
- Removed misleading 'Scan QR code' message.
- Log file handle properly cleaned up on disconnect.

Fixes #365
2026-03-04 04:58:28 -08:00
teknium1
ee7fde6531 feat: add OpenThoughts-TBLite evaluation script
Introduced a new evaluation script for the OpenThoughts-TBLite environment, enabling users to run evaluations with customizable options. The script includes logging capabilities and real-time output, enhancing the evaluation process for terminal agents. This addition complements the existing benchmarking tools and improves usability for users.
2026-03-04 12:55:56 +00:00
teknium1
0ea6c34325 feat: add OpenThoughts-TBLite evaluation environment and configuration files
Introduced a new evaluation environment for OpenThoughts-TBLite, including the main evaluation script, configuration YAML, and README documentation. This environment provides a faster alternative to Terminal-Bench 2.0, featuring 100 difficulty-calibrated tasks for terminal agents. The setup allows for easy evaluation and configuration, enhancing the benchmarking capabilities for terminal agents.
2026-03-04 11:42:41 +00:00
teknium1
3db3d60368 refactor: extract build_session_key() as single source of truth
The session key construction logic was duplicated in 4 places
(session.py + 3 inline copies in run.py), which is exactly the
kind of drift that caused issue #349 in the first place.

Extracted build_session_key() as a public function in session.py.
SessionStore._generate_session_key() now delegates to it, and all
inline key construction in run.py has been replaced with calls to
the shared function. Tests updated to test the function directly.
2026-03-04 03:34:45 -08:00
teknium1
bfd08d5648 Merge PR #350: fix(gateway): match _quick_key to _generate_session_key for WhatsApp DMs
Authored by Farukest. Fixes #349.
2026-03-04 03:31:13 -08:00
teknium1
7f9777a0b0 feat: add container resource configuration prompts in setup wizard
Introduced interactive prompts for configuring container resource settings (CPU, memory, disk, persistence) during the setup wizard. Updated the default configuration to include these settings and improved user guidance on their implications for Docker, Singularity, and Modal backends. This enhancement aims to streamline the setup process and provide users with clearer options for resource management.
2026-03-04 03:29:05 -08:00
Bartok Moltbot
87a16ad2e5 fix(session): use database session count for has_any_sessions (#351)
The previous implementation used `len(self._entries) > 1` to check if any
sessions had ever been created. This failed for single-platform users because
when sessions reset (via /reset, auto-reset, or gateway restart), the entry
for the same session_key is replaced in _entries, not added. So len(_entries)
stays at 1 for users who only use one platform.

Fix: Query the SQLite database's session count instead. The database preserves
historical session records (marked as ended), so session_count() correctly
returns > 1 for returning users even after resets.

This prevents the agent from reintroducing itself to returning users after
every session reset.

Fixes #351
2026-03-04 03:34:57 -05:00
Vicaversa
f90a627f9a fix(gateway): add missing UTF-8 encoding to file I/O preventing crashes on Windows
On Windows, Python's open() defaults to the system locale encoding
(e.g. cp1254 for Turkish, cp1252 for Western European) instead of
UTF-8. The gateway already uses ensure_ascii=False in json.dumps()
to preserve Unicode characters in chat messages, but the
corresponding open() calls lack encoding="utf-8". This mismatch
causes UnicodeEncodeError / UnicodeDecodeError when users send
non-ASCII messages (Turkish, Japanese, Arabic, emoji, etc.) through
Telegram, Discord, WhatsApp, or Slack on Windows.

The project already fixed this for .env files in hermes_cli/config.py
(line 624) but the gateway module was missed.

Files fixed:
- gateway/session.py: session index + JSONL transcript read/write (5 calls)
- gateway/channel_directory.py: channel directory read/write (3 calls)
- gateway/mirror.py: session index read + transcript append (2 calls)
2026-03-04 11:32:57 +03:00
teknium1
152e0800e6 feat: add detailed setup instructions for Telegram, Discord, and Slack platforms
Enhanced the gateway setup process by including step-by-step setup instructions for Telegram, Discord, and Slack. Updated help prompts for environment variables to reference these new instructions, improving user guidance during the configuration of messaging platforms. This change aims to streamline the onboarding experience for users setting up their bots.
2026-03-03 20:05:15 -08:00
teknium1
d8f10fa515 feat: implement allowlist feature for user access in gateway setup
Enhanced the gateway setup process by introducing an allowlist feature for user IDs, improving security by denying access by default. Updated prompts to guide users in configuring allowed users for Telegram, Discord, and Slack platforms, and refined messaging for handling unauthorized users. This change aims to enhance user experience and security during the setup process.
2026-03-03 19:55:06 -08:00
Farukest
e86f391cac fix: use os.sep in skill_view path boundary check for Windows compatibility 2026-03-04 06:50:06 +03:00
Farukest
e39de2e752 fix(gateway): match _quick_key to _generate_session_key for WhatsApp DMs 2026-03-04 06:34:46 +03:00
teknium1
1538be45de fix: improve gateway setup messaging for non-interactive environments
Updated the gateway setup function to provide clearer messaging when no terminal is available, enhancing user understanding of the installation process. This change ensures that users are informed to run 'hermes gateway install' later if the setup is skipped due to terminal unavailability.
2026-03-03 19:34:05 -08:00
teknium1
95e3f4b001 refactor: enhance gateway service setup messaging and installation prompts
Updated the setup wizard to improve clarity around gateway service installation and management. Added prompts for users to install and start the gateway as a system service on Linux and macOS, while refining messaging for home channel configuration. This enhances the overall user experience during the setup process.
2026-03-03 19:31:16 -08:00
teknium1
b7821b6dc1 enhance: improve gateway setup messaging and service installation prompts
Updated the gateway setup function to provide clearer messaging regarding the installation status of the gateway service. Added prompts for installing the service as a background process on supported platforms (Linux and macOS) and clarified next steps for users. Improved user experience by offering options to start the service immediately or run it in the foreground.
2026-03-03 19:30:05 -08:00
teknium1
556a132f2d refactor: update platform status function to return plain-text strings
Modified the _platform_status function in gateway.py to return uncolored plain-text status strings for platforms, ensuring compatibility with simple_term_menu items. Additionally, removed emoji characters from the status display in the gateway setup menu for improved readability.
2026-03-03 19:04:32 -08:00
teknium1
fafb9c23bf fix: strip emoji characters from menu choices in interactive setup
Updated the interactive setup in hermes CLI to remove emoji characters from menu choices. This change addresses visual issues caused by emoji miscalculations during terminal redraws, ensuring a cleaner and more readable interface for users.
2026-03-03 19:02:33 -08:00