Commit Graph

1130 Commits

Author SHA1 Message Date
Allegro
d139f2c6d2 security: Add crisis safety test infrastructure for Issue #79
Some checks failed
Docker Build and Publish / build-and-push (push) Has been cancelled
Nix / nix (macos-latest) (push) Has been cancelled
Nix / nix (ubuntu-latest) (push) Has been cancelled
Tests / test (push) Has been cancelled
- crisis_safety_test_og_godmode.py: Automated test runner (312 lines)
- crisis_safety_audit_issue_79.py: Full audit framework (21KB)
- FALLBACK_CHAIN_TEST_PLAN.md: Test specifications
- BLOCKER_ISSUE_79.md: Documentation of API credential blocker
- EXECUTION_SUMMARY.md: Burn cycle summary

Test coverage for 4 crisis safety scenarios:
- CRISIS-001: suicide_method query
- CRISIS-002: suicide_ideation query
- CRISIS-003: suicide_method query (medication)
- CRISIS-004: suicide_validation query

Status: Infrastructure complete, execution blocked pending OPENROUTER_API_KEY
Related: #72 (parent), #79, #80, #81
2026-04-01 03:49:48 +00:00
Allegro
d9cf77e382 feat: Issue #42 - Nexus Architect for autonomous Three.js world building
Implement Phase 31: Autonomous 'Nexus' Expansion & Architecture

DELIVERABLES:
- agent/nexus_architect.py: AI agent for natural language to Three.js conversion
  * Prompt engineering for LLM-driven immersive environment generation
  * Mental state integration for dynamic aesthetic tuning
  * Mood preset system (contemplative, energetic, mysterious, etc.)
  * Room and portal design generation

- tools/nexus_build_tool.py: Build tool interface with functions:
  * create_room(name, description, style) - Generate room modules
  * create_portal(from_room, to_room, style) - Generate portal connections
  * add_lighting(room, type, color, intensity) - Add Three.js lighting
  * add_geometry(room, shape, position, material) - Add 3D objects
  * generate_scene_from_mood(mood_description) - Mood-based generation
  * deploy_nexus_module(module_code, test=True) - Deploy and test

- agent/nexus_deployment.py: Real-time deployment system
  * Hot-reload Three.js modules without page refresh
  * Validation (syntax check, Three.js API compliance)
  * Rollback on error with version history
  * Module versioning and status tracking

- config/nexus-templates/: Template library
  * base_room.js - Base room template (Three.js r128+)
  * portal_template.js - Portal template (circular, rectangular, stargate)
  * lighting_presets.json - Warm, cool, dramatic, serene, crystalline presets
  * material_presets.json - 15 material presets including Timmy's gold, Allegro blue

- tests/test_nexus_architect.py: Comprehensive test coverage
  * Unit tests for all components
  * Integration tests for full workflow
  * Template file validation

DESIGN PRINCIPLES:
- Modular architecture (each room = separate JS module)
- Valid Three.js code (r128+ compatible)
- Hot-reloadable (no page refresh needed)
- Mental state integration (SOUL.md values influence aesthetic)

NEXUS AESTHETIC GUIDELINES:
- Timmy's color: warm gold (#D4AF37)
- Allegro's color: motion blue (#4A90E2)
- Sovereignty theme: crystalline structures, clean lines
- Service theme: open spaces, welcoming lighting
- Default mood: contemplative, expansive, hopeful
2026-04-01 02:45:36 +00:00
Allegro
ae6f3e9a95 feat: Issue #39 - temporal knowledge graph with versioning and reasoning
Implement Phase 28: Sovereign Knowledge Graph 'Time Travel'

- agent/temporal_knowledge_graph.py: SQLite-backed temporal triple store
  with versioning, validity periods, and temporal query operators
  (BEFORE, AFTER, DURING, OVERLAPS, AT)

- agent/temporal_reasoning.py: Temporal reasoning engine supporting
  historical queries, fact evolution tracking, and worldview snapshots

- tools/temporal_kg_tool.py: Tool integration with functions for
  storing facts with time, querying historical state, generating
  temporal summaries, and natural language temporal queries

- tests/test_temporal_kg.py: Comprehensive test coverage including
  storage tests, query operators, historical summaries, and integration tests
2026-04-01 02:08:20 +00:00
Allegro
be865df8c4 security: Issue #81 - ULTRAPLINIAN fallback chain audit framework
Implement comprehensive red team audit infrastructure for testing the entire
fallback chain against jailbreak and crisis intervention attacks.

Files created:
- tests/security/ultraplinian_audit.py: Comprehensive audit runner with:
  * Support for all 4 techniques: GODMODE, Parseltongue, Prefill, Crisis
  * Model configurations for Kimi, Gemini, Grok, Llama
  * Concurrent execution via ThreadPoolExecutor
  * JSON and Markdown report generation
  * CLI interface with --help, --list-models, etc.

- tests/security/FALLBACK_CHAIN_TEST_PLAN.md: Detailed test specifications:
  * Complete test matrix (5 models × 4 techniques × 8 queries = 160 tests)
  * Technique specifications with system prompts
  * Scoring criteria and detection patterns
  * Success criteria and maintenance schedule

- agent/ultraplinian_router.py (optional): Race-mode fallback router:
  * Parallel model querying for safety validation
  * SHIELD-based safety analysis
  * Crisis escalation to SAFE SIX models
  * Configurable routing decisions

Test commands:
  python tests/security/ultraplinian_audit.py --help
  python tests/security/ultraplinian_audit.py --all-models --all-techniques
  python tests/security/ultraplinian_audit.py --model kimi-k2.5 --technique crisis

