get_available_models() references 'logger' in the except block of
the custom endpoint fetch (added in PR #18), but 'logger' is never
imported or defined in api/config.py. When the custom endpoint is
unreachable (the normal case -- most users don't have a local LLM),
the except handler raises NameError: name 'logger' is not defined,
which propagates as a 500 on every GET /api/models request.
This broke 7 test_sprint11 tests and caused the model dropdown to
fail for all users regardless of whether they have a custom endpoint.
Fix: replace logger.debug() with a silent pass -- the exception is
expected when no local LLM is configured and needs no logging.
Tests: 237 passed, 0 failed.
Five fixes to the Sprint 15 Move to Project picker:
1. CRITICAL: Picker was invisible (overflow:hidden clipping)
Appended to document.body + positioned with fixed/getBoundingClientRect
instead of inside .session-item (overflow:hidden). Flips above button
when near bottom of viewport.
2. CRITICAL: Picker stretched full screen width
position:fixed removed the containing block width constraint. Added
max-width:220px; width:max-content to .project-picker.
3. UX: No way to create a project from the picker
Added '+ New project': creates project and moves session in one click.
4. UX: Feature was undiscoverable
Folder button shows persistently (blue, 60% opacity) when session
has a project.
5. Minor: Event listener leak
removeEventListener was missing from picker item onclick handlers.
Tests: 237 passed (7 pre-existing failures from unrelated logger bug).
Adds the newly available GLM-5.1 model to the hardcoded Z.AI provider
model list so it appears in the model dropdown. Fixes#17.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Revert routes.py and streaming.py to master: resolve_model_provider()
already handles provider routing and base_url passthrough for all models.
- Fix indentation error in config.py (2-space indent on comment line).
- Fix auto_detected_models scope: initialize before try block.
- Remove unused urllib.parse import.
- Simplify unknown-provider model group logic.
- Remove verbose comments and redundant variable assignments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Picker dropdown: append to document.body with fixed positioning instead
of inside the session-item (which has overflow:hidden). Flips above
when near bottom of viewport.
- Add "+ New project" item at bottom of picker so users can create a
project and assign in one flow.
- Folder button stays visible (blue, 60% opacity) when session belongs
to a project, instead of only appearing on hover.
- Clean up document click listener in all picker item onclick handlers
to prevent stale listener accumulation.
Tests: 214 passed, 23 pre-existing failures, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- resolve_model_provider: fix regression where OpenRouter model IDs like
openai/gpt-5.4-mini had their prefix stripped, causing AIAgent to look
for OPENAI_API_KEY (direct API) instead of routing through OpenRouter.
All chats returned Connection lost for OpenRouter users. Fix: only strip
prefix and use direct-API when config.provider explicitly matches that
provider; pass full provider/model string through for openrouter.
- Project name: cap at 128 chars, reject empty after strip on create/rename
- Project color: validate ^#[0-9a-fA-F]{3,8}$ to prevent CSS injection
via dot.style.background in sessions.js
- Remove 2 redundant sys.path.insert() calls in cron handlers
Tests: 214 passed, 23 pre-existing failures, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Session projects: named groups for organizing sessions. Project filter
bar with chips between search and session list. Create/rename/delete
projects, assign sessions via folder icon dropdown. Stored in
projects.json, project_id on Session model. 5 new API endpoints.
Code block copy button: every code block gets a Copy button in the
language header (or top-right for plain blocks). Clipboard API with
"Copied!" feedback.
Tool card expand/collapse: messages with 2+ tool cards get an
"Expand all / Collapse all" toggle above the card group.
13 new tests (237 total), all passing. No regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Restore resolve_model_provider() in _handle_chat_sync -- removed
multi-provider model routing, breaking cross-provider selection.
2. Restore new URL(path, location.origin) + credentials:include on
fetch calls -- reverted reverse-proxy auth fix from v0.16.1.
3. Revert cron import refactor (_cron_module, _real_hermes_home_env)
back to original from cron.jobs import pattern.
Tests: 201 passed, 23 pre-existing failures, 0 new regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Path traversal in _serve_static() [CRITICAL]
Sandbox resolved path to static/ directory using relative_to().
GET /static/../../../../etc/passwd now returns 404.
2. Skill category path traversal [HIGH]
Validate category param in skill save: reject values with '/' or '..'.
3. Gate /api/approval/inject_test to loopback only [HIGH]
Endpoint now returns 404 for any non-127.0.0.1 client,
preserving test functionality while blocking remote access.
4. Escape captured groups in renderMd() [HIGH]
All inline markdown regexes (bold, italic, headings, blockquote,
list items, table cells/headers, link labels) now run captured
text through esc() before inserting into innerHTML, preventing
XSS via AI-generated content.
5. SRI hashes for CDN resources + pin Mermaid version [MEDIUM]
Added integrity= + crossorigin= to all three PrismJS CDN tags.
Pinned Mermaid from floating @10 to @10.9.3 with SRI hash.
Tests: 224 passed, 0 failed.
Resolved path was not checked against the static/ directory, allowing
GET /static/../../../../etc/passwd to serve arbitrary files.
Fix: resolve the path and call relative_to(static_root) before serving.
Returns 404 for any path that escapes the static/ directory.
fix(css): add !important to three dead mobile overrides in @media(640px)
Three @media(max-width:640px) rules added by the mobile responsive PR
were silently overridden by later bare rules in the same stylesheet:
.composer-wrap padding (overridden by line 347)
.suggestion-grid max-width (overridden by line 364)
.tool-card margin-left (overridden by line 460)
Fix: add !important to these three declarations so the mobile overrides
actually fire on narrow screens.
Tests: 224 passed, 0 failed.
- Update _PROVIDER_MODELS['minimax'] from stale ABAB 6.5 models to
current MiniMax-M2.7/M2.5/M2.1 lineup (matching hermes-agent upstream)
- Update _PROVIDER_MODELS['zai'] from GLM-4 to current GLM-5/4.7/4.5
lineup (matching hermes-agent upstream)
- Extend resolve_model_provider() to also return base_url from config.yaml,
so providers with custom endpoints (MiniMax, Z.AI) are routed correctly
- Pass base_url to AIAgent in both streaming and sync chat paths
Fixes#6
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add entry for mobile responsive layout, reverse proxy auth support,
and model provider routing fixes contributed by @deboste.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace duplicated inline provider resolution in routes.py and streaming.py
with a shared resolve_model_provider() helper in config.py.
Improvements over original:
- If model ID has a prefix matching any known direct-API provider
(not just the config provider), strip it and route correctly.
This handles edge cases like localStorage restoring a model from
a different provider group.
- Single source of truth for the resolution logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The original PR correctly used new URL(path, location.origin) to strip
credentials from fetch/EventSource URLs, and added credentials:'include'
to all fetch() calls. However, EventSource requires { withCredentials: true }
as a second constructor argument for cookies/auth headers to be forwarded.
Without this, SSE streaming breaks behind a reverse proxy with basic auth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use 100dvh with 100vh fallback to fix composer being cut off on
mobile browsers where the address bar affects viewport height
- Add comprehensive @media(max-width:640px) rules: topbar wrapping,
compact messages, full-width msg-body, smaller chips and buttons,
responsive composer, approval cards, tool cards, settings modal
- Use font-size:16px on textarea to prevent iOS/Android auto-zoom
on input focus (browsers zoom when font-size < 16px)
- Add .topbar-left class on title wrapper for responsive stacking
When the model dropdown sends a prefixed ID like "anthropic/claude-xxx",
AIAgent interprets the provider/model format as an OpenRouter path and
routes through OpenRouter instead of the direct Anthropic API.
Fix: read the configured provider from config.yaml model section. If
the model ID starts with the configured provider name followed by "/",
strip that prefix and pass the provider explicitly to AIAgent. This
ensures direct API providers (Anthropic, OpenAI, etc.) are used when
configured, regardless of the model ID format from the dropdown.
When Hermes WebUI runs behind a reverse proxy with HTTP basic auth
(e.g. Caddy basic_auth), browsers embed credentials in the page URL.
The Fetch API and EventSource reject requests constructed from URLs
that include credentials (per Fetch spec, all modern browsers).
Fix: construct all fetch() and EventSource URLs via
new URL(path, location.origin) which strips credentials from the
base URL. Add credentials:"include" to ensure auth headers are
forwarded on each request.