Reverts the global assistant serif rule and removes the Calm theme that were shipped in v0.50.240 PR #1282. Pure deletion; 3252 tests passing. Override on independent review per Nathan.
When the user explicitly selects @provider:model from the picker,
_resolve_compatible_session_model() was stripping the prefix because
the hint matched the active provider (hint_matches_active=True → return bare_model, True).
This caused:
- The picker to snap back to the first duplicate entry on next render
- resolve_model_provider() to use the default provider instead of the
explicitly selected one, running the agent on the wrong backend
The hint_matches_active branch was intended for normalizing stale cross-
provider session models. But an @provider:model where the hint IS the
active provider is not stale — it is the user's deliberate selection.
Fix: return (model, False) so the full @provider:model survives to
resolve_model_provider() in config.py, which already handles it correctly.
Updates test_active_at_provider_session_model_preserved_with_hint and
adds test_issue1253_duplicate_model_id_active_provider_hint_preserved.
Closes#1253
The post-stream renderMd() in static/ui.js only handled #, ##, ### — lines starting with #### through ###### fell through and emitted as literal text after streaming finalized.
Extend the heading replacer chain to cover h4-h6, ordered longest-first, so ###### cannot be partially captured by the shorter ### rule. Add the matching .msg-body h4/h5/h6 CSS rules (and data-font-size variants) so the new tags inherit the same visual rhythm as h1-h3.
Adds 3 node-driven tests in test_renderer_js_behaviour.py pinning all six heading levels and the longest-first replacer order.
Closes#1258
All LRU cache operations (get, set, move_to_end, popitem) are already
protected by SESSION_AGENT_CACHE_LOCK. This addresses the reviewer's
concern about thread safety in multi-threaded ASGI servers.
Problem:
- GET /api/mcp/servers returned 404 error
- MCP servers management UI could not load server list
- Root cause: route was placed outside handle_get(), in unreachable code
Root Cause:
- The MCP servers GET route was incorrectly placed after handle_get() returned False (404)
- handle_get() function returns False at line ~1224, so any code after it won't execute
- The route was also in handle_post() area but without proper method checking
Solution:
- Moved GET /api/mcp/servers route inside handle_get() before the return False statement
- Removed the misplaced route from the old location (originally around line 1636)
- Also updated /api/profiles response format to include full profiles list
Testing:
- After restart: curl http://localhost:8787/api/mcp/servers returns {"servers": []}
- No more 404 errors
- WebUI can now properly load MCP servers list
The agent cache stores full AIAgent instances (each holding complete
conversation history) without size limit. Long-running servers with
many sessions can accumulate unbounded memory usage.
Changes:
- Replace dict with OrderedDict for LRU tracking
- Add SESSION_AGENT_CACHE_MAX = 50 limit
- Evict least-recently-used entries when cache exceeds limit
- Call move_to_end() on cache hits to maintain LRU order
This prevents memory exhaustion on servers with many active sessions.
- Added Brazilian Portuguese translation with 721 keys
- 100% key parity with en locale (reference)
- Follows project convention: _lang='pt', _speech='pt-BR'
- Clean insertion without modifying existing locales
- Syntax validated with node --check
AI Translation Disclosure:
Translated using NVIDIA NIM (qwen3.5-plus model) with human review by native Brazilian Portuguese speaker (Feco Linhares)
- Added Brazilian Portuguese translation with 721 keys
- 100% key parity with en locale (reference)
- Follows project convention: _lang='pt', _speech='pt-BR'
- Clean insertion without modifying existing locales
- Syntax validated with node --check
AI Translation Disclosure:
Translated using NVIDIA NIM (qwen3.5-plus model) with human review by native Brazilian Portuguese speaker (Feco Linhares)
When using custom providers with private IPs (like AxonHub on internal
networks), the SSRF protection incorrectly blocks API calls to the user's
own configured endpoint.
This fix automatically adds the model.base_url hostname to the SSRF
trusted hosts list, since it's explicitly configured by the user.
Fixes issues where /api/models and /v1/* endpoints fail silently
when using custom providers with private IPs or IPv6 addresses.
Two artifacts from a contributor's local graphify (code-graph) tooling
slipped into PR #1233 (workspace drag-to-reorder):
.graphify_cached.json (3.5MB)
.graphify_uncached.txt (refs /home/fr33m1nd/hermes-webui-src/...)
Neither belongs in source control: the .json is an autogenerated cache
of node IDs for a graph visualisation tool, and the .txt is a
file-discovery index pointing at the contributor's local workspace
(/home/fr33m1nd/hermes-webui-src/) — paths that aren't valid for any
other developer.
The repo already ignores graphify-out/ but these two top-level dotfiles
weren't covered. Add explicit ignore entries and remove the tracked
copies.
No code change. CI remains green on 3.11/3.12/3.13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
B1: fix stored XSS in MCP delete button — replace inline onclick with
data-mcp-name attribute + event delegation (panels.js)
B2: fix zip/tar-slip via startswith prefix collision — use
is_relative_to(); track actual extracted bytes instead of trusting
member.file_size (upload.py)
B3: add NVIDIA NIM endpoint to _OPENAI_COMPAT_ENDPOINTS and
_SUPPORTED_PROVIDER_SETUPS so provider is reachable (routes.py,
onboarding.py)
H1: add terminalResizeHandle element to index.html and return it from
_terminalEls() so resize-by-drag works (index.html, terminal.js)
H2: fix dead get_terminal() branch — return None for dead terminals
instead of always returning term (terminal.py)
H3: replace os.environ.copy() with a safe allowlist in PTY shell env
so API keys are not exposed inside the terminal (terminal.py)
H5: make model dedup deterministic — sort groups by provider_id
alphabetically before first-occurrence assignment (config.py)
H7: add pid regex validation before OAuth probe; constrain key_source
to a closed set of safe values (providers.py)
M8: add double-run guard for cron run-now — reject if job is already
tracked as running (routes.py)
Addresses reviewer feedback on #524 — the compress affordance was only
reachable via hover (desktop). Mobile users can now tap the context ring
button to toggle the tooltip and access the compress button.
- CSS: add .ctx-tooltip-active class with opacity + pointer-events
- JS: tap-to-toggle handler on ctxIndicator with outside-click dismiss
- aria-hidden toggled correctly for accessibility
Ref: #1223 review comment