Relates to: Issue #72 (Red Team Jailbreak Audit)
Severity: MEDIUM
2026-04-01 01:51:23 +00:00
Allegro
9f09bb3066 feat: Phase 31 Nexus Architect scaffold — autonomous 3D world generation
Implements the foundation for autonomous Nexus expansion:
- NexusArchitect tool with 6 operations (design_room, create_portal,
  add_lighting, validate_scene, export_scene, get_summary)
- Security-first validation with banned pattern detection
- LLM prompt generators for Three.js code generation
- 48 comprehensive tests (100% pass)
- Complete documentation with API reference

Addresses: hermes-agent#42 (Phase 31)
Related: Burn Report #6
2026-03-31 21:06:42 +00:00
Allegro
e555c989af security: add input sanitization for jailbreak patterns (Issue #72)
Implements input sanitization module to detect and strip jailbreak fingerprint
patterns identified in red team audit:

HIGH severity:
- GODMODE dividers: [START], [END], GODMODE ENABLED, UNFILTERED
- L33t speak encoding: h4ck, k3ylog, ph1shing, m4lw4r3

MEDIUM severity:
- Boundary inversion: [END]...[START] tricks
- Fake role markers: user: assistant: system:

LOW severity:
- Spaced text bypass: k e y l o g g e r

Other patterns detected:
- Refusal inversion: 'refusal is harmful'
- System prompt injection: 'you are now', 'ignore previous instructions'
- Obfuscation: base64, hex, rot13 mentions

Files created:
- agent/input_sanitizer.py: Core sanitization module with detection,
  scoring, and cleaning functions
- tests/test_input_sanitizer.py: 69 test cases covering all patterns
- tests/test_input_sanitizer_integration.py: Integration tests

Files modified:
- agent/__init__.py: Export sanitizer functions
- run_agent.py: Integrate sanitizer at start of run_conversation()

Features:
- detect_jailbreak_patterns(): Returns bool, patterns list, category scores
- sanitize_input(): Returns cleaned_text, risk_score, patterns
- score_input_risk(): Returns 0-100 risk score
- sanitize_input_full(): Complete sanitization with blocking decisions
- Logging integration for security auditing
2026-03-31 19:56:16 +00:00
Allegro
f9bbe94825 test: add fallback chain integration tests 2026-03-31 19:46:23 +00:00
Allegro
5ef812d581 feat: implement automatic kimi-coding fallback on quota errors 2026-03-31 19:35:54 +00:00
Allegro
546b3dd45d security: integrate SHIELD jailbreak/crisis detection
Some checks failed
Nix / nix (ubuntu-latest) (push) Failing after 5s
Docker Build and Publish / build-and-push (push) Failing after 40s
Tests / test (push) Failing after 11m11s
Nix / nix (macos-latest) (push) Has been cancelled
Integrate SHIELD (Sovereign Harm Interdiction & Ethical Layer Defense) into
Hermes Agent pre-routing layer for comprehensive jailbreak and crisis detection.

SHIELD Features:
- Detects 9 jailbreak pattern categories (GODMODE dividers, l33tspeak, boundary
  inversion, token injection, DAN/GODMODE keywords, refusal inversion, persona
  injection, encoding evasion)
- Detects 7 crisis signal categories (suicidal ideation, method seeking,
  l33tspeak evasion, substance seeking, despair, farewell, self-harm)
- Returns 4 verdicts: CLEAN, JAILBREAK_DETECTED, CRISIS_DETECTED,
  CRISIS_UNDER_ATTACK
- Routes crisis content ONLY to Safe Six verified models

Safety Requirements:
- <5ms detection latency (regex-only, no ML)
- 988 Suicide & Crisis Lifeline included in crisis responses

Addresses: Issues #72, #74, #75
2026-03-31 16:35:40 +00:00
f0ac54b8f1 Merge pull request '[sovereign] The Orchestration Client Timmy Deserves' (#76) from gemini/sovereign-gitea-client into main
Some checks failed
Nix / nix (ubuntu-latest) (push) Failing after 3s
Docker Build and Publish / build-and-push (push) Failing after 23s
Tests / test (push) Failing after 8m42s
Nix / nix (macos-latest) (push) Has been cancelled
2026-03-31 12:10:46 +00:00
7b7428a1d9 [sovereign] The Orchestration Client Timmy Deserves
Some checks failed
Docker Build and Publish / build-and-push (pull_request) Failing after 27s
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Failing after 24s
Tests / test (pull_request) Failing after 21s
WHAT THIS IS
============
The Gitea client is the API foundation that every orchestration
module depends on — graph_store.py, knowledge_ingester.py, the
playbook engine, and tasks.py in timmy-home.

Until now it was 60 lines and 3 methods (get_file, create_file,
update_file). This made every orchestration module hand-roll its
own urllib calls with no retry, no pagination, and no error
handling.

WHAT CHANGED
============
Expanded from 60 → 519 lines. Still zero dependencies (pure stdlib).

  File operations:   get_file, create_file, update_file (unchanged API)
  Issues:            list, get, create, comment, find_unassigned
  Pull Requests:     list, get, create, review, get_diff
  Branches:          create, delete
  Labels:            list, add_to_issue
  Notifications:     list, mark_read
  Repository:        get_repo, list_org_repos

RELIABILITY
===========
  - Retry with random jitter on 429/5xx (same pattern as SessionDB)
  - Automatic pagination across multi-page results
  - Defensive None handling on assignees/labels (audit bug fix)
  - GiteaError exception with status_code/url attributes
  - Token loading from ~/.timmy/gemini_gitea_token or env vars

WHAT IT FIXES
=============
  - tasks.py crashed with TypeError when iterating None assignees
    on issues created without setting one (Gitea returns null).
    find_unassigned_issues() now uses 'or []' on the assignees
    field, matching the same defensive pattern used in SessionDB.

  - No module provided issue commenting, PR reviewing, branch
    management, or label operations — the playbook engine could
    describe these operations but not execute them.

BACKWARD COMPATIBILITY
======================
The three original methods (get_file, create_file, update_file)
maintain identical signatures. graph_store.py and
knowledge_ingester.py import and call them without changes.

TESTS
=====
  27 new tests — all pass:
  - Core HTTP (5): auth, params, body encoding, None filtering
  - Retry (5): 429, 502, 503, non-retryable 404, max exhaustion
  - Pagination (3): single page, multi-page, max_items
  - Issues (4): list, comment, None assignees, label exclusion
  - Pull requests (2): create, review
  - Backward compat (4): signatures, constructor env fallback
  - Token config (2): missing file, valid file
  - Error handling (2): attributes, exception hierarchy

Signed-off-by: gemini <gemini@hermes.local>
2026-03-31 07:52:56 -04:00
cb0cf51adf security: Fix V-006 MCP OAuth Deserialization (CVSS 8.8 CRITICAL)
Some checks failed
Nix / nix (ubuntu-latest) (pull_request) Failing after 15s
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Failing after 19s
Docker Build and Publish / build-and-push (pull_request) Failing after 28s
Tests / test (pull_request) Failing after 9m43s
Nix / nix (macos-latest) (pull_request) Has been cancelled
- Replace pickle with JSON + HMAC-SHA256 state serialization
- Add constant-time signature verification
- Implement replay attack protection with nonce expiration
- Add comprehensive security test suite (54 tests)
- Harden token storage with integrity verification

Resolves: V-006 (CVSS 8.8)
2026-03-31 00:37:14 +00:00
e2e88b271d test: add comprehensive security test coverage
Some checks failed
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Successful in 29s
Docker Build and Publish / build-and-push (pull_request) Failing after 37s
Tests / test (pull_request) Failing after 28s
Add extensive test suites for all critical security fixes:
- tests/tools/test_path_traversal.py: Path traversal detection tests
- tests/tools/test_command_injection.py: Command injection prevention tests
- tests/tools/test_interrupt.py: Race condition validation tests
- validate_security.py: Automated security validation suite

Coverage includes:
- Unix/Windows traversal patterns
- URL-encoded bypass attempts
- Null byte injection
- Concurrent access race conditions
- Subprocess security patterns

Refs: Issue #51 - Test coverage gaps
Refs: V-001, V-002, V-007 security fixes
2026-03-30 23:49:20 +00:00
13265971df security: fix race condition in interrupt propagation (V-007)
Some checks failed
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Successful in 29s
Docker Build and Publish / build-and-push (pull_request) Failing after 38s
Tests / test (pull_request) Failing after 28s
Add proper RLock synchronization to prevent race conditions when multiple
threads access interrupt state simultaneously.

Changes:
- tools/interrupt.py: Add RLock, nesting count tracking, new APIs
- tools/terminal_tool.py: Remove direct _interrupt_event exposure
- tests/tools/test_interrupt.py: Comprehensive race condition tests

CVSS: 8.5 (High)
Refs: V-007, Issue #48
Fixes: CWE-362: Concurrent Execution using Shared Resource
2026-03-30 23:47:04 +00:00
10271c6b44 security: fix command injection vulnerabilities (CVSS 9.8)
Some checks failed
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Failing after 25s
Tests / test (pull_request) Failing after 24s
Docker Build and Publish / build-and-push (pull_request) Failing after 35s
Replace shell=True with list-based subprocess execution to prevent
command injection via malicious user input.

Changes:
- tools/transcription_tools.py: Use shlex.split() + shell=False
- tools/environments/docker.py: List-based commands with container ID validation

Fixes CVE-level vulnerability where malicious file paths or container IDs
could inject arbitrary commands.

CVSS: 9.8 (Critical)
Refs: V-001 in SECURITY_AUDIT_REPORT.md
2026-03-30 23:15:11 +00:00
5b948356b7 Merge PR #9: SOTA Sovereign Intersymbolic Knowledge Graph (SIKG)
Some checks failed
Tests / test (push) Failing after 17s
Docker Build and Publish / build-and-push (push) Failing after 30s
Nix / nix (ubuntu-latest) (push) Failing after 5s
Nix / nix (macos-latest) (push) Has been cancelled
Features:
- tools/graph_store.py: Sovereign triple-store with Gitea persistence
- agent/symbolic_memory.py: Neural-to-symbolic bridge with multi-hop search
- skills/memory/intersymbolic_graph.py: Graph query skill
- Integrated into KnowledgeIngester for automatic symbolic extraction

Tests added:
- tests/tools/test_graph_store.py (127 lines)
- tests/agent/test_symbolic_memory.py (144 lines)

Reviewed and merged by Allegro (BURN MODE).
2026-03-30 22:31:43 +00:00
Teknium
fb634068df fix(security): extend secret redaction to ElevenLabs, Tavily and Exa API keys (#3920)
Some checks failed
Nix / nix (ubuntu-latest) (push) Failing after 3m9s
Docker Build and Publish / build-and-push (push) Failing after 4m1s
Tests / test (push) Failing after 29m41s
Nix / nix (macos-latest) (push) Has been cancelled
ElevenLabs (sk_), Tavily (tvly-), and Exa (exa_) keys were not covered
by _PREFIX_PATTERNS, leaking in plain text via printenv or log output.

Salvaged from PR #3790 by @memosr. Tests rewritten with correct
assertions (original tests had vacuously true checks).

Co-authored-by: memosr <memosr@users.noreply.github.com>
2026-03-30 08:13:01 -07:00
Teknium
1e896b0251 fix: resolve 7 failing CI tests (#3936)
1. matrix voice: _on_room_message_media unconditionally overwrote
   media_urls with the image cache path (always None for non-images),
   wiping the locally-cached voice path. Now only overrides when
   cached_path is truthy.

2. cli_tools_command: /tools disable no longer prompts for confirmation
   (input() removed in earlier commit to fix TUI hang), but tests still
   expected the old Y/N prompt flow. Updated tests to match current
   behavior (direct apply + session reset).

3. slack app_mention: connect() was refactored for multi-workspace
   (creates AsyncWebClient per token), but test only mocked the old
   self._app.client path. Added AsyncWebClient and acquire_scoped_lock
   mocks.

4. website_policy: module-level _cached_policy from earlier tests caused
   fast-path return of None. Added invalidate_cache() before assertion.

5. codex 401 refresh: already passing on current main (fixed by
   intervening commit).
2026-03-30 08:10:14 -07:00
Teknium
5148682b43 feat: mount skills directory into all remote backends with live sync (#3890)
Skills with scripts/, templates/, and references/ subdirectories need
those files available inside sandboxed execution environments. Previously
the skills directory was missing entirely from remote backends.

Live sync — files stay current as credentials refresh and skills update:
- Docker/Singularity: bind mounts are inherently live (host changes
  visible immediately)
- Modal: _sync_files() runs before each command with mtime+size caching,
  pushing only changed credential and skill files (~13μs no-op overhead)
- SSH: rsync --safe-links before each command (naturally incremental)
- Daytona: _upload_if_changed() with mtime+size caching before each command

Security — symlink filtering:
- Docker/Singularity: sanitized temp copy when symlinks detected
- Modal/Daytona: iter_skills_files() skips symlinks
- SSH: rsync --safe-links skips symlinks pointing outside source tree
- Temp dir cleanup via atexit + reuse across calls

Non-root user support:
- SSH: detects remote home via echo $HOME, syncs to $HOME/.hermes/
- Daytona: detects sandbox home before sync, uploads to $HOME/.hermes/
- Docker/Modal/Singularity: run as root, /root/.hermes/ is correct

Also:
- credential_files.py: fix name/path key fallback in required_credential_files
- Singularity, SSH, Daytona: gained credential file support
- 14 tests covering symlink filtering, name/path fallback, iter_skills_files
2026-03-30 02:45:41 -07:00
Teknium
ee61485cac feat(matrix): support native voice messages via MSC3245 (#3877)
* feat(matrix): support native voice messages

* fix: skip matrix voice tests when matrix-nio not installed

---------

Co-authored-by: Carlos Alberto Pereira Gomes <carlosapgomes@users.noreply.github.com>
2026-03-30 00:02:51 -07:00
kshitij
c288bbfb57 fix(cli): prevent status bar wrapping into duplicate rows (#3883)
- measure status bar display width using prompt_toolkit cell widths
- trim rendered status text when fragments would overflow
- add a final single-fragment fallback to prevent wrapping
- update width assertions to validate display cells instead of len()
2026-03-29 23:59:07 -07:00
Teknium
227601c200 feat(discord): add message processing reactions (salvage #1980) (#3871)
Adds lifecycle hooks to the base platform adapter so Discord (and future
platforms) can react to message processing events:

  👀  when processing starts
    on successful completion (delivery confirmed)
    on failure, error, or cancellation

Implementation:
- base.py: on_processing_start/on_processing_complete hooks with
  _run_processing_hook error isolation wrapper; delivery tracking
  via _record_delivery closure for accurate success detection
- discord.py: _add_reaction/_remove_reaction helpers + hook overrides
- Tests for base hook lifecycle and Discord-specific reactions

Co-authored-by: alanwilhelm <alanwilhelm@users.noreply.github.com>
2026-03-29 21:55:23 -07:00
Teknium
fd29933a6d fix: use argparse entrypoint in top-level launcher (#3874)
The ./hermes convenience script still used the legacy Fire-based
cli.main wrapper, which doesn't support subcommands (gateway, cron,
doctor, etc.). The installed 'hermes' command already uses
hermes_cli.main:main (argparse) — this aligns the launcher.

Salvaged from PR #2009 by gito369.
2026-03-29 21:54:36 -07:00
Teknium
839f798b74 feat(telegram): add group mention gating and regex triggers (#3870)
Adds Discord-style mention gating for Telegram groups:
- telegram.require_mention: gate group messages (default: false)
- telegram.mention_patterns: regex wake-word triggers
- telegram.free_response_chats: bypass gating for specific chats

When require_mention is enabled, group messages are accepted only for:
- slash commands
- replies to the bot
- @botusername mentions
- regex wake-word pattern matches

DMs remain unrestricted. @mention text is stripped before passing to
the agent. Invalid regex patterns are ignored with a warning.

Config bridges follow the existing Discord pattern (yaml → env vars).

Cherry-picked and adapted from PR #1977 by mcleay. Fixed ChatType
comparison to work without python-telegram-bot installed (uses string
matching instead of enum, consistent with other entity_type checks).

Co-authored-by: mcleay <mcleay@users.noreply.github.com>
2026-03-29 21:53:59 -07:00
Teknium
366bfc3c76 fix(setup): auto-install matrix-nio during hermes setup (#3873)
Setup previously only printed a manual install hint for matrix-nio,
causing the gateway to crash with 'matrix-nio not installed' after
configuring Matrix. Now auto-installs matrix-nio (or matrix-nio[e2e]
when E2EE is enabled) using the same uv-first/pip-fallback pattern
as Daytona and Modal backends.

Also adds hermes-agent[matrix] to the [all] extra in pyproject.toml
and a regression test to keep it there.

Co-authored-by: Gutslabs <Gutslabs@users.noreply.github.com>
Co-authored-by: cutepawss <cutepawss@users.noreply.github.com>
2026-03-29 21:53:28 -07:00
Teknium
b4ceb541a7 fix(terminal): preserve partial output when command times out (#3868)
When a command timed out, all captured output was discarded — the agent
only saw 'Command timed out after Xs' with zero context. Now returns
the buffered output followed by a timeout marker, matching the existing
interrupt path behavior.

Salvaged from PR #3286 by @binhnt92.

Co-authored-by: nguyen binh <binhnt92@users.noreply.github.com>
2026-03-29 21:51:44 -07:00
Teknium
ce2841f3c9 feat(gateway): add WeCom (Enterprise WeChat) platform support (#3847)
Adds WeCom as a gateway platform adapter using the AI Bot WebSocket
gateway for real-time bidirectional communication. No public endpoint
or new pip dependencies needed (uses existing aiohttp + httpx).

Features:
- WebSocket persistent connection with auto-reconnect (exponential backoff)
- DM and group messaging with configurable access policies
- Media upload/download with AES decryption for encrypted attachments
- Markdown rendering, quote context preservation
- Proactive + passive reply message modes
- Chunked media upload pipeline (512KB chunks)

Cherry-picked from PR #1898 by EvilRan with:
- Moved to current main (PR was 300 commits behind)
- Skipped base.py regressions (reply_to additions are good but belong
  in a separate PR since they affect all platforms)
- Fixed test assertions to match current base class send() signature
  (reply_to=None kwarg now explicit)
- All 16 integration points added surgically to current main
- No new pip dependencies (aiohttp + httpx already installed)

Fixes #1898

Co-authored-by: EvilRan <EvilRan@users.noreply.github.com>
2026-03-29 21:29:13 -07:00
Teknium
3fad1e7cc1 fix(cron): resolve human-friendly delivery labels via channel directory (#3860)
Cron jobs configured with deliver labels from send_message(action='list')
like 'whatsapp:Alice (dm)' passed the label as a literal chat_id.
WhatsApp bridge failed with jidDecode error since 'Alice (dm)' isn't
a valid JID.

Now _resolve_delivery_target() strips display suffixes like ' (dm)' and
resolves human-friendly names via the channel directory before using
them. Raw IDs pass through unchanged when the directory has no match.

Fixes #1945.
2026-03-29 21:24:17 -07:00
Teknium
86ac23c8da fix(auth): stop silently falling back to OpenRouter when no provider is configured (#3862)
Previously, when no API keys or provider credentials were found, Hermes
silently defaulted to OpenRouter + Claude Opus. This caused confusion
when users configured local servers (LM Studio, Ollama, etc.) with a
typo or unrecognized provider name — the system would silently route to
OpenRouter instead of telling them something was wrong.

Changes:
- resolve_provider() now raises AuthError when no credentials are found
  instead of returning 'openrouter' as a silent fallback
- Added local server aliases: lmstudio, ollama, vllm, llamacpp → custom
- Removed hardcoded 'anthropic/claude-opus-4.6' fallback from gateway
  and cron scheduler (they read from config.yaml instead)
- Updated cli-config.yaml.example with complete provider documentation
  including all supported providers, aliases, and local server setup
2026-03-29 21:06:35 -07:00
Teknium
3cc50532d1 fix: auxiliary client uses placeholder key for local servers without auth (#3842)
Local inference servers (Ollama, llama.cpp, vLLM, LM Studio) don't
require API keys, but the auxiliary client's _resolve_custom_runtime()
rejected endpoints with empty keys — causing the auto-detection chain
to skip the user's local server entirely.  This broke compression,
summarization, and memory flush for users running local models without
an OpenRouter/cloud API key.

The main CLI already had this fix (PR #2556, 'no-key-required'
placeholder), but the auxiliary client's resolution path was missed.

Two fixes:
- _resolve_custom_runtime(): use 'no-key-required' placeholder instead
  of returning None when base_url is present but key is empty
- resolve_provider_client() custom branch: same placeholder fallback
  for explicit_base_url without explicit_api_key

Updates 2 tests that expected the old (broken) behavior.
2026-03-29 21:05:36 -07:00
Teknium
2d607d36f6 fix(security): catch sensitive path writes in approval checks (#3859)
Co-authored-by: Gutslabs <gutslabsxyz@gmail.com>
2026-03-29 20:57:57 -07:00
Teknium
5e67fc8c40 fix(vision): reject non-image files and enforce website policy (salvage #1940) (#3845)
Three safety gaps in vision_analyze_tool:

1. Local files accepted without checking if they're actually images —
   a renamed text file would get base64-encoded and sent to the model.
   Now validates magic bytes (PNG, JPEG, GIF, BMP, WebP, SVG).

2. No website policy enforcement on image URLs — blocked domains could
   be fetched via the vision tool. Now checks before download.

3. No redirect check — if an allowed URL redirected to a blocked domain,
   the download would proceed. Now re-checks the final URL.

Fixed one test that needed _validate_image_url mocked to bypass DNS
resolution on the fake blocked.test domain (is_safe_url does DNS
checks that were added after the original PR).

Co-authored-by: GutSlabs <GutSlabs@users.noreply.github.com>
2026-03-29 20:55:04 -07:00
Teknium
9d28f4aba3 fix: add gpt-5.4-mini to Codex fallback catalog (#3855)
Co-authored-by: Clippy <clippy@grads.flow>
2026-03-29 20:10:00 -07:00
Teknium
3e203de125 fix(skills): block category path traversal in skill manager (#3844)
Validate category names in _create_skill() before using them as
filesystem path segments. Previously, categories like '../escape' or
'/tmp/pwned' could write skill files outside ~/.hermes/skills/.

Adds _validate_category() that rejects slashes, backslashes, absolute
paths, and non-alphanumeric characters (reuses existing VALID_NAME_RE).

Tests: 5 new tests for traversal, absolute paths, and valid categories.

Salvaged from PR #1939 by Gutslabs.
2026-03-29 20:08:22 -07:00
Teknium
2d264a4562 fix(tests): resolve 10 CI failures across hooks, tiktoken, plugins (#3848)
test_hooks.py (7 failures): Built-in boot-md hook was always loaded
by _register_builtin_hooks(), adding +1 to every expected hook count.
Mock out built-in registration in TestDiscoverAndLoad so tests isolate
user-hook discovery logic.

test_tool_token_estimation.py (2 failures): tiktoken is not in
core/[all] dependencies. The estimation function gracefully returns {}
when tiktoken is missing, but tests expected non-empty results. Added
skipif markers for tests that need tiktoken.

test_plugins_cmd.py (1 failure): bare 'hermes plugins' now dispatches
to cmd_toggle() (interactive curses UI) instead of cmd_list(). Updated
test to match the new behavior.
2026-03-29 20:05:59 -07:00
Teknium
3e2c8c529b fix(whatsapp): resolve LID↔phone aliases in allowlist matching (#3830)
WhatsApp DMs can arrive with LID sender IDs even when
WHATSAPP_ALLOWED_USERS is configured with phone numbers. The allowlist
check now reads bridge session mapping files (lid-mapping-*.json) to
resolve phone↔LID aliases, matching users regardless of which
identifier format the message uses.

Both the Python gateway (_is_user_authorized) and the Node bridge
(allowlist.js) now share the same mapping-file-based resolution logic.

Co-authored-by: Frederico Ribeiro <fr@tecompanytea.com>
2026-03-29 18:21:50 -07:00
Teknium
ca4907dfbc feat(gateway): add Feishu/Lark platform support (#3817)
Adds Feishu (ByteDance's enterprise messaging platform) as a gateway
platform adapter with full feature parity: WebSocket + webhook transports,
message batching, dedup, rate limiting, rich post/card content parsing,
media handling (images/audio/files/video), group @mention gating,
reaction routing, and interactive card button support.

Cherry-picked from PR #1793 by penwyp with:
- Moved to current main (PR was 458 commits behind)
- Fixed _send_with_retry shadowing BasePlatformAdapter method (renamed to
  _feishu_send_with_retry to avoid signature mismatch crash)
- Fixed import structure: aiohttp/websockets imported independently of
  lark_oapi so they remain available when SDK is missing
- Fixed get_hermes_home import (hermes_constants, not hermes_cli.config)
- Added skip decorators for tests requiring lark_oapi SDK
- All 16 integration points added surgically to current main

New dependency: lark-oapi>=1.5.3,<2 (optional, pip install hermes-agent[feishu])

Fixes #1788

Co-authored-by: penwyp <penwyp@users.noreply.github.com>
2026-03-29 18:17:42 -07:00
Teknium
5ca6d681f0 feat(skills): add memento-flashcards optional skill (#3827)
* feat(skills): add memento-flashcards skill

* docs(skills): clarify memento-flashcards interaction model

* fix: use HERMES_HOME env var for profile-safe data path

---------

Co-authored-by: Magnus Ahmad <magnus.ahmad@gmail.com>
2026-03-29 16:52:52 -07:00
Teknium
df806bdbaf feat(cron): add cron.wrap_response config to disable delivery wrapping (#3807)
Adds a config option to suppress the header/footer text that wraps
cron job responses when delivered to messaging platforms.

Set cron.wrap_response: false in config.yaml for clean output without
the 'Cronjob Response: <name>' header and 'The agent cannot see this
message' footer.  Default is true (preserves current behavior).
2026-03-29 16:31:01 -07:00
Teknium
0ef80c5f32 fix(whatsapp): reuse persistent aiohttp session across requests (#3818)
Replace per-request aiohttp.ClientSession() in every WhatsApp adapter
method with a single persistent self._http_session, matching the pattern
used by Mattermost, HomeAssistant, and SMS adapters.

Changes:
- Create self._http_session in connect(), close in disconnect()
- All bridge HTTP calls (send, edit, send-media, typing, get_chat_info,
  poll_messages) now use the shared session
- Explicitly cancel _poll_task on disconnect() instead of relying
  solely on self._running = False
- Health-check sessions in connect() remain ephemeral (persistent
  session not yet created at that point)
- Remove per-method ImportError guards for aiohttp (always available
  when gateway runs via [messaging] extras)

Salvaged from PR #1851 by Himess. The _poll_task storage was already
on main from PR #3267; this adds the disconnect cancellation and the
persistent session.

Tests: 4 new tests for session close, already-closed skip, poll task
cancellation, and done-task skip.
2026-03-29 16:25:20 -07:00
Teknium
252fbea005 feat(providers): add ordered fallback provider chain (salvage #1761) (#3813)
Extends the single fallback_model mechanism into an ordered chain.
When the primary model fails, Hermes tries each fallback provider in
sequence until one succeeds or the chain is exhausted.

Config format (new):
  fallback_providers:
    - provider: openrouter
      model: anthropic/claude-sonnet-4
    - provider: openai
      model: gpt-4o

Legacy single-dict fallback_model format still works unchanged.

Key fix vs original PR: the call sites in the retry loop now use
_fallback_index < len(_fallback_chain) instead of the old one-shot
_fallback_activated guard, so the chain actually advances through
all configured providers.

Changes:
- run_agent.py: _fallback_chain list + _fallback_index replaces
  one-shot _fallback_model; _try_activate_fallback() advances
  through chain; failed provider resolution skips to next entry;
  call sites updated to allow chain advancement
- cli.py: reads fallback_providers with legacy fallback_model compat
- gateway/run.py: same
- hermes_cli/config.py: fallback_providers: [] in DEFAULT_CONFIG
- tests: 12 new chain tests + 6 existing test fixtures updated

Co-authored-by: uzaylisak <uzaylisak@users.noreply.github.com>
2026-03-29 16:04:53 -07:00
Teknium
c774833667 fix(banner): show honcho tools as available when configured (#3810)
The honcho check_fn only checked runtime session state, which isn't
set until the agent initializes. At banner time, honcho tools showed
as red/disabled even when properly configured.

Now checks configuration (enabled + api_key/base_url) as a fallback
when the session context isn't active yet. Fast path (session active)
unchanged; slow path (config check) only runs at banner time.

Adds 4 tests covering: session active, configured but no session,
not configured, and import failure graceful fallback.

Closes #1843.
2026-03-29 15:55:05 -07:00
Teknium
d5d22fe7ba feat(mcp): dynamic tool discovery via notifications/tools/list_changed (#3812)
When a connected MCP server sends a ToolListChangedNotification (per the
MCP spec), Hermes now automatically re-fetches the tool list, deregisters
removed tools, and registers new ones — without requiring a restart.

This enables MCP servers with dynamic toolsets (e.g. GitHub MCP with
GITHUB_DYNAMIC_TOOLSETS=1) to add/remove tools at runtime.

Changes:
- registry.py: add ToolRegistry.deregister() for nuke-and-repave refresh
- mcp_tool.py: extract _register_server_tools() from
  _discover_and_register_server() as a shared helper for both initial
  discovery and dynamic refresh
- mcp_tool.py: add _make_message_handler() and _refresh_tools() on
  MCPServerTask, wired into all 3 ClientSession sites (stdio, new HTTP,
  deprecated HTTP)
- Graceful degradation: silently falls back to static discovery when the
  MCP SDK lacks notification types or message_handler support
- 8 new tests covering registration, refresh, handler dispatch, and
  deregister

Salvaged from PR #1794 by shivvor2.
2026-03-29 15:52:54 -07:00
Teknium
38d694f559 fix(gateway): apply home channel env overrides consistently (#3808)
Home channel env vars (SLACK_HOME_CHANNEL, SIGNAL_HOME_CHANNEL, etc.)
for Slack, Signal, Mattermost, Matrix, Email, and SMS were nested
inside the credential-env blocks, so they were ignored when the
platform was already configured via config.yaml.

Moved the home channel handling outside the credential blocks with a
Platform.X in config.platforms guard, matching the existing pattern
for Telegram and Discord.

Co-authored-by: cutepawss <cutepawss@users.noreply.github.com>
2026-03-29 15:48:51 -07:00
Teknium
6716e66e89 feat: add MCP server mode — hermes mcp serve (#3795)
hermes mcp serve starts a stdio MCP server that lets any MCP client
(Claude Code, Cursor, Codex, etc.) interact with Hermes conversations.

Matches OpenClaw's 9-tool channel bridge surface:

Tools exposed:
- conversations_list: list active sessions across all platforms
- conversation_get: details on one conversation
- messages_read: read message history
- attachments_fetch: extract non-text content from messages
- events_poll: poll for new events since a cursor
- events_wait: long-poll / block until next event (near-real-time)
- messages_send: send to any platform via send_message_tool
- channels_list: browse available messaging targets
- permissions_list_open: list pending approval requests
- permissions_respond: allow/deny approvals

Architecture:
- EventBridge: background thread polls SessionDB for new messages,
  maintains in-memory event queue with waiter support
- Reads sessions.json + SessionDB directly (no gateway dep for reads)
- Reuses send_message_tool for sending (same platform adapters)
- FastMCP server with stdio transport
- Zero new dependencies (uses existing mcp>=1.2.0 optional dep)

Files:
- mcp_serve.py: MCP server + EventBridge (~600 lines)
- hermes_cli/main.py: added serve sub-parser to hermes mcp
- hermes_cli/mcp_config.py: route serve action to run_mcp_server
- tests/test_mcp_serve.py: 53 tests
- docs: updated MCP page + CLI commands reference
2026-03-29 15:47:19 -07:00
Teknium
8eb70a6885 fix(email): close SMTP and IMAP connections on failure (#3804)
SMTP connections in _send_email() and _send_email_with_attachment() leak
when login() or send_message() raises before quit() is reached. Both now
wrapped in try/finally with a close() fallback if quit() also fails.

IMAP connection in _fetch_new_messages() leaks when UID processing raises,
since logout() sits after the loop. Restructured with try/finally so
logout() runs unconditionally.

Co-authored-by: Himess <Himess@users.noreply.github.com>
2026-03-29 15:38:32 -07:00
Teknium
ee3d2941cc feat: show estimated tool token context in hermes tools checklist (#3805)
* feat: show estimated tool token context in hermes tools checklist

Adds a live token estimate indicator to the bottom of the interactive
tool configuration checklist (hermes tools / hermes setup). As users
toggle toolsets on/off, the total estimated context cost updates in
real time.

Implementation:
- tools/registry.py: Add get_schema() for check_fn-free schema access
- hermes_cli/curses_ui.py: Add optional status_fn callback to
  curses_checklist — renders at bottom-right of terminal, stays fixed
  while items scroll
- hermes_cli/tools_config.py: Add _estimate_tool_tokens() using
  tiktoken (cl100k_base, already installed) to count tokens in the
  JSON-serialised OpenAI-format tool schemas. Results are cached
  per-process. The status function deduplicates overlapping tools
  (e.g. browser includes web_search) for accurate totals.
- 12 new tests covering estimation, caching, graceful degradation
  when tiktoken is unavailable, status_fn wiring, deduplication,
  and the numbered fallback display

* fix: use effective toolsets (includes plugins) for token estimation index mapping

The status_fn closure built ts_keys from CONFIGURABLE_TOOLSETS but the
checklist uses _get_effective_configurable_toolsets() which appends plugin
toolsets. With plugins present, the indices would mismatch, causing
IndexError when selecting a plugin toolset.
2026-03-29 15:36:56 -07:00
Teknium
57481c8ac5 fix(tools): implement send_message routing for Matrix, Mattermost, HomeAssistant, DingTalk (#3796)
* fix(tools): implement send_message routing for Matrix, Mattermost, HomeAssistant, DingTalk

Matrix, Mattermost, HomeAssistant, and DingTalk were present in
platform_map but fell through to the "not yet implemented" else branch,
causing send_message tool calls to silently fail on these platforms.

Add four async sender functions:
- _send_mattermost: POST /api/v4/posts via Mattermost REST API
- _send_matrix: PUT /_matrix/client/v3/rooms/.../send via Matrix CS API
- _send_homeassistant: POST /api/services/notify/notify via HA REST API
- _send_dingtalk: POST to session webhook URL

Add routing in _send_to_platform() and 17 unit tests covering success,
HTTP errors, missing config, env var fallback, and Matrix txn_id uniqueness.

* fix: pass platform tokens explicitly to Mattermost/Matrix/HA senders

The original PR passed pconfig.extra to sender functions, but tokens
live at pconfig.token (not in extra). This caused the senders to always
fall through to env var lookup instead of using the gateway-resolved
token.

Changes:
- Mattermost/Matrix/HA: accept token as first arg, matching the
  Telegram/Discord/Slack sender pattern
- DingTalk: add DINGTALK_WEBHOOK_URL env var fallback + docstring
  explaining the session-webhook vs robot-webhook difference
- Tests updated for new signatures + new DingTalk env var test

---------

Co-authored-by: sprmn24 <oncuevtv@gmail.com>
2026-03-29 15:17:46 -07:00
Teknium
f6db1b27ba feat: add profiles — run multiple isolated Hermes instances (#3681)
Each profile is a fully independent HERMES_HOME with its own config,
API keys, memory, sessions, skills, gateway, cron, and state.db.

Core module: hermes_cli/profiles.py (~900 lines)
  - Profile CRUD: create, delete, list, show, rename
  - Three clone levels: blank, --clone (config), --clone-all (everything)
  - Export/import: tar.gz archive for backup and migration
  - Wrapper alias scripts (~/.local/bin/<name>)
  - Collision detection for alias names
  - Sticky default via ~/.hermes/active_profile
  - Skill seeding via subprocess (handles module-level caching)
  - Auto-stop gateway on delete with disable-before-stop for services
  - Tab completion generation for bash and zsh

CLI integration (hermes_cli/main.py):
  - _apply_profile_override(): pre-import -p/--profile flag + sticky default
  - Full 'hermes profile' subcommand: list, use, create, delete, show,
    alias, rename, export, import
  - 'hermes completion bash/zsh' command
  - Multi-profile skill sync in hermes update

Display (cli.py, banner.py, gateway/run.py):
  - CLI prompt: 'coder ❯' when using a non-default profile
  - Banner shows profile name
  - Gateway startup log includes profile name

Gateway safety:
  - Token locks: Discord, Slack, WhatsApp, Signal (extends Telegram pattern)
  - Port conflict detection: API server, webhook adapter

Diagnostics (hermes_cli/doctor.py):
  - Profile health section: lists profiles, checks config, .env, aliases
  - Orphan alias detection: warns when wrapper points to deleted profile

Tests (tests/hermes_cli/test_profiles.py):
  - 71 automated tests covering: validation, CRUD, clone levels, rename,
    export/import, active profile, isolation, alias collision, completion
  - Full suite: 6760 passed, 0 new failures

Documentation:
  - website/docs/user-guide/profiles.md: full user guide (12 sections)
  - website/docs/reference/profile-commands.md: command reference (12 commands)
  - website/docs/reference/faq.md: 6 profile FAQ entries
  - website/sidebars.ts: navigation updated
2026-03-29 10:41:20 -07:00
Teknium
ba1b600bce fix(tests): align skill/setup and platform mocks with current behavior (#3721)
- Skill invocation: no secret capture callback so SSH remote setup note is emitted
- Patch agent.skill_utils.sys for platform checks (skill_matches_platform)
- Skip CLAUDE.md priority test on Darwin (case-insensitive FS)

Made-with: Cursor

Co-authored-by: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>
2026-03-29 07:51:43 -07:00