* fix: decode HTML entities before markdown processing + zh/zh-Hant translations (#239)
Adds decode() helper in renderMd() to fix double-escaping of HTML entities
from LLM output (e.g. <code> becoming &lt;code&gt; instead
of rendering). XSS-safe: decode runs before esc(), only 5 entity patterns.
Also adds 40+ missing zh (Simplified Chinese) translation keys and a new
zh-Hant (Traditional Chinese) locale with 163 keys.
Fix applied: removed duplicate settings_label_notifications key in both
zh and zh-Hant locales.
Fixes#240
* fix: restore custom model list discovery with config api key (#238)
get_available_models() now reads api_key from config.yaml before env vars:
1. model.api_key
2. providers.<active>.api_key / providers.custom.api_key
3. env var fallbacks (HERMES_API_KEY, OPENAI_API_KEY, etc.)
Also adds OpenAI/Python User-Agent header and a regression test covering
authenticated /v1/models discovery.
Fixes users with LM Studio / Ollama custom endpoints configured in
config.yaml whose model picker silently collapsed to the default model.
* feat: Docker UID/GID matching to avoid root-owned .hermes files (#237)
Adds docker_init.bash with hermeswebuitoo/hermeswebui user pattern so
container files match the host user UID/GID. Prevents .hermes volume
mounts from being owned by root when using a non-root host user.
Configure via WANTED_UID and WANTED_GID env vars (default 1000/1000).
Readme updated with setup instructions.
Fix applied: removed duplicate WANTED_GID=1000 line in docker-compose.yml
that was overriding the ${GID:-1000} variable expansion.
* security: redact credentials from API responses and fix credential file permissions (#243)
Adds response-layer credential redaction to three endpoints:
- GET /api/session — messages[], tool_calls[], and title
- GET /api/session/export — download also redacted
- SSE done event — session payload in stream
- GET /api/memory — MEMORY.md and USER.md content
Adds api/startup.py with fix_credential_permissions() at server startup.
Adds 13 tests in tests/test_security_redaction.py.
Merged with #237 container detection changes in server.py.
* fix: cancel button now interrupts agent and cleans up UI state (#244)
Wires agent.interrupt() into cancel_stream() so the backend actually
stops tool execution when the user clicks Cancel, rather than only
stopping the SSE stream while the agent keeps running.
Changes:
- api/config.py: adds AGENT_INSTANCES dict (stream_id -> AIAgent)
- api/streaming.py: stores agent in AGENT_INSTANCES after creation,
checks CANCEL_FLAGS immediately after store (race condition fix),
calls agent.interrupt() in cancel_stream(), cleans up in finally block
- static/boot.js: removes stale setStatus(cancelling) call
- static/messages.js: setBusy(false)/setStatus('') unconditionally on cancel
Race condition fix: after storing agent in AGENT_INSTANCES, immediately
checks if CANCEL_FLAGS[stream_id] is already set (cancel arrived during
agent init) and interrupts before starting. Check is inside the same
STREAMS_LOCK acquisition, making it atomic.
New test file: tests/test_cancel_interrupt.py with 6 unit tests.
* docs: v0.46.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
When hermes-agent is not discoverable (common in Docker), create_profile_api()
raised a hard RuntimeError while list and delete already had manual fallbacks.
Changes:
- Add _create_profile_fallback() that bootstraps profile directory structure
directly (matching upstream hermes_cli.profiles: 8 subdirs + config clone)
- Extract _validate_profile_name() so validation works without hermes_cli
- Add constants _PROFILE_ID_RE, _PROFILE_DIRS, _CLONE_CONFIG_FILES matching
upstream hermes-agent
- Remove :ro from docker-compose.yml hermes home mount so profiles dir is
writable inside the container
Closes#44
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BUG-1 (critical): CSS cascade — .sidebar{position:relative} and
.rightpanel{position:relative} at line 528/530 appeared after the
@media(max-width:640px) block and silently overrode the position:fixed
overlay behavior needed for the mobile slide-in. Wrapped both in
@media(min-width:641px) so they only apply on desktop.
BUG-2 (medium): mobileSwitchPanel() in boot.js always reopened the
sidebar overlay after closing it, with a stale comment saying 'close
after a moment' but no actual auto-close. For the 'chat' panel, the
content lives in the main area — reopening the sidebar obstructs it.
Fixed: only open sidebar for non-chat panels; chat tap closes sidebar.
BUG-3 (medium): Dockerfile was missing 'pip install -r requirements.txt'.
pyyaml (required by api/config.py) is not in the python:3.12-slim base
image — the container would fail at startup with ImportError.
SEC-2 (medium): No .dockerignore — COPY . /app included .git/, tests/,
and .env* in every image. Added .dockerignore excluding these.
NIT-3: docker-compose.yml used ${HERMES_HOME:-~/.hermes} but Docker
Compose does not shell-expand ~ in default values. Changed to
${HERMES_HOME:-${HOME}/.hermes}.
Tests: 415 passed, 0 failed (same as pre-fix).
Mobile responsive (Issue #21):
- Hamburger sidebar: slide-in overlay on mobile (<640px) with backdrop.
Tap hamburger in topbar to open, tap outside to close. Full session
list, project chips, all panel content accessible.
- Bottom navigation bar: 5-tab fixed bar (Chat, Tasks, Skills, Memory,
Spaces) replaces sidebar nav tabs on mobile. iOS-style layout.
Tapping a tab opens the sidebar overlay with that panel active.
- Right panel slide-over: Files button in topbar chips opens workspace
panel as a slide-over from the right on mobile/tablet.
- Touch targets: all interactive elements get min 44x44px touch areas.
Session items, approval buttons, composer buttons all sized for fingers.
- Composer positioned above bottom nav bar with proper spacing.
- Sidebar nav tabs and bottom section hidden on mobile (replaced by
bottom nav + topbar chips).
- Clicking a session auto-closes the sidebar overlay.
- Desktop layout completely unchanged — all mobile elements are
display:none by default, only shown inside @media(max-width:640px).
Docker (Issue #7):
- Dockerfile: python:3.12-slim, HERMES_WEBUI_HOST=0.0.0.0, port 8787.
- docker-compose.yml: named volume for state persistence, optional
~/.hermes mount for agent features, password env var documented.
- README: Docker quick start section with compose and manual commands.
Tests: 392 passed, 23 pre-existing failures, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>