Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
"""
|
|
|
|
|
Hermes Web UI -- Route handlers for GET and POST endpoints.
|
|
|
|
|
Extracted from server.py (Sprint 11) so server.py is a thin shell.
|
|
|
|
|
"""
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-06 15:06:16 +00:00
|
|
|
import html as _html
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
import json
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
import logging
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
import os
|
|
|
|
|
import queue
|
|
|
|
|
import sys
|
|
|
|
|
import threading
|
|
|
|
|
import time
|
|
|
|
|
import uuid
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from urllib.parse import parse_qs
|
|
|
|
|
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
from api.config import (
|
2026-04-12 07:11:41 +00:00
|
|
|
STATE_DIR,
|
|
|
|
|
SESSION_DIR,
|
|
|
|
|
DEFAULT_WORKSPACE,
|
|
|
|
|
DEFAULT_MODEL,
|
|
|
|
|
SESSIONS,
|
|
|
|
|
SESSIONS_MAX,
|
|
|
|
|
LOCK,
|
|
|
|
|
STREAMS,
|
|
|
|
|
STREAMS_LOCK,
|
|
|
|
|
CANCEL_FLAGS,
|
|
|
|
|
SERVER_START_TIME,
|
|
|
|
|
CLI_TOOLSETS,
|
|
|
|
|
_INDEX_HTML_PATH,
|
|
|
|
|
get_available_models,
|
|
|
|
|
IMAGE_EXTS,
|
|
|
|
|
MD_EXTS,
|
|
|
|
|
MIME_MAP,
|
|
|
|
|
MAX_FILE_BYTES,
|
|
|
|
|
MAX_UPLOAD_BYTES,
|
|
|
|
|
CHAT_LOCK,
|
|
|
|
|
load_settings,
|
|
|
|
|
save_settings,
|
|
|
|
|
)
|
|
|
|
|
from api.helpers import (
|
|
|
|
|
require,
|
|
|
|
|
bad,
|
|
|
|
|
safe_resolve,
|
|
|
|
|
j,
|
|
|
|
|
t,
|
|
|
|
|
read_body,
|
|
|
|
|
_security_headers,
|
|
|
|
|
_sanitize_error,
|
|
|
|
|
redact_session_data,
|
|
|
|
|
_redact_text,
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
)
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
|
|
|
|
|
# ── CSRF: validate Origin/Referer on POST ────────────────────────────────────
|
|
|
|
|
import re as _re
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
def _check_csrf(handler) -> bool:
|
|
|
|
|
"""Reject cross-origin POST requests. Returns True if OK."""
|
2026-04-12 07:11:41 +00:00
|
|
|
origin = handler.headers.get("Origin", "")
|
|
|
|
|
referer = handler.headers.get("Referer", "")
|
|
|
|
|
host = handler.headers.get("Host", "")
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
if not origin and not referer:
|
|
|
|
|
return True # non-browser clients (curl, agent) have no Origin
|
|
|
|
|
target = origin or referer
|
2026-04-10 08:24:18 +00:00
|
|
|
# Extract host:port from origin/referer
|
2026-04-12 07:11:41 +00:00
|
|
|
m = _re.match(r"^https?://([^/]+)", target)
|
2026-04-10 08:24:18 +00:00
|
|
|
if not m:
|
|
|
|
|
return False
|
|
|
|
|
origin_host = m.group(1)
|
|
|
|
|
# Allow same-origin: check Host, X-Forwarded-Host (reverse proxy), and
|
|
|
|
|
# X-Real-Host against the origin. Reverse proxies (Caddy, nginx) set
|
|
|
|
|
# X-Forwarded-Host to the client's original Host header.
|
2026-04-12 07:11:41 +00:00
|
|
|
allowed_hosts = {
|
|
|
|
|
h.strip()
|
|
|
|
|
for h in [
|
|
|
|
|
host,
|
|
|
|
|
handler.headers.get("X-Forwarded-Host", ""),
|
|
|
|
|
handler.headers.get("X-Real-Host", ""),
|
|
|
|
|
]
|
|
|
|
|
if h.strip()
|
|
|
|
|
}
|
2026-04-10 08:24:18 +00:00
|
|
|
if origin_host in allowed_hosts:
|
|
|
|
|
return True
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
return False
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
from api.models import (
|
2026-04-12 07:11:41 +00:00
|
|
|
Session,
|
|
|
|
|
get_session,
|
|
|
|
|
new_session,
|
|
|
|
|
all_sessions,
|
|
|
|
|
title_from,
|
|
|
|
|
_write_session_index,
|
|
|
|
|
SESSION_INDEX_FILE,
|
|
|
|
|
load_projects,
|
|
|
|
|
save_projects,
|
|
|
|
|
import_cli_session,
|
|
|
|
|
get_cli_sessions,
|
|
|
|
|
get_cli_session_messages,
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
)
|
|
|
|
|
from api.workspace import (
|
2026-04-12 07:11:41 +00:00
|
|
|
load_workspaces,
|
|
|
|
|
save_workspaces,
|
|
|
|
|
get_last_workspace,
|
|
|
|
|
set_last_workspace,
|
|
|
|
|
list_dir,
|
|
|
|
|
read_file_content,
|
|
|
|
|
safe_resolve_ws,
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
)
|
|
|
|
|
from api.upload import handle_upload
|
|
|
|
|
from api.streaming import _sse, _run_agent_streaming, cancel_stream
|
2026-04-12 07:11:41 +00:00
|
|
|
from api.onboarding import (
|
|
|
|
|
apply_onboarding_setup,
|
|
|
|
|
get_onboarding_status,
|
|
|
|
|
complete_onboarding,
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
# Approval system (optional -- graceful fallback if agent not available)
|
|
|
|
|
try:
|
|
|
|
|
from tools.approval import (
|
v0.44.0: approval fix, login CSP, update diagnostics, Lucide icons
* fix: approval pending check broken by stale has_pending import (#228)
api/routes.py imported has_pending/pop_pending from tools.approval, but the
agent module renamed has_pending to has_blocking_approval (checks gateway
queue, not _pending dict) and removed pop_pending. The import fell through
to fallback lambdas that always returned False, making GET /api/approval/pending
always return {pending:null} even after a successful inject_test.
Fix: check _pending directly under _lock — same dict submit_pending writes to.
Stale imports removed.
Before: 554 pass, 1 fail | After: 555 pass, 0 fail
* fix: move login JS into external file, remove inline handlers (#226)
Login page used inline onsubmit/onkeydown handlers and an inline <script>
block — all blocked by strict script-src CSP, causing silent login failure.
Fix: extract doLogin() and Enter key listener into static/login.js (served
from /static/, already a public path). Form uses id='login-form' and
data-* attributes for i18n strings instead of injected JS literals.
Also guards res.json() parse with try/catch so non-JSON error bodies
(e.g. HTTP 500) show the password-error fallback instead of 'Connection failed'.
Fixes #222.
* fix: improve update error messages when pull fails (#227)
_apply_update_inner() ran git pull --ff-only and returned only raw stderr
on failure, making all failure modes indistinguishable.
Fix: explicit git fetch before pull; if fetch fails, returns human-readable
network error. Diverged history and missing upstream tracking branch each
get distinct messages with exact recovery commands. Generic fallback
truncates to 300 chars and shows sentinel when git produces no output.
Also adds tests/test_update_checker.py with 13 tests covering all 4 new
diagnostic code paths (0 tests existed before).
Fixes #223.
* fix: stabilize 30s terminal approval prompt visibility (#225)
Adds minimum 30-second visibility guard for the approval card using
_approvalVisibleSince, _approvalHideTimer, and a signature fingerprint
to deduplicate repeated poll ticks.
Fix: respondApproval() and all stream-end paths (done/cancel/apperror/
error/start-error) now call hideApprovalCard(true) so the card hides
immediately when the user responds or the session ends. The 30s guard
only applies to mid-session poll ticks where the approval is still live
but briefly absent.
Adds 11 structural tests covering the new timer variables, force
parameter, force-on-respond, force-on-stream-end, and poll-loop
no-force behavior.
* feat: replace emoji icons with self-hosted Lucide SVG icons (#221)
Replaces all sidebar/button emoji icons with SVG paths from Lucide bundled
in static/icons.js (no CDN dependency). Adds li(name) function returning
inline SVG geometry from a hardcoded whitelist — unknown keys return '' so
dynamic server-supplied names never inject arbitrary SVG.
Changes:
- static/icons.js: new file with 21 icon paths + li() renderer
- static/index.html: all nav/action buttons now use li() icons
- static/ui.js: toolIcon(), fileIcon() use li() for tool/file icons
- static/messages.js: cancelStream button uses SVG square stop icon
- .gitignore: adds node_modules/ entry
Verified: all 35 onclick= functions exist in JS, all 21 li() calls
reference defined icons, applyBotName() selectors intact, version
label present, no removed IDs referenced by JS.
* docs: v0.44.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-10 17:02:28 +00:00
|
|
|
submit_pending,
|
2026-04-12 07:11:41 +00:00
|
|
|
approve_session,
|
|
|
|
|
approve_permanent,
|
|
|
|
|
save_permanent_allowlist,
|
|
|
|
|
is_approved,
|
|
|
|
|
_pending,
|
|
|
|
|
_lock,
|
|
|
|
|
_permanent_approved,
|
fix: surface approval prompt in UI instead of getting stuck in Thinking (#187)
* fix: surface approval prompt in UI instead of getting stuck in Thinking
When a dangerous command was detected during streaming, the approval system
would call submit_pending() but no SSE 'approval' event would be emitted to
the frontend. The agent thread either blocked indefinitely (gateway path) or
returned an approval_required status the UI never saw (EXEC_ASK path). Either
way the chat UI stayed stuck in 'Thinking...' with no prompt shown.
Root cause: streaming.py used HERMES_EXEC_ASK=1 but never registered a
register_gateway_notify() callback. Without it, check_all_command_guards()
fell back to the legacy polling path (submit_pending only), which relies on
on_tool() polling -- but on_tool() fires *before* the tool runs, so by the
time the terminal tool detected the dangerous command and called submit_pending,
the approval event had already missed its window.
Fix (streaming.py):
- Register a gateway-style notify_cb via register_gateway_notify() before the
agent runs. The callback calls put('approval', ...) to emit the SSE event
the moment a dangerous command is detected, regardless of on_tool() timing.
- Unregister via unregister_gateway_notify() in the finally block to unblock
any threads still waiting if the stream ends or is cancelled mid-approval.
- Keep the on_tool() fallback poll for older approval module versions.
Fix (routes.py):
- Import and call resolve_gateway_approval() in _handle_approval_respond().
This unblocks the agent thread parked in entry.event.wait() when the user
clicks Allow or Deny in the UI. Without this call the thread would block
until the 5-minute gateway timeout.
Tests (tests/test_approval_unblock.py):
- 16 new tests covering: resolve_gateway_approval() event signalling, deny/
session/once choices, resolve_all, notify_cb registration/firing/cleanup,
unregister signals blocked entries, full end-to-end streaming simulation,
module symbol exports, and HTTP endpoint regressions.
515 tests pass (499 existing + 16 new).
* feat: full approval UI — i18n buttons, keyboard shortcut, loading state, scoping fix
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-09 03:16:22 +00:00
|
|
|
resolve_gateway_approval,
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
)
|
|
|
|
|
except ImportError:
|
|
|
|
|
submit_pending = lambda *a, **k: None
|
|
|
|
|
approve_session = lambda *a, **k: None
|
|
|
|
|
approve_permanent = lambda *a, **k: None
|
|
|
|
|
save_permanent_allowlist = lambda *a, **k: None
|
|
|
|
|
is_approved = lambda *a, **k: True
|
fix: surface approval prompt in UI instead of getting stuck in Thinking (#187)
* fix: surface approval prompt in UI instead of getting stuck in Thinking
When a dangerous command was detected during streaming, the approval system
would call submit_pending() but no SSE 'approval' event would be emitted to
the frontend. The agent thread either blocked indefinitely (gateway path) or
returned an approval_required status the UI never saw (EXEC_ASK path). Either
way the chat UI stayed stuck in 'Thinking...' with no prompt shown.
Root cause: streaming.py used HERMES_EXEC_ASK=1 but never registered a
register_gateway_notify() callback. Without it, check_all_command_guards()
fell back to the legacy polling path (submit_pending only), which relies on
on_tool() polling -- but on_tool() fires *before* the tool runs, so by the
time the terminal tool detected the dangerous command and called submit_pending,
the approval event had already missed its window.
Fix (streaming.py):
- Register a gateway-style notify_cb via register_gateway_notify() before the
agent runs. The callback calls put('approval', ...) to emit the SSE event
the moment a dangerous command is detected, regardless of on_tool() timing.
- Unregister via unregister_gateway_notify() in the finally block to unblock
any threads still waiting if the stream ends or is cancelled mid-approval.
- Keep the on_tool() fallback poll for older approval module versions.
Fix (routes.py):
- Import and call resolve_gateway_approval() in _handle_approval_respond().
This unblocks the agent thread parked in entry.event.wait() when the user
clicks Allow or Deny in the UI. Without this call the thread would block
until the 5-minute gateway timeout.
Tests (tests/test_approval_unblock.py):
- 16 new tests covering: resolve_gateway_approval() event signalling, deny/
session/once choices, resolve_all, notify_cb registration/firing/cleanup,
unregister signals blocked entries, full end-to-end streaming simulation,
module symbol exports, and HTTP endpoint regressions.
515 tests pass (499 existing + 16 new).
* feat: full approval UI — i18n buttons, keyboard shortcut, loading state, scoping fix
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-09 03:16:22 +00:00
|
|
|
resolve_gateway_approval = lambda *a, **k: 0
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
_pending = {}
|
|
|
|
|
_lock = threading.Lock()
|
|
|
|
|
_permanent_approved = set()
|
|
|
|
|
|
|
|
|
|
|
2026-04-08 14:55:03 +00:00
|
|
|
# ── Login page locale strings ─────────────────────────────────────────────────
|
|
|
|
|
# Add entries here to support more languages on the login page.
|
|
|
|
|
# The key must match the 'language' setting value (from static/i18n.js LOCALES).
|
|
|
|
|
_LOGIN_LOCALE = {
|
2026-04-12 07:11:41 +00:00
|
|
|
"en": {
|
|
|
|
|
"lang": "en",
|
|
|
|
|
"title": "Sign in",
|
|
|
|
|
"subtitle": "Enter your password to continue",
|
|
|
|
|
"placeholder": "Password",
|
|
|
|
|
"btn": "Sign in",
|
|
|
|
|
"invalid_pw": "Invalid password",
|
|
|
|
|
"conn_failed": "Connection failed",
|
2026-04-08 14:55:03 +00:00
|
|
|
},
|
2026-04-12 07:11:41 +00:00
|
|
|
"zh": {
|
|
|
|
|
"lang": "zh-CN",
|
|
|
|
|
"title": "\u767b\u5f55",
|
|
|
|
|
"subtitle": "\u8f93\u5165\u5bc6\u7801\u7ee7\u7eed\u4f7f\u7528",
|
|
|
|
|
"placeholder": "\u5bc6\u7801",
|
|
|
|
|
"btn": "\u767b\u5f55",
|
|
|
|
|
"invalid_pw": "\u5bc6\u7801\u9519\u8bef",
|
|
|
|
|
"conn_failed": "\u8fde\u63a5\u5931\u8d25",
|
2026-04-08 14:55:03 +00:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
# ── Login page (self-contained, no external deps) ────────────────────────────
|
2026-04-12 07:11:41 +00:00
|
|
|
_LOGIN_PAGE_HTML = """<!doctype html>
|
2026-04-08 14:55:03 +00:00
|
|
|
<html lang="{{LANG}}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
|
|
|
<title>{{BOT_NAME}} — {{LOGIN_TITLE}}</title>
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
<style>
|
|
|
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
|
|
|
body{background:#1a1a2e;color:#e8e8f0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
|
|
|
|
|
height:100vh;display:flex;align-items:center;justify-content:center}
|
|
|
|
|
.card{background:#16213e;border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:36px 32px;
|
|
|
|
|
width:320px;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.3)}
|
|
|
|
|
.logo{width:48px;height:48px;border-radius:12px;background:linear-gradient(145deg,#e8a030,#e94560);
|
|
|
|
|
display:flex;align-items:center;justify-content:center;font-weight:800;font-size:20px;color:#fff;
|
|
|
|
|
margin:0 auto 12px;box-shadow:0 2px 12px rgba(233,69,96,.3)}
|
|
|
|
|
h1{font-size:18px;font-weight:600;margin-bottom:4px}
|
|
|
|
|
.sub{font-size:12px;color:#8888aa;margin-bottom:24px}
|
|
|
|
|
input{width:100%;padding:10px 14px;border-radius:10px;border:1px solid rgba(255,255,255,.1);
|
|
|
|
|
background:rgba(255,255,255,.04);color:#e8e8f0;font-size:14px;outline:none;margin-bottom:14px;
|
|
|
|
|
transition:border-color .15s}
|
|
|
|
|
input:focus{border-color:rgba(124,185,255,.5);box-shadow:0 0 0 3px rgba(124,185,255,.1)}
|
|
|
|
|
button{width:100%;padding:10px;border-radius:10px;border:none;background:rgba(124,185,255,.15);
|
|
|
|
|
border:1px solid rgba(124,185,255,.3);color:#7cb9ff;font-size:14px;font-weight:600;cursor:pointer;
|
|
|
|
|
transition:all .15s}
|
|
|
|
|
button:hover{background:rgba(124,185,255,.25)}
|
|
|
|
|
.err{color:#e94560;font-size:12px;margin-top:10px;display:none}
|
|
|
|
|
</style></head><body>
|
|
|
|
|
<div class="card">
|
2026-04-06 14:47:00 +00:00
|
|
|
<div class="logo">{{BOT_NAME_INITIAL}}</div>
|
|
|
|
|
<h1>{{BOT_NAME}}</h1>
|
2026-04-08 14:55:03 +00:00
|
|
|
<p class="sub">{{LOGIN_SUBTITLE}}</p>
|
v0.44.0: approval fix, login CSP, update diagnostics, Lucide icons
* fix: approval pending check broken by stale has_pending import (#228)
api/routes.py imported has_pending/pop_pending from tools.approval, but the
agent module renamed has_pending to has_blocking_approval (checks gateway
queue, not _pending dict) and removed pop_pending. The import fell through
to fallback lambdas that always returned False, making GET /api/approval/pending
always return {pending:null} even after a successful inject_test.
Fix: check _pending directly under _lock — same dict submit_pending writes to.
Stale imports removed.
Before: 554 pass, 1 fail | After: 555 pass, 0 fail
* fix: move login JS into external file, remove inline handlers (#226)
Login page used inline onsubmit/onkeydown handlers and an inline <script>
block — all blocked by strict script-src CSP, causing silent login failure.
Fix: extract doLogin() and Enter key listener into static/login.js (served
from /static/, already a public path). Form uses id='login-form' and
data-* attributes for i18n strings instead of injected JS literals.
Also guards res.json() parse with try/catch so non-JSON error bodies
(e.g. HTTP 500) show the password-error fallback instead of 'Connection failed'.
Fixes #222.
* fix: improve update error messages when pull fails (#227)
_apply_update_inner() ran git pull --ff-only and returned only raw stderr
on failure, making all failure modes indistinguishable.
Fix: explicit git fetch before pull; if fetch fails, returns human-readable
network error. Diverged history and missing upstream tracking branch each
get distinct messages with exact recovery commands. Generic fallback
truncates to 300 chars and shows sentinel when git produces no output.
Also adds tests/test_update_checker.py with 13 tests covering all 4 new
diagnostic code paths (0 tests existed before).
Fixes #223.
* fix: stabilize 30s terminal approval prompt visibility (#225)
Adds minimum 30-second visibility guard for the approval card using
_approvalVisibleSince, _approvalHideTimer, and a signature fingerprint
to deduplicate repeated poll ticks.
Fix: respondApproval() and all stream-end paths (done/cancel/apperror/
error/start-error) now call hideApprovalCard(true) so the card hides
immediately when the user responds or the session ends. The 30s guard
only applies to mid-session poll ticks where the approval is still live
but briefly absent.
Adds 11 structural tests covering the new timer variables, force
parameter, force-on-respond, force-on-stream-end, and poll-loop
no-force behavior.
* feat: replace emoji icons with self-hosted Lucide SVG icons (#221)
Replaces all sidebar/button emoji icons with SVG paths from Lucide bundled
in static/icons.js (no CDN dependency). Adds li(name) function returning
inline SVG geometry from a hardcoded whitelist — unknown keys return '' so
dynamic server-supplied names never inject arbitrary SVG.
Changes:
- static/icons.js: new file with 21 icon paths + li() renderer
- static/index.html: all nav/action buttons now use li() icons
- static/ui.js: toolIcon(), fileIcon() use li() for tool/file icons
- static/messages.js: cancelStream button uses SVG square stop icon
- .gitignore: adds node_modules/ entry
Verified: all 35 onclick= functions exist in JS, all 21 li() calls
reference defined icons, applyBotName() selectors intact, version
label present, no removed IDs referenced by JS.
* docs: v0.44.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-10 17:02:28 +00:00
|
|
|
<form id="login-form" data-invalid-pw="{{LOGIN_INVALID_PW}}" data-conn-failed="{{LOGIN_CONN_FAILED}}">
|
|
|
|
|
<input type="password" id="pw" placeholder="{{LOGIN_PLACEHOLDER}}" autofocus>
|
2026-04-08 14:55:03 +00:00
|
|
|
<button type="submit">{{LOGIN_BTN}}</button>
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
</form>
|
|
|
|
|
<div class="err" id="err"></div>
|
|
|
|
|
</div>
|
v0.44.0: approval fix, login CSP, update diagnostics, Lucide icons
* fix: approval pending check broken by stale has_pending import (#228)
api/routes.py imported has_pending/pop_pending from tools.approval, but the
agent module renamed has_pending to has_blocking_approval (checks gateway
queue, not _pending dict) and removed pop_pending. The import fell through
to fallback lambdas that always returned False, making GET /api/approval/pending
always return {pending:null} even after a successful inject_test.
Fix: check _pending directly under _lock — same dict submit_pending writes to.
Stale imports removed.
Before: 554 pass, 1 fail | After: 555 pass, 0 fail
* fix: move login JS into external file, remove inline handlers (#226)
Login page used inline onsubmit/onkeydown handlers and an inline <script>
block — all blocked by strict script-src CSP, causing silent login failure.
Fix: extract doLogin() and Enter key listener into static/login.js (served
from /static/, already a public path). Form uses id='login-form' and
data-* attributes for i18n strings instead of injected JS literals.
Also guards res.json() parse with try/catch so non-JSON error bodies
(e.g. HTTP 500) show the password-error fallback instead of 'Connection failed'.
Fixes #222.
* fix: improve update error messages when pull fails (#227)
_apply_update_inner() ran git pull --ff-only and returned only raw stderr
on failure, making all failure modes indistinguishable.
Fix: explicit git fetch before pull; if fetch fails, returns human-readable
network error. Diverged history and missing upstream tracking branch each
get distinct messages with exact recovery commands. Generic fallback
truncates to 300 chars and shows sentinel when git produces no output.
Also adds tests/test_update_checker.py with 13 tests covering all 4 new
diagnostic code paths (0 tests existed before).
Fixes #223.
* fix: stabilize 30s terminal approval prompt visibility (#225)
Adds minimum 30-second visibility guard for the approval card using
_approvalVisibleSince, _approvalHideTimer, and a signature fingerprint
to deduplicate repeated poll ticks.
Fix: respondApproval() and all stream-end paths (done/cancel/apperror/
error/start-error) now call hideApprovalCard(true) so the card hides
immediately when the user responds or the session ends. The 30s guard
only applies to mid-session poll ticks where the approval is still live
but briefly absent.
Adds 11 structural tests covering the new timer variables, force
parameter, force-on-respond, force-on-stream-end, and poll-loop
no-force behavior.
* feat: replace emoji icons with self-hosted Lucide SVG icons (#221)
Replaces all sidebar/button emoji icons with SVG paths from Lucide bundled
in static/icons.js (no CDN dependency). Adds li(name) function returning
inline SVG geometry from a hardcoded whitelist — unknown keys return '' so
dynamic server-supplied names never inject arbitrary SVG.
Changes:
- static/icons.js: new file with 21 icon paths + li() renderer
- static/index.html: all nav/action buttons now use li() icons
- static/ui.js: toolIcon(), fileIcon() use li() for tool/file icons
- static/messages.js: cancelStream button uses SVG square stop icon
- .gitignore: adds node_modules/ entry
Verified: all 35 onclick= functions exist in JS, all 21 li() calls
reference defined icons, applyBotName() selectors intact, version
label present, no removed IDs referenced by JS.
* docs: v0.44.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-10 17:02:28 +00:00
|
|
|
<script src="/static/login.js"></script>
|
2026-04-12 07:11:41 +00:00
|
|
|
</body></html>"""
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
# ── GET routes ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-05 06:30:20 +00:00
|
|
|
def handle_get(handler, parsed) -> bool:
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
"""Handle all GET routes. Returns True if handled, False for 404."""
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path in ("/", "/index.html"):
|
|
|
|
|
return t(
|
|
|
|
|
handler,
|
|
|
|
|
_INDEX_HTML_PATH.read_text(encoding="utf-8"),
|
|
|
|
|
content_type="text/html; charset=utf-8",
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/login":
|
2026-04-08 14:55:03 +00:00
|
|
|
_settings = load_settings()
|
2026-04-12 07:11:41 +00:00
|
|
|
_bn = _html.escape(_settings.get("bot_name") or "Hermes")
|
|
|
|
|
_lang = _settings.get("language", "en")
|
|
|
|
|
_login_strings = _LOGIN_LOCALE.get(_lang, _LOGIN_LOCALE["en"])
|
2026-04-08 14:55:03 +00:00
|
|
|
_page = (
|
2026-04-12 07:11:41 +00:00
|
|
|
_LOGIN_PAGE_HTML.replace("{{BOT_NAME}}", _bn)
|
|
|
|
|
.replace("{{BOT_NAME_INITIAL}}", _bn[0].upper())
|
|
|
|
|
.replace("{{LANG}}", _html.escape(_login_strings["lang"]))
|
|
|
|
|
.replace("{{LOGIN_TITLE}}", _html.escape(_login_strings["title"]))
|
|
|
|
|
.replace("{{LOGIN_SUBTITLE}}", _html.escape(_login_strings["subtitle"]))
|
|
|
|
|
.replace(
|
|
|
|
|
"{{LOGIN_PLACEHOLDER}}", _html.escape(_login_strings["placeholder"])
|
|
|
|
|
)
|
|
|
|
|
.replace("{{LOGIN_BTN}}", _html.escape(_login_strings["btn"]))
|
|
|
|
|
.replace("{{LOGIN_INVALID_PW}}", _html.escape(_login_strings["invalid_pw"]))
|
|
|
|
|
.replace(
|
|
|
|
|
"{{LOGIN_CONN_FAILED}}", _html.escape(_login_strings["conn_failed"])
|
|
|
|
|
)
|
2026-04-08 14:55:03 +00:00
|
|
|
)
|
2026-04-12 07:11:41 +00:00
|
|
|
return t(handler, _page, content_type="text/html; charset=utf-8")
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/auth/status":
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
from api.auth import is_auth_enabled, parse_cookie, verify_session
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
logged_in = False
|
|
|
|
|
if is_auth_enabled():
|
|
|
|
|
cv = parse_cookie(handler)
|
|
|
|
|
logged_in = bool(cv and verify_session(cv))
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"auth_enabled": is_auth_enabled(), "logged_in": logged_in})
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/favicon.ico":
|
|
|
|
|
handler.send_response(204)
|
|
|
|
|
handler.end_headers()
|
|
|
|
|
return True
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/health":
|
|
|
|
|
with STREAMS_LOCK:
|
|
|
|
|
n_streams = len(STREAMS)
|
|
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{
|
|
|
|
|
"status": "ok",
|
|
|
|
|
"sessions": len(SESSIONS),
|
|
|
|
|
"active_streams": n_streams,
|
|
|
|
|
"uptime_seconds": round(time.time() - SERVER_START_TIME, 1),
|
|
|
|
|
},
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/models":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return j(handler, get_available_models())
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/settings":
|
2026-04-03 13:05:41 +00:00
|
|
|
settings = load_settings()
|
|
|
|
|
# Never expose the stored password hash to clients
|
2026-04-12 07:11:41 +00:00
|
|
|
settings.pop("password_hash", None)
|
2026-04-03 13:05:41 +00:00
|
|
|
return j(handler, settings)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/onboarding/status":
|
|
|
|
|
return j(handler, get_onboarding_status())
|
|
|
|
|
|
|
|
|
|
if parsed.path.startswith("/static/"):
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _serve_static(handler, parsed)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session":
|
|
|
|
|
sid = parse_qs(parsed.query).get("session_id", [""])[0]
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if not sid:
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"error": "session_id is required"}, status=400)
|
2026-04-04 02:54:54 +00:00
|
|
|
try:
|
|
|
|
|
s = get_session(sid)
|
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* 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>
2026-04-11 17:17:52 +00:00
|
|
|
raw = s.compact() | {
|
2026-04-12 07:11:41 +00:00
|
|
|
"messages": s.messages,
|
|
|
|
|
"tool_calls": getattr(s, "tool_calls", []),
|
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* 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>
2026-04-11 17:17:52 +00:00
|
|
|
}
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"session": redact_session_data(raw)})
|
2026-04-04 02:54:54 +00:00
|
|
|
except KeyError:
|
|
|
|
|
# Not a WebUI session -- try CLI store
|
|
|
|
|
msgs = get_cli_session_messages(sid)
|
|
|
|
|
if msgs:
|
|
|
|
|
cli_meta = None
|
|
|
|
|
for cs in get_cli_sessions():
|
2026-04-12 07:11:41 +00:00
|
|
|
if cs["session_id"] == sid:
|
2026-04-04 02:54:54 +00:00
|
|
|
cli_meta = cs
|
|
|
|
|
break
|
|
|
|
|
sess = {
|
2026-04-12 07:11:41 +00:00
|
|
|
"session_id": sid,
|
|
|
|
|
"title": (cli_meta or {}).get("title", "CLI Session"),
|
|
|
|
|
"workspace": (cli_meta or {}).get("workspace", ""),
|
|
|
|
|
"model": (cli_meta or {}).get("model", "unknown"),
|
|
|
|
|
"message_count": len(msgs),
|
|
|
|
|
"created_at": (cli_meta or {}).get("created_at", 0),
|
|
|
|
|
"updated_at": (cli_meta or {}).get("updated_at", 0),
|
|
|
|
|
"pinned": False,
|
|
|
|
|
"archived": False,
|
|
|
|
|
"project_id": None,
|
|
|
|
|
"profile": (cli_meta or {}).get("profile"),
|
|
|
|
|
"is_cli_session": True,
|
|
|
|
|
"messages": msgs,
|
|
|
|
|
"tool_calls": [],
|
2026-04-04 02:54:54 +00:00
|
|
|
}
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"session": redact_session_data(sess)})
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/sessions":
|
2026-04-04 02:54:54 +00:00
|
|
|
webui_sessions = all_sessions()
|
2026-04-04 04:06:23 +00:00
|
|
|
settings = load_settings()
|
2026-04-12 07:11:41 +00:00
|
|
|
if settings.get("show_cli_sessions"):
|
2026-04-04 04:06:23 +00:00
|
|
|
cli = get_cli_sessions()
|
2026-04-12 07:11:41 +00:00
|
|
|
webui_ids = {s["session_id"] for s in webui_sessions}
|
|
|
|
|
deduped_cli = [s for s in cli if s["session_id"] not in webui_ids]
|
2026-04-04 04:06:23 +00:00
|
|
|
else:
|
|
|
|
|
deduped_cli = []
|
2026-04-04 02:54:54 +00:00
|
|
|
merged = webui_sessions + deduped_cli
|
2026-04-12 07:11:41 +00:00
|
|
|
merged.sort(key=lambda s: s.get("updated_at", 0) or 0, reverse=True)
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
# Redact credentials from session titles before returning
|
|
|
|
|
for s in merged:
|
|
|
|
|
if isinstance(s.get("title"), str):
|
|
|
|
|
s["title"] = _redact_text(s["title"])
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"sessions": merged, "cli_count": len(deduped_cli)})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/projects":
|
|
|
|
|
return j(handler, {"projects": load_projects()})
|
2026-04-02 06:55:21 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/export":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_session_export(handler, parsed)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/workspaces":
|
|
|
|
|
return j(
|
|
|
|
|
handler, {"workspaces": load_workspaces(), "last": get_last_workspace()}
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/sessions/search":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_sessions_search(handler, parsed)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/list":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_list_dir(handler, parsed)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/personalities":
|
2026-04-06 21:10:30 +00:00
|
|
|
# Read personalities from config.yaml agent.personalities section
|
|
|
|
|
# (matches hermes-agent CLI behavior, not filesystem SOUL.md approach)
|
|
|
|
|
from api.config import reload_config as _reload_cfg
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-06 21:10:30 +00:00
|
|
|
_reload_cfg() # pick up config.yaml changes without server restart
|
|
|
|
|
from api.config import get_config as _get_cfg
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-06 21:10:30 +00:00
|
|
|
_cfg = _get_cfg()
|
2026-04-12 07:11:41 +00:00
|
|
|
agent_cfg = _cfg.get("agent", {})
|
|
|
|
|
raw_personalities = agent_cfg.get("personalities", {})
|
feat: /personality slash command with backend integration (#143)
* feat: /personality slash command with backend integration
Add /personality command to switch the agent's system prompt personality.
Hermes CLI supports personalities stored at ~/.hermes/personalities/<name>/SOUL.md.
Backend:
- GET /api/personalities: lists available personalities from the active
profile's personalities directory (reads first line of SOUL.md for desc)
- POST /api/personality/set: sets active personality on the session, reads
and validates the SOUL.md file exists, returns the prompt text
- streaming.py: injects personality prompt (SOUL.md content) as prefix to
the system_message when run_conversation is called
Frontend (commands.js):
- /personality with no args: lists available personalities as a local message
- /personality <name>: sets the personality with a toast confirmation
- /personality none|default|clear: removes the active personality
Session model: new 'personality' field (backward-compatible, defaults to None)
Closes #139
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: path traversal in personality name + case sensitivity
Security: personality name is now validated with regex ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$
in both routes.py (POST /api/personality/set) and streaming.py (system
prompt injection). Defense-in-depth: resolve().relative_to() check ensures
the path stays inside the personalities directory even if regex is bypassed.
Also: removed toLowerCase() from frontend command handler so personality
names are case-preserved (filesystem may be case-sensitive).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: /personality command — hardened, compact() fix, tests
Fixes on top of original PR:
- compact() was missing 'personality' field — UI couldn't know active
personality after page load. Added to Session.compact().
- GET /api/personalities: add symlink guard (is_symlink() skip) and
resolve() check — prevents reading SOUL.md from symlink targets
outside personalities dir.
- POST /api/personality/set: require() only checks session_id (not name)
so clearing with name='' works correctly instead of 400.
- POST /api/personality/set: add MAX_FILE_BYTES size cap on SOUL.md to
prevent unbounded context window consumption.
- POST /api/personality/set: return personality:null (not '') when cleared.
- streaming.py: same MAX_FILE_BYTES guard before prepending to system msg.
Added tests/test_sprint28.py: 11 tests for API round-trip, listing,
symlink guard, path traversal rejection, clear, size cap, persistence.
Tests pass in isolation; full-suite run has a test-isolation interaction
with shared server state across sprint tests (tracked as follow-up).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 18:16:37 +00:00
|
|
|
personalities = []
|
2026-04-06 21:10:30 +00:00
|
|
|
if isinstance(raw_personalities, dict):
|
|
|
|
|
for name, value in raw_personalities.items():
|
2026-04-12 07:11:41 +00:00
|
|
|
desc = ""
|
2026-04-06 21:10:30 +00:00
|
|
|
if isinstance(value, dict):
|
2026-04-12 07:11:41 +00:00
|
|
|
desc = value.get("description", "")
|
2026-04-06 21:10:30 +00:00
|
|
|
elif isinstance(value, str):
|
2026-04-12 07:11:41 +00:00
|
|
|
desc = value[:80] + ("..." if len(value) > 80 else "")
|
|
|
|
|
personalities.append({"name": name, "description": desc})
|
|
|
|
|
return j(handler, {"personalities": personalities})
|
feat: /personality slash command with backend integration (#143)
* feat: /personality slash command with backend integration
Add /personality command to switch the agent's system prompt personality.
Hermes CLI supports personalities stored at ~/.hermes/personalities/<name>/SOUL.md.
Backend:
- GET /api/personalities: lists available personalities from the active
profile's personalities directory (reads first line of SOUL.md for desc)
- POST /api/personality/set: sets active personality on the session, reads
and validates the SOUL.md file exists, returns the prompt text
- streaming.py: injects personality prompt (SOUL.md content) as prefix to
the system_message when run_conversation is called
Frontend (commands.js):
- /personality with no args: lists available personalities as a local message
- /personality <name>: sets the personality with a toast confirmation
- /personality none|default|clear: removes the active personality
Session model: new 'personality' field (backward-compatible, defaults to None)
Closes #139
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: path traversal in personality name + case sensitivity
Security: personality name is now validated with regex ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$
in both routes.py (POST /api/personality/set) and streaming.py (system
prompt injection). Defense-in-depth: resolve().relative_to() check ensures
the path stays inside the personalities directory even if regex is bypassed.
Also: removed toLowerCase() from frontend command handler so personality
names are case-preserved (filesystem may be case-sensitive).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: /personality command — hardened, compact() fix, tests
Fixes on top of original PR:
- compact() was missing 'personality' field — UI couldn't know active
personality after page load. Added to Session.compact().
- GET /api/personalities: add symlink guard (is_symlink() skip) and
resolve() check — prevents reading SOUL.md from symlink targets
outside personalities dir.
- POST /api/personality/set: require() only checks session_id (not name)
so clearing with name='' works correctly instead of 400.
- POST /api/personality/set: add MAX_FILE_BYTES size cap on SOUL.md to
prevent unbounded context window consumption.
- POST /api/personality/set: return personality:null (not '') when cleared.
- streaming.py: same MAX_FILE_BYTES guard before prepending to system msg.
Added tests/test_sprint28.py: 11 tests for API round-trip, listing,
symlink guard, path traversal rejection, clear, size cap, persistence.
Tests pass in isolation; full-suite run has a test-isolation interaction
with shared server state across sprint tests (tracked as follow-up).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 18:16:37 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/git-info":
|
feat: workspace git detection with branch/status badge
When the workspace root is a git repo, a badge in the panel header
shows the current branch name, dirty file count, and ahead/behind
status. Updates on every root directory load.
Backend:
- git_info_for_workspace() in api/workspace.py runs lightweight git
commands (rev-parse, status --porcelain, rev-list) with 3s timeout
- New GET /api/git-info endpoint returns branch, dirty count, modified,
untracked, ahead, behind
Frontend:
- _refreshGitBadge() in workspace.js fetches git info on root load
- Git badge element in panel header shows branch + status
- Badge turns gold when workspace has uncommitted changes
Inspired by PR #75 (@MartinNielsenDev).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:08:25 +00:00
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = qs.get("session_id", [""])[0]
|
feat: workspace git detection with branch/status badge
When the workspace root is a git repo, a badge in the panel header
shows the current branch name, dirty file count, and ahead/behind
status. Updates on every root directory load.
Backend:
- git_info_for_workspace() in api/workspace.py runs lightweight git
commands (rev-parse, status --porcelain, rev-list) with 3s timeout
- New GET /api/git-info endpoint returns branch, dirty count, modified,
untracked, ahead, behind
Frontend:
- _refreshGitBadge() in workspace.js fetches git info on root load
- Git badge element in panel header shows branch + status
- Badge turns gold when workspace has uncommitted changes
Inspired by PR #75 (@MartinNielsenDev).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:08:25 +00:00
|
|
|
if not sid:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "session_id required")
|
2026-04-04 21:29:24 +00:00
|
|
|
try:
|
|
|
|
|
s = get_session(sid)
|
|
|
|
|
except KeyError:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Session not found", 404)
|
feat: workspace git detection with branch/status badge
When the workspace root is a git repo, a badge in the panel header
shows the current branch name, dirty file count, and ahead/behind
status. Updates on every root directory load.
Backend:
- git_info_for_workspace() in api/workspace.py runs lightweight git
commands (rev-parse, status --porcelain, rev-list) with 3s timeout
- New GET /api/git-info endpoint returns branch, dirty count, modified,
untracked, ahead, behind
Frontend:
- _refreshGitBadge() in workspace.js fetches git info on root load
- Git badge element in panel header shows branch + status
- Badge turns gold when workspace has uncommitted changes
Inspired by PR #75 (@MartinNielsenDev).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:08:25 +00:00
|
|
|
from api.workspace import git_info_for_workspace
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: workspace git detection with branch/status badge
When the workspace root is a git repo, a badge in the panel header
shows the current branch name, dirty file count, and ahead/behind
status. Updates on every root directory load.
Backend:
- git_info_for_workspace() in api/workspace.py runs lightweight git
commands (rev-parse, status --porcelain, rev-list) with 3s timeout
- New GET /api/git-info endpoint returns branch, dirty count, modified,
untracked, ahead, behind
Frontend:
- _refreshGitBadge() in workspace.js fetches git info on root load
- Git badge element in panel header shows branch + status
- Badge turns gold when workspace has uncommitted changes
Inspired by PR #75 (@MartinNielsenDev).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:08:25 +00:00
|
|
|
info = git_info_for_workspace(Path(s.workspace))
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"git": info})
|
feat: workspace git detection with branch/status badge
When the workspace root is a git repo, a badge in the panel header
shows the current branch name, dirty file count, and ahead/behind
status. Updates on every root directory load.
Backend:
- git_info_for_workspace() in api/workspace.py runs lightweight git
commands (rev-parse, status --porcelain, rev-list) with 3s timeout
- New GET /api/git-info endpoint returns branch, dirty count, modified,
untracked, ahead, behind
Frontend:
- _refreshGitBadge() in workspace.js fetches git info on root load
- Git badge element in panel header shows branch + status
- Badge turns gold when workspace has uncommitted changes
Inspired by PR #75 (@MartinNielsenDev).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:08:25 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/updates/check":
|
feat: self-update checker with one-click update for WebUI + Agent
Shows a blue banner when the webui or hermes-agent git repos are behind
their upstream branches. One-click 'Update Now' button does stash, pull
--ff-only, stash pop, then reloads the page.
Backend (api/updates.py):
- _check_repo(): git fetch + rev-list count with 15s timeout
- check_for_updates(): 30-min server-side cache, thread-safe, skips
Docker (no .git dir)
- apply_update(): stash (if dirty), pull --ff-only, pop, invalidate cache
Routes:
- GET /api/updates/check -- returns cached {webui, agent} with behind count
- POST /api/updates/apply -- {target: 'webui'|'agent'}
Frontend:
- Blue banner (matches reconnect-banner pattern) with 'Later' / 'Update Now'
- Non-blocking boot check via fire-and-forget .then(), once per tab session
- sessionStorage guards prevent re-checking and re-showing after dismiss
Settings:
- 'Check for updates' checkbox (default: on) -- when off, no git operations
- Removed 'Default Workspace' dropdown to keep settings panel compact
Performance:
- Server cache: git fetch at most 2x/hour regardless of client count
- sessionStorage: one check per browser tab session
- _check_in_progress flag prevents concurrent fetch storms
- Fire-and-forget: does NOT block the boot sequence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:11:44 +00:00
|
|
|
settings = load_settings()
|
2026-04-12 07:11:41 +00:00
|
|
|
if not settings.get("check_for_updates", True):
|
|
|
|
|
return j(handler, {"disabled": True})
|
2026-04-05 16:20:12 +00:00
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
force = qs.get("force", ["0"])[0] == "1"
|
2026-04-05 16:20:12 +00:00
|
|
|
# ?simulate=1 returns fake behind counts for UI testing (localhost only)
|
2026-04-12 07:11:41 +00:00
|
|
|
if (
|
|
|
|
|
qs.get("simulate", ["0"])[0] == "1"
|
|
|
|
|
and handler.client_address[0] == "127.0.0.1"
|
|
|
|
|
):
|
|
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{
|
|
|
|
|
"webui": {
|
|
|
|
|
"name": "webui",
|
|
|
|
|
"behind": 3,
|
|
|
|
|
"current_sha": "abc1234",
|
|
|
|
|
"latest_sha": "def5678",
|
|
|
|
|
"branch": "master",
|
|
|
|
|
},
|
|
|
|
|
"agent": {
|
|
|
|
|
"name": "agent",
|
|
|
|
|
"behind": 1,
|
|
|
|
|
"current_sha": "aaa0001",
|
|
|
|
|
"latest_sha": "bbb0002",
|
|
|
|
|
"branch": "master",
|
|
|
|
|
},
|
|
|
|
|
"checked_at": 0,
|
|
|
|
|
},
|
|
|
|
|
)
|
feat: self-update checker with one-click update for WebUI + Agent
Shows a blue banner when the webui or hermes-agent git repos are behind
their upstream branches. One-click 'Update Now' button does stash, pull
--ff-only, stash pop, then reloads the page.
Backend (api/updates.py):
- _check_repo(): git fetch + rev-list count with 15s timeout
- check_for_updates(): 30-min server-side cache, thread-safe, skips
Docker (no .git dir)
- apply_update(): stash (if dirty), pull --ff-only, pop, invalidate cache
Routes:
- GET /api/updates/check -- returns cached {webui, agent} with behind count
- POST /api/updates/apply -- {target: 'webui'|'agent'}
Frontend:
- Blue banner (matches reconnect-banner pattern) with 'Later' / 'Update Now'
- Non-blocking boot check via fire-and-forget .then(), once per tab session
- sessionStorage guards prevent re-checking and re-showing after dismiss
Settings:
- 'Check for updates' checkbox (default: on) -- when off, no git operations
- Removed 'Default Workspace' dropdown to keep settings panel compact
Performance:
- Server cache: git fetch at most 2x/hour regardless of client count
- sessionStorage: one check per browser tab session
- _check_in_progress flag prevents concurrent fetch storms
- Fire-and-forget: does NOT block the boot sequence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:11:44 +00:00
|
|
|
from api.updates import check_for_updates
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: self-update checker with one-click update for WebUI + Agent
Shows a blue banner when the webui or hermes-agent git repos are behind
their upstream branches. One-click 'Update Now' button does stash, pull
--ff-only, stash pop, then reloads the page.
Backend (api/updates.py):
- _check_repo(): git fetch + rev-list count with 15s timeout
- check_for_updates(): 30-min server-side cache, thread-safe, skips
Docker (no .git dir)
- apply_update(): stash (if dirty), pull --ff-only, pop, invalidate cache
Routes:
- GET /api/updates/check -- returns cached {webui, agent} with behind count
- POST /api/updates/apply -- {target: 'webui'|'agent'}
Frontend:
- Blue banner (matches reconnect-banner pattern) with 'Later' / 'Update Now'
- Non-blocking boot check via fire-and-forget .then(), once per tab session
- sessionStorage guards prevent re-checking and re-showing after dismiss
Settings:
- 'Check for updates' checkbox (default: on) -- when off, no git operations
- Removed 'Default Workspace' dropdown to keep settings panel compact
Performance:
- Server cache: git fetch at most 2x/hour regardless of client count
- sessionStorage: one check per browser tab session
- _check_in_progress flag prevents concurrent fetch storms
- Fire-and-forget: does NOT block the boot sequence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:11:44 +00:00
|
|
|
return j(handler, check_for_updates(force=force))
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/chat/stream/status":
|
|
|
|
|
stream_id = parse_qs(parsed.query).get("stream_id", [""])[0]
|
|
|
|
|
return j(handler, {"active": stream_id in STREAMS, "stream_id": stream_id})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/chat/cancel":
|
|
|
|
|
stream_id = parse_qs(parsed.query).get("stream_id", [""])[0]
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if not stream_id:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "stream_id required")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
cancelled = cancel_stream(stream_id)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "cancelled": cancelled, "stream_id": stream_id})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/chat/stream":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_sse_stream(handler, parsed)
|
|
|
|
|
|
feat: real-time gateway session sync (Phase 1) (#274)
* feat: add real-time gateway session sync (Phase 1)
- Add gateway_watcher.py: background daemon polling state.db every 5s
for gateway session changes (telegram, discord, slack, etc.)
- Extend get_cli_sessions() to include all non-webui sources
- Add SSE endpoint /api/sessions/gateway/stream for real-time push
- Add dynamic source badges (telegram=blue, discord=purple, slack=dark purple)
- Rename 'Show CLI sessions' to 'Show agent sessions'
- Wire watcher lifecycle into server start/stop
- 10 tests covering metadata, filtering, SSE, and watcher lifecycle
- Activated via the same checkbox as CLI session import
Addresses GitHub issue #272
* fix: SSE event name mismatch, TLS attribute, remove PLAN.md
- Fix critical SSE bug: frontend listened for 'gateway_session_update'
but backend sends 'sessions_changed' -- events were silently dropped
- Fix frontend field check: data.changed -> data.sessions (matches
the actual payload structure from gateway_watcher)
- Fix TLS: ssl.TLSv1_2 -> ssl.TLSVersion.TLSv1_2 (the bare attribute
does not exist, would crash TLS setup and silently fall back to HTTP)
- Remove PLAN.md: implementation plan should not be committed to repo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: test isolation and slow-consumer sentinel in gateway sync
tests/test_gateway_sync.py:
- Fix _get_test_state_dir() path mismatch: the function was computing
HERMES_HOME/webui-mvp-test but conftest.py sets HERMES_HOME=TEST_STATE_DIR,
so state.db was written to a double-nested path the server never read.
Now uses HERMES_WEBUI_STATE_DIR first (which conftest sets directly to
TEST_STATE_DIR), fixing the 7/10 test failures in full-suite ordering.
- Fix conn cleanup: removed conn.close() from inside try blocks so the
connection stays valid for _remove_test_sessions() in the finally block.
Previously the closed conn caused ProgrammingError in finally (swallowed
by bare except), leaving ghost sessions in state.db on test failure.
api/gateway_watcher.py:
- Fix slow-consumer queue eviction: when a subscriber queue fills (>10 events)
and is removed from _subscribers, now puts a None sentinel into it so the
SSE handler unblocks and closes the connection, letting EventSource
auto-reconnect. Without this the connection stayed open but received no
further events.
* fix: test isolation — set HERMES_WEBUI_TEST_STATE_DIR in conftest
The gateway sync tests write directly to state.db and must use the same
path the test server reads from. Previously they computed the path
independently, which broke when test_auth_sessions.py set a different
HERMES_WEBUI_STATE_DIR in the test-process environment at import time.
tests/conftest.py:
- Set HERMES_WEBUI_TEST_STATE_DIR=TEST_STATE_DIR in the test process's
os.environ (via setdefault) so gateway tests can read it reliably.
Using setdefault preserves any explicit override the caller may pass.
tests/test_gateway_sync.py:
- Simplify _get_test_state_dir(): check HERMES_WEBUI_TEST_STATE_DIR first
(now reliably set by conftest), fall back to HERMES_HOME/webui-mvp-test.
Remove the workaround that tried to snapshot HERMES_HOME at import time.
Result: 658/658 tests pass in full-suite ordering (was 651 pass / 7 fail).
---------
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 03:53:12 +00:00
|
|
|
if parsed.path == '/api/sessions/gateway/stream':
|
|
|
|
|
return _handle_gateway_sse_stream(handler)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/file/raw":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_file_raw(handler, parsed)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/file":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_file_read(handler, parsed)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/approval/pending":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_approval_pending(handler, parsed)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/approval/inject_test":
|
2026-04-02 06:46:40 +00:00
|
|
|
# Loopback-only: used by automated tests; blocked from any remote client
|
2026-04-12 07:11:41 +00:00
|
|
|
if handler.client_address[0] != "127.0.0.1":
|
|
|
|
|
return j(handler, {"error": "not found"}, status=404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_approval_inject(handler, parsed)
|
|
|
|
|
|
|
|
|
|
# ── Cron API (GET) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/crons":
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import list_jobs
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"jobs": list_jobs(include_disabled=True)})
|
|
|
|
|
|
|
|
|
|
if parsed.path == "/api/crons/output":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_cron_output(handler, parsed)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/crons/recent":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_cron_recent(handler, parsed)
|
|
|
|
|
|
|
|
|
|
# ── Skills API (GET) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/skills":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
from tools.skills_tool import skills_list as _skills_list
|
2026-04-12 07:11:41 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
raw = _skills_list()
|
|
|
|
|
data = json.loads(raw) if isinstance(raw, str) else raw
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"skills": data.get("skills", [])})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/skills/content":
|
feat: Sprint 23 — agentic transparency + polish
Track A: Token/cost display
- Read agent usage attrs (session_prompt_tokens, session_completion_tokens,
session_estimated_cost_usd) after run_conversation in streaming.py
- Add input_tokens, output_tokens, estimated_cost fields to Session model
- Include usage in done SSE event payload
- Store usage on S.lastUsage in messages.js done handler
- Render usage badge below last assistant message (input/output/cost)
Track B: Subagent delegation cards
- Add subagent_progress to toolIcon map with shuffle emoji
- Special-case subagent_progress in buildToolCard: "Subagent" label,
strip double emoji from preview, add tool-card-subagent CSS class
- Indented border-left styling for subagent cards
- Clean delegate_task display name
Track C: Skill picker in cron create form
- Add skill search input + tag chips to cron create form HTML
- Skill picker JS in panels.js: search/filter, click-to-add tags,
remove tag chips, pre-fetch skill list on form open
- submitCronCreate sends skills array in POST body
- Skill picker dropdown + tag CSS
Track D: Skill linked files viewer
- Add file query param to /api/skills/content endpoint
- Serve linked files from skill directory with path traversal protection
- Ensure linked_files key always present in skill content response
- Render linked files section below SKILL.md content in preview panel
- openSkillFile function for viewing individual linked files
Track E: Bug fixes and code quality
- Expand Session.__init__ and compact() to readable multi-line format
- Remove inline import json as _j2 inside loop in streaming.py
- Fix tool_calls: capture args from assistant messages, skip unresolved names
- Store args snapshot in persisted tool_calls for reload display
6 new tests. Total: 421 (409 passing).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 01:33:49 +00:00
|
|
|
from tools.skills_tool import skill_view as _skill_view, SKILLS_DIR
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: Sprint 23 — agentic transparency + polish
Track A: Token/cost display
- Read agent usage attrs (session_prompt_tokens, session_completion_tokens,
session_estimated_cost_usd) after run_conversation in streaming.py
- Add input_tokens, output_tokens, estimated_cost fields to Session model
- Include usage in done SSE event payload
- Store usage on S.lastUsage in messages.js done handler
- Render usage badge below last assistant message (input/output/cost)
Track B: Subagent delegation cards
- Add subagent_progress to toolIcon map with shuffle emoji
- Special-case subagent_progress in buildToolCard: "Subagent" label,
strip double emoji from preview, add tool-card-subagent CSS class
- Indented border-left styling for subagent cards
- Clean delegate_task display name
Track C: Skill picker in cron create form
- Add skill search input + tag chips to cron create form HTML
- Skill picker JS in panels.js: search/filter, click-to-add tags,
remove tag chips, pre-fetch skill list on form open
- submitCronCreate sends skills array in POST body
- Skill picker dropdown + tag CSS
Track D: Skill linked files viewer
- Add file query param to /api/skills/content endpoint
- Serve linked files from skill directory with path traversal protection
- Ensure linked_files key always present in skill content response
- Render linked files section below SKILL.md content in preview panel
- openSkillFile function for viewing individual linked files
Track E: Bug fixes and code quality
- Expand Session.__init__ and compact() to readable multi-line format
- Remove inline import json as _j2 inside loop in streaming.py
- Fix tool_calls: capture args from assistant messages, skip unresolved names
- Store args snapshot in persisted tool_calls for reload display
6 new tests. Total: 421 (409 passing).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 01:33:49 +00:00
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
name = qs.get("name", [""])[0]
|
|
|
|
|
if not name:
|
|
|
|
|
return j(handler, {"error": "name required"}, status=400)
|
|
|
|
|
file_path = qs.get("file", [""])[0]
|
feat: Sprint 23 — agentic transparency + polish
Track A: Token/cost display
- Read agent usage attrs (session_prompt_tokens, session_completion_tokens,
session_estimated_cost_usd) after run_conversation in streaming.py
- Add input_tokens, output_tokens, estimated_cost fields to Session model
- Include usage in done SSE event payload
- Store usage on S.lastUsage in messages.js done handler
- Render usage badge below last assistant message (input/output/cost)
Track B: Subagent delegation cards
- Add subagent_progress to toolIcon map with shuffle emoji
- Special-case subagent_progress in buildToolCard: "Subagent" label,
strip double emoji from preview, add tool-card-subagent CSS class
- Indented border-left styling for subagent cards
- Clean delegate_task display name
Track C: Skill picker in cron create form
- Add skill search input + tag chips to cron create form HTML
- Skill picker JS in panels.js: search/filter, click-to-add tags,
remove tag chips, pre-fetch skill list on form open
- submitCronCreate sends skills array in POST body
- Skill picker dropdown + tag CSS
Track D: Skill linked files viewer
- Add file query param to /api/skills/content endpoint
- Serve linked files from skill directory with path traversal protection
- Ensure linked_files key always present in skill content response
- Render linked files section below SKILL.md content in preview panel
- openSkillFile function for viewing individual linked files
Track E: Bug fixes and code quality
- Expand Session.__init__ and compact() to readable multi-line format
- Remove inline import json as _j2 inside loop in streaming.py
- Fix tool_calls: capture args from assistant messages, skip unresolved names
- Store args snapshot in persisted tool_calls for reload display
6 new tests. Total: 421 (409 passing).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 01:33:49 +00:00
|
|
|
if file_path:
|
|
|
|
|
# Serve a linked file from the skill directory
|
2026-04-04 01:54:53 +00:00
|
|
|
import re as _re
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
if _re.search(r"[*?\[\]]", name):
|
|
|
|
|
return bad(handler, "Invalid skill name", 400)
|
feat: Sprint 23 — agentic transparency + polish
Track A: Token/cost display
- Read agent usage attrs (session_prompt_tokens, session_completion_tokens,
session_estimated_cost_usd) after run_conversation in streaming.py
- Add input_tokens, output_tokens, estimated_cost fields to Session model
- Include usage in done SSE event payload
- Store usage on S.lastUsage in messages.js done handler
- Render usage badge below last assistant message (input/output/cost)
Track B: Subagent delegation cards
- Add subagent_progress to toolIcon map with shuffle emoji
- Special-case subagent_progress in buildToolCard: "Subagent" label,
strip double emoji from preview, add tool-card-subagent CSS class
- Indented border-left styling for subagent cards
- Clean delegate_task display name
Track C: Skill picker in cron create form
- Add skill search input + tag chips to cron create form HTML
- Skill picker JS in panels.js: search/filter, click-to-add tags,
remove tag chips, pre-fetch skill list on form open
- submitCronCreate sends skills array in POST body
- Skill picker dropdown + tag CSS
Track D: Skill linked files viewer
- Add file query param to /api/skills/content endpoint
- Serve linked files from skill directory with path traversal protection
- Ensure linked_files key always present in skill content response
- Render linked files section below SKILL.md content in preview panel
- openSkillFile function for viewing individual linked files
Track E: Bug fixes and code quality
- Expand Session.__init__ and compact() to readable multi-line format
- Remove inline import json as _j2 inside loop in streaming.py
- Fix tool_calls: capture args from assistant messages, skip unresolved names
- Store args snapshot in persisted tool_calls for reload display
6 new tests. Total: 421 (409 passing).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 01:33:49 +00:00
|
|
|
skill_dir = None
|
|
|
|
|
for p in SKILLS_DIR.rglob(name):
|
2026-04-12 07:11:41 +00:00
|
|
|
if p.is_dir():
|
|
|
|
|
skill_dir = p
|
|
|
|
|
break
|
|
|
|
|
if not skill_dir:
|
|
|
|
|
return bad(handler, "Skill not found", 404)
|
feat: Sprint 23 — agentic transparency + polish
Track A: Token/cost display
- Read agent usage attrs (session_prompt_tokens, session_completion_tokens,
session_estimated_cost_usd) after run_conversation in streaming.py
- Add input_tokens, output_tokens, estimated_cost fields to Session model
- Include usage in done SSE event payload
- Store usage on S.lastUsage in messages.js done handler
- Render usage badge below last assistant message (input/output/cost)
Track B: Subagent delegation cards
- Add subagent_progress to toolIcon map with shuffle emoji
- Special-case subagent_progress in buildToolCard: "Subagent" label,
strip double emoji from preview, add tool-card-subagent CSS class
- Indented border-left styling for subagent cards
- Clean delegate_task display name
Track C: Skill picker in cron create form
- Add skill search input + tag chips to cron create form HTML
- Skill picker JS in panels.js: search/filter, click-to-add tags,
remove tag chips, pre-fetch skill list on form open
- submitCronCreate sends skills array in POST body
- Skill picker dropdown + tag CSS
Track D: Skill linked files viewer
- Add file query param to /api/skills/content endpoint
- Serve linked files from skill directory with path traversal protection
- Ensure linked_files key always present in skill content response
- Render linked files section below SKILL.md content in preview panel
- openSkillFile function for viewing individual linked files
Track E: Bug fixes and code quality
- Expand Session.__init__ and compact() to readable multi-line format
- Remove inline import json as _j2 inside loop in streaming.py
- Fix tool_calls: capture args from assistant messages, skip unresolved names
- Store args snapshot in persisted tool_calls for reload display
6 new tests. Total: 421 (409 passing).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 01:33:49 +00:00
|
|
|
target = (skill_dir / file_path).resolve()
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
target.relative_to(skill_dir.resolve())
|
|
|
|
|
except ValueError:
|
|
|
|
|
return bad(handler, "Invalid file path", 400)
|
feat: Sprint 23 — agentic transparency + polish
Track A: Token/cost display
- Read agent usage attrs (session_prompt_tokens, session_completion_tokens,
session_estimated_cost_usd) after run_conversation in streaming.py
- Add input_tokens, output_tokens, estimated_cost fields to Session model
- Include usage in done SSE event payload
- Store usage on S.lastUsage in messages.js done handler
- Render usage badge below last assistant message (input/output/cost)
Track B: Subagent delegation cards
- Add subagent_progress to toolIcon map with shuffle emoji
- Special-case subagent_progress in buildToolCard: "Subagent" label,
strip double emoji from preview, add tool-card-subagent CSS class
- Indented border-left styling for subagent cards
- Clean delegate_task display name
Track C: Skill picker in cron create form
- Add skill search input + tag chips to cron create form HTML
- Skill picker JS in panels.js: search/filter, click-to-add tags,
remove tag chips, pre-fetch skill list on form open
- submitCronCreate sends skills array in POST body
- Skill picker dropdown + tag CSS
Track D: Skill linked files viewer
- Add file query param to /api/skills/content endpoint
- Serve linked files from skill directory with path traversal protection
- Ensure linked_files key always present in skill content response
- Render linked files section below SKILL.md content in preview panel
- openSkillFile function for viewing individual linked files
Track E: Bug fixes and code quality
- Expand Session.__init__ and compact() to readable multi-line format
- Remove inline import json as _j2 inside loop in streaming.py
- Fix tool_calls: capture args from assistant messages, skip unresolved names
- Store args snapshot in persisted tool_calls for reload display
6 new tests. Total: 421 (409 passing).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 01:33:49 +00:00
|
|
|
if not target.exists() or not target.is_file():
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "File not found", 404)
|
|
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{"content": target.read_text(encoding="utf-8"), "path": file_path},
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
raw = _skill_view(name)
|
|
|
|
|
data = json.loads(raw) if isinstance(raw, str) else raw
|
2026-04-12 07:11:41 +00:00
|
|
|
if "linked_files" not in data:
|
|
|
|
|
data["linked_files"] = {}
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return j(handler, data)
|
|
|
|
|
|
|
|
|
|
# ── Memory API (GET) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/memory":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_memory_read(handler)
|
|
|
|
|
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
# ── Profile API (GET) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/profiles":
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
from api.profiles import list_profiles_api, get_active_profile_name
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{"profiles": list_profiles_api(), "active": get_active_profile_name()},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if parsed.path == "/api/profile/active":
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
from api.profiles import get_active_profile_name, get_active_hermes_home
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{"name": get_active_profile_name(), "path": str(get_active_hermes_home())},
|
|
|
|
|
)
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return False # 404
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── POST routes ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-05 06:30:20 +00:00
|
|
|
def handle_post(handler, parsed) -> bool:
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
"""Handle all POST routes. Returns True if handled, False for 404."""
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
# CSRF: reject cross-origin browser requests
|
|
|
|
|
if not _check_csrf(handler):
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"error": "Cross-origin request rejected"}, status=403)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/upload":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return handle_upload(handler)
|
|
|
|
|
|
|
|
|
|
body = read_body(handler)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/new":
|
|
|
|
|
s = new_session(workspace=body.get("workspace"), model=body.get("model"))
|
|
|
|
|
return j(handler, {"session": s.compact() | {"messages": s.messages}})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/sessions/cleanup":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_sessions_cleanup(handler, body, zero_only=False)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/sessions/cleanup_zero_message":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_sessions_cleanup(handler, body, zero_only=True)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/rename":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "session_id", "title")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
s.title = str(body["title"]).strip()[:80] or "Untitled"
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
s.save()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"session": s.compact()})
|
|
|
|
|
|
|
|
|
|
if parsed.path == "/api/personality/set":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "session_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
if "name" not in body:
|
|
|
|
|
return bad(handler, "Missing required field: name")
|
|
|
|
|
sid = body["session_id"]
|
|
|
|
|
name = body["name"].strip()
|
feat: /personality slash command with backend integration (#143)
* feat: /personality slash command with backend integration
Add /personality command to switch the agent's system prompt personality.
Hermes CLI supports personalities stored at ~/.hermes/personalities/<name>/SOUL.md.
Backend:
- GET /api/personalities: lists available personalities from the active
profile's personalities directory (reads first line of SOUL.md for desc)
- POST /api/personality/set: sets active personality on the session, reads
and validates the SOUL.md file exists, returns the prompt text
- streaming.py: injects personality prompt (SOUL.md content) as prefix to
the system_message when run_conversation is called
Frontend (commands.js):
- /personality with no args: lists available personalities as a local message
- /personality <name>: sets the personality with a toast confirmation
- /personality none|default|clear: removes the active personality
Session model: new 'personality' field (backward-compatible, defaults to None)
Closes #139
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: path traversal in personality name + case sensitivity
Security: personality name is now validated with regex ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$
in both routes.py (POST /api/personality/set) and streaming.py (system
prompt injection). Defense-in-depth: resolve().relative_to() check ensures
the path stays inside the personalities directory even if regex is bypassed.
Also: removed toLowerCase() from frontend command handler so personality
names are case-preserved (filesystem may be case-sensitive).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: /personality command — hardened, compact() fix, tests
Fixes on top of original PR:
- compact() was missing 'personality' field — UI couldn't know active
personality after page load. Added to Session.compact().
- GET /api/personalities: add symlink guard (is_symlink() skip) and
resolve() check — prevents reading SOUL.md from symlink targets
outside personalities dir.
- POST /api/personality/set: require() only checks session_id (not name)
so clearing with name='' works correctly instead of 400.
- POST /api/personality/set: add MAX_FILE_BYTES size cap on SOUL.md to
prevent unbounded context window consumption.
- POST /api/personality/set: return personality:null (not '') when cleared.
- streaming.py: same MAX_FILE_BYTES guard before prepending to system msg.
Added tests/test_sprint28.py: 11 tests for API round-trip, listing,
symlink guard, path traversal rejection, clear, size cap, persistence.
Tests pass in isolation; full-suite run has a test-isolation interaction
with shared server state across sprint tests (tracked as follow-up).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 18:16:37 +00:00
|
|
|
try:
|
|
|
|
|
s = get_session(sid)
|
|
|
|
|
except KeyError:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Session not found", 404)
|
2026-04-06 21:10:30 +00:00
|
|
|
# Resolve personality from config.yaml agent.personalities section
|
|
|
|
|
# (matches hermes-agent CLI behavior)
|
2026-04-12 07:11:41 +00:00
|
|
|
prompt = ""
|
feat: /personality slash command with backend integration (#143)
* feat: /personality slash command with backend integration
Add /personality command to switch the agent's system prompt personality.
Hermes CLI supports personalities stored at ~/.hermes/personalities/<name>/SOUL.md.
Backend:
- GET /api/personalities: lists available personalities from the active
profile's personalities directory (reads first line of SOUL.md for desc)
- POST /api/personality/set: sets active personality on the session, reads
and validates the SOUL.md file exists, returns the prompt text
- streaming.py: injects personality prompt (SOUL.md content) as prefix to
the system_message when run_conversation is called
Frontend (commands.js):
- /personality with no args: lists available personalities as a local message
- /personality <name>: sets the personality with a toast confirmation
- /personality none|default|clear: removes the active personality
Session model: new 'personality' field (backward-compatible, defaults to None)
Closes #139
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: path traversal in personality name + case sensitivity
Security: personality name is now validated with regex ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$
in both routes.py (POST /api/personality/set) and streaming.py (system
prompt injection). Defense-in-depth: resolve().relative_to() check ensures
the path stays inside the personalities directory even if regex is bypassed.
Also: removed toLowerCase() from frontend command handler so personality
names are case-preserved (filesystem may be case-sensitive).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: /personality command — hardened, compact() fix, tests
Fixes on top of original PR:
- compact() was missing 'personality' field — UI couldn't know active
personality after page load. Added to Session.compact().
- GET /api/personalities: add symlink guard (is_symlink() skip) and
resolve() check — prevents reading SOUL.md from symlink targets
outside personalities dir.
- POST /api/personality/set: require() only checks session_id (not name)
so clearing with name='' works correctly instead of 400.
- POST /api/personality/set: add MAX_FILE_BYTES size cap on SOUL.md to
prevent unbounded context window consumption.
- POST /api/personality/set: return personality:null (not '') when cleared.
- streaming.py: same MAX_FILE_BYTES guard before prepending to system msg.
Added tests/test_sprint28.py: 11 tests for API round-trip, listing,
symlink guard, path traversal rejection, clear, size cap, persistence.
Tests pass in isolation; full-suite run has a test-isolation interaction
with shared server state across sprint tests (tracked as follow-up).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 18:16:37 +00:00
|
|
|
if name:
|
2026-04-06 21:10:30 +00:00
|
|
|
from api.config import reload_config as _reload_cfg2
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-06 21:10:30 +00:00
|
|
|
_reload_cfg2() # pick up config changes without restart
|
|
|
|
|
from api.config import get_config as _get_cfg2
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-06 21:10:30 +00:00
|
|
|
_cfg2 = _get_cfg2()
|
2026-04-12 07:11:41 +00:00
|
|
|
agent_cfg = _cfg2.get("agent", {})
|
|
|
|
|
raw_personalities = agent_cfg.get("personalities", {})
|
2026-04-06 21:10:30 +00:00
|
|
|
if not isinstance(raw_personalities, dict) or name not in raw_personalities:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(
|
|
|
|
|
handler, f'Personality "{name}" not found in config.yaml', 404
|
|
|
|
|
)
|
2026-04-06 21:10:30 +00:00
|
|
|
value = raw_personalities[name]
|
|
|
|
|
# Resolve prompt using the same logic as hermes-agent cli.py
|
|
|
|
|
if isinstance(value, dict):
|
2026-04-12 07:11:41 +00:00
|
|
|
parts = [value.get("system_prompt", "") or value.get("prompt", "")]
|
|
|
|
|
if value.get("tone"):
|
|
|
|
|
parts.append(f"Tone: {value['tone']}")
|
|
|
|
|
if value.get("style"):
|
|
|
|
|
parts.append(f"Style: {value['style']}")
|
|
|
|
|
prompt = "\n".join(p for p in parts if p)
|
feat: /personality slash command with backend integration (#143)
* feat: /personality slash command with backend integration
Add /personality command to switch the agent's system prompt personality.
Hermes CLI supports personalities stored at ~/.hermes/personalities/<name>/SOUL.md.
Backend:
- GET /api/personalities: lists available personalities from the active
profile's personalities directory (reads first line of SOUL.md for desc)
- POST /api/personality/set: sets active personality on the session, reads
and validates the SOUL.md file exists, returns the prompt text
- streaming.py: injects personality prompt (SOUL.md content) as prefix to
the system_message when run_conversation is called
Frontend (commands.js):
- /personality with no args: lists available personalities as a local message
- /personality <name>: sets the personality with a toast confirmation
- /personality none|default|clear: removes the active personality
Session model: new 'personality' field (backward-compatible, defaults to None)
Closes #139
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: path traversal in personality name + case sensitivity
Security: personality name is now validated with regex ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$
in both routes.py (POST /api/personality/set) and streaming.py (system
prompt injection). Defense-in-depth: resolve().relative_to() check ensures
the path stays inside the personalities directory even if regex is bypassed.
Also: removed toLowerCase() from frontend command handler so personality
names are case-preserved (filesystem may be case-sensitive).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: /personality command — hardened, compact() fix, tests
Fixes on top of original PR:
- compact() was missing 'personality' field — UI couldn't know active
personality after page load. Added to Session.compact().
- GET /api/personalities: add symlink guard (is_symlink() skip) and
resolve() check — prevents reading SOUL.md from symlink targets
outside personalities dir.
- POST /api/personality/set: require() only checks session_id (not name)
so clearing with name='' works correctly instead of 400.
- POST /api/personality/set: add MAX_FILE_BYTES size cap on SOUL.md to
prevent unbounded context window consumption.
- POST /api/personality/set: return personality:null (not '') when cleared.
- streaming.py: same MAX_FILE_BYTES guard before prepending to system msg.
Added tests/test_sprint28.py: 11 tests for API round-trip, listing,
symlink guard, path traversal rejection, clear, size cap, persistence.
Tests pass in isolation; full-suite run has a test-isolation interaction
with shared server state across sprint tests (tracked as follow-up).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 18:16:37 +00:00
|
|
|
else:
|
2026-04-06 21:10:30 +00:00
|
|
|
prompt = str(value)
|
feat: /personality slash command with backend integration (#143)
* feat: /personality slash command with backend integration
Add /personality command to switch the agent's system prompt personality.
Hermes CLI supports personalities stored at ~/.hermes/personalities/<name>/SOUL.md.
Backend:
- GET /api/personalities: lists available personalities from the active
profile's personalities directory (reads first line of SOUL.md for desc)
- POST /api/personality/set: sets active personality on the session, reads
and validates the SOUL.md file exists, returns the prompt text
- streaming.py: injects personality prompt (SOUL.md content) as prefix to
the system_message when run_conversation is called
Frontend (commands.js):
- /personality with no args: lists available personalities as a local message
- /personality <name>: sets the personality with a toast confirmation
- /personality none|default|clear: removes the active personality
Session model: new 'personality' field (backward-compatible, defaults to None)
Closes #139
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: path traversal in personality name + case sensitivity
Security: personality name is now validated with regex ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$
in both routes.py (POST /api/personality/set) and streaming.py (system
prompt injection). Defense-in-depth: resolve().relative_to() check ensures
the path stays inside the personalities directory even if regex is bypassed.
Also: removed toLowerCase() from frontend command handler so personality
names are case-preserved (filesystem may be case-sensitive).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: /personality command — hardened, compact() fix, tests
Fixes on top of original PR:
- compact() was missing 'personality' field — UI couldn't know active
personality after page load. Added to Session.compact().
- GET /api/personalities: add symlink guard (is_symlink() skip) and
resolve() check — prevents reading SOUL.md from symlink targets
outside personalities dir.
- POST /api/personality/set: require() only checks session_id (not name)
so clearing with name='' works correctly instead of 400.
- POST /api/personality/set: add MAX_FILE_BYTES size cap on SOUL.md to
prevent unbounded context window consumption.
- POST /api/personality/set: return personality:null (not '') when cleared.
- streaming.py: same MAX_FILE_BYTES guard before prepending to system msg.
Added tests/test_sprint28.py: 11 tests for API round-trip, listing,
symlink guard, path traversal rejection, clear, size cap, persistence.
Tests pass in isolation; full-suite run has a test-isolation interaction
with shared server state across sprint tests (tracked as follow-up).
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 18:16:37 +00:00
|
|
|
s.personality = name if name else None
|
|
|
|
|
s.save()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "personality": s.personality, "prompt": prompt})
|
|
|
|
|
|
|
|
|
|
if parsed.path == "/api/session/update":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "session_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
new_ws = str(Path(body.get("workspace", s.workspace)).expanduser().resolve())
|
|
|
|
|
s.workspace = new_ws
|
|
|
|
|
s.model = body.get("model", s.model)
|
|
|
|
|
s.save()
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
set_last_workspace(new_ws)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"session": s.compact() | {"messages": s.messages}})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/delete":
|
|
|
|
|
sid = body.get("session_id", "")
|
|
|
|
|
if not sid:
|
|
|
|
|
return bad(handler, "session_id is required")
|
2026-04-05 00:33:55 +00:00
|
|
|
# Delete from WebUI session store
|
2026-04-12 07:11:41 +00:00
|
|
|
with LOCK:
|
|
|
|
|
SESSIONS.pop(sid, None)
|
|
|
|
|
p = SESSION_DIR / f"{sid}.json"
|
|
|
|
|
try:
|
|
|
|
|
p.unlink(missing_ok=True)
|
|
|
|
|
except Exception:
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger.debug("Failed to unlink session file %s", p)
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
SESSION_INDEX_FILE.unlink(missing_ok=True)
|
|
|
|
|
except Exception:
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger.debug("Failed to unlink session index")
|
2026-04-05 00:33:55 +00:00
|
|
|
# Also delete from CLI state.db (for CLI sessions shown in sidebar)
|
|
|
|
|
try:
|
|
|
|
|
from api.models import delete_cli_session
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-05 00:33:55 +00:00
|
|
|
delete_cli_session(sid)
|
2026-04-12 07:11:41 +00:00
|
|
|
except Exception:
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger.debug("Failed to delete CLI session %s", sid)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True})
|
|
|
|
|
|
|
|
|
|
if parsed.path == "/api/session/clear":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "session_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
s.messages = []
|
|
|
|
|
s.tool_calls = []
|
|
|
|
|
s.title = "Untitled"
|
|
|
|
|
s.save()
|
|
|
|
|
return j(handler, {"ok": True, "session": s.compact()})
|
|
|
|
|
|
|
|
|
|
if parsed.path == "/api/session/truncate":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "session_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
if body.get("keep_count") is None:
|
|
|
|
|
return bad(handler, "Missing required field(s): keep_count")
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
keep = int(body["keep_count"])
|
|
|
|
|
s.messages = s.messages[:keep]
|
|
|
|
|
s.save()
|
|
|
|
|
return j(
|
|
|
|
|
handler, {"ok": True, "session": s.compact() | {"messages": s.messages}}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if parsed.path == "/api/chat/start":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_chat_start(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/chat":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_chat_sync(handler, body)
|
|
|
|
|
|
|
|
|
|
# ── Cron API (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/crons/create":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_cron_create(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/crons/update":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_cron_update(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/crons/delete":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_cron_delete(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/crons/run":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_cron_run(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/crons/pause":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_cron_pause(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/crons/resume":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_cron_resume(handler, body)
|
|
|
|
|
|
|
|
|
|
# ── File ops (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/file/delete":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_file_delete(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/file/save":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_file_save(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/file/create":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_file_create(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/file/rename":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_file_rename(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/file/create-dir":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_create_dir(handler, body)
|
|
|
|
|
|
|
|
|
|
# ── Workspace management (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/workspaces/add":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_workspace_add(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/workspaces/remove":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_workspace_remove(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/workspaces/rename":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_workspace_rename(handler, body)
|
|
|
|
|
|
|
|
|
|
# ── Approval (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/approval/respond":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_approval_respond(handler, body)
|
|
|
|
|
|
|
|
|
|
# ── Skills (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/skills/save":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_skill_save(handler, body)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/skills/delete":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_skill_delete(handler, body)
|
|
|
|
|
|
|
|
|
|
# ── Memory (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/memory/write":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_memory_write(handler, body)
|
|
|
|
|
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
# ── Profile API (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/profile/switch":
|
|
|
|
|
name = body.get("name", "").strip()
|
|
|
|
|
if not name:
|
|
|
|
|
return bad(handler, "name is required")
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
try:
|
|
|
|
|
from api.profiles import switch_profile
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
result = switch_profile(name)
|
|
|
|
|
return j(handler, result)
|
|
|
|
|
except (ValueError, FileNotFoundError) as e:
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
return bad(handler, _sanitize_error(e), 404)
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
except RuntimeError as e:
|
|
|
|
|
return bad(handler, str(e), 409)
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/profile/create":
|
|
|
|
|
name = body.get("name", "").strip()
|
|
|
|
|
if not name:
|
|
|
|
|
return bad(handler, "name is required")
|
2026-04-03 18:06:18 +00:00
|
|
|
import re as _re
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
if not _re.match(r"^[a-z0-9][a-z0-9_-]{0,63}$", name):
|
|
|
|
|
return bad(
|
|
|
|
|
handler,
|
|
|
|
|
"Invalid profile name: lowercase letters, numbers, hyphens, underscores only",
|
|
|
|
|
)
|
|
|
|
|
clone_from = body.get("clone_from")
|
2026-04-03 18:06:18 +00:00
|
|
|
if clone_from is not None:
|
|
|
|
|
clone_from = str(clone_from).strip()
|
2026-04-12 07:11:41 +00:00
|
|
|
if not _re.match(r"^[a-z0-9][a-z0-9_-]{0,63}$", clone_from):
|
|
|
|
|
return bad(handler, "Invalid clone_from name")
|
|
|
|
|
base_url = body.get("base_url", "").strip() if body.get("base_url") else None
|
|
|
|
|
api_key = body.get("api_key", "").strip() if body.get("api_key") else None
|
|
|
|
|
if base_url and not base_url.startswith(("http://", "https://")):
|
|
|
|
|
return bad(handler, "base_url must start with http:// or https://")
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
try:
|
|
|
|
|
from api.profiles import create_profile_api
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
result = create_profile_api(
|
|
|
|
|
name,
|
2026-04-03 18:06:18 +00:00
|
|
|
clone_from=clone_from,
|
2026-04-12 07:11:41 +00:00
|
|
|
clone_config=bool(body.get("clone_config", False)),
|
2026-04-10 18:43:49 +00:00
|
|
|
base_url=base_url,
|
|
|
|
|
api_key=api_key,
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "profile": result})
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
except (ValueError, FileExistsError, RuntimeError) as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/profile/delete":
|
|
|
|
|
name = body.get("name", "").strip()
|
|
|
|
|
if not name:
|
|
|
|
|
return bad(handler, "name is required")
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
try:
|
|
|
|
|
from api.profiles import delete_profile_api
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
result = delete_profile_api(name)
|
|
|
|
|
return j(handler, result)
|
|
|
|
|
except (ValueError, FileNotFoundError) as e:
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
return bad(handler, _sanitize_error(e))
|
2026-04-03 18:06:18 +00:00
|
|
|
except RuntimeError as e:
|
|
|
|
|
return bad(handler, str(e), 409)
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
# ── Settings (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/settings":
|
|
|
|
|
if "bot_name" in body:
|
|
|
|
|
body["bot_name"] = (str(body["bot_name"]) or "").strip() or "Hermes"
|
2026-04-03 13:17:32 +00:00
|
|
|
saved = save_settings(body)
|
2026-04-12 07:11:41 +00:00
|
|
|
saved.pop("password_hash", None) # never expose hash to client
|
2026-04-03 13:17:32 +00:00
|
|
|
return j(handler, saved)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/onboarding/setup":
|
2026-04-12 23:35:47 +00:00
|
|
|
# Writing API keys to disk - restrict to local/private networks unless auth is active.
|
|
|
|
|
# In Docker, requests arrive from the bridge network (172.x.x.x), not 127.0.0.1,
|
|
|
|
|
# even when the user accesses via localhost:8787 on the host.
|
2026-04-12 07:11:41 +00:00
|
|
|
from api.auth import is_auth_enabled
|
2026-04-12 23:35:47 +00:00
|
|
|
if not is_auth_enabled():
|
|
|
|
|
import ipaddress
|
|
|
|
|
try:
|
|
|
|
|
addr = ipaddress.ip_address(handler.client_address[0])
|
|
|
|
|
is_local = addr.is_loopback or addr.is_private
|
|
|
|
|
except ValueError:
|
|
|
|
|
is_local = False
|
|
|
|
|
if not is_local:
|
|
|
|
|
return bad(handler, "Onboarding setup is only available from local networks when auth is not enabled.", 403)
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
return j(handler, apply_onboarding_setup(body))
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
except RuntimeError as e:
|
|
|
|
|
return bad(handler, str(e), 500)
|
|
|
|
|
|
|
|
|
|
if parsed.path == "/api/onboarding/complete":
|
|
|
|
|
return j(handler, complete_onboarding())
|
|
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
# ── Session pin (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/pin":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "session_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
s.pinned = bool(body.get("pinned", True))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
s.save()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "session": s.compact()})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
# ── Session archive (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/archive":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "session_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
s.archived = bool(body.get("archived", True))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
s.save()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "session": s.compact()})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
2026-04-02 06:55:21 +00:00
|
|
|
# ── Session move to project (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/move":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "session_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
s.project_id = body.get("project_id") or None
|
2026-04-02 06:55:21 +00:00
|
|
|
s.save()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "session": s.compact()})
|
2026-04-02 06:55:21 +00:00
|
|
|
|
|
|
|
|
# ── Project CRUD (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/projects/create":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "name")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
2026-04-02 08:01:58 +00:00
|
|
|
import re as _re
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
name = body["name"].strip()[:128]
|
|
|
|
|
if not name:
|
|
|
|
|
return bad(handler, "name required")
|
|
|
|
|
color = body.get("color")
|
|
|
|
|
if color and not _re.match(r"^#[0-9a-fA-F]{3,8}$", color):
|
|
|
|
|
return bad(handler, "Invalid color format")
|
2026-04-02 06:55:21 +00:00
|
|
|
projects = load_projects()
|
2026-04-12 07:11:41 +00:00
|
|
|
proj = {
|
|
|
|
|
"project_id": uuid.uuid4().hex[:12],
|
|
|
|
|
"name": name,
|
|
|
|
|
"color": color,
|
|
|
|
|
"created_at": time.time(),
|
|
|
|
|
}
|
2026-04-02 06:55:21 +00:00
|
|
|
projects.append(proj)
|
|
|
|
|
save_projects(projects)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "project": proj})
|
2026-04-02 06:55:21 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/projects/rename":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "project_id", "name")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
2026-04-02 08:01:58 +00:00
|
|
|
import re as _re
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-02 06:55:21 +00:00
|
|
|
projects = load_projects()
|
2026-04-12 07:11:41 +00:00
|
|
|
proj = next(
|
|
|
|
|
(p for p in projects if p["project_id"] == body["project_id"]), None
|
|
|
|
|
)
|
|
|
|
|
if not proj:
|
|
|
|
|
return bad(handler, "Project not found", 404)
|
|
|
|
|
proj["name"] = body["name"].strip()[:128]
|
|
|
|
|
if "color" in body:
|
|
|
|
|
color = body["color"]
|
|
|
|
|
if color and not _re.match(r"^#[0-9a-fA-F]{3,8}$", color):
|
|
|
|
|
return bad(handler, "Invalid color format")
|
|
|
|
|
proj["color"] = color
|
2026-04-02 06:55:21 +00:00
|
|
|
save_projects(projects)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "project": proj})
|
2026-04-02 06:55:21 +00:00
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/projects/delete":
|
|
|
|
|
try:
|
|
|
|
|
require(body, "project_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
2026-04-02 06:55:21 +00:00
|
|
|
projects = load_projects()
|
2026-04-12 07:11:41 +00:00
|
|
|
proj = next(
|
|
|
|
|
(p for p in projects if p["project_id"] == body["project_id"]), None
|
|
|
|
|
)
|
|
|
|
|
if not proj:
|
|
|
|
|
return bad(handler, "Project not found", 404)
|
|
|
|
|
projects = [p for p in projects if p["project_id"] != body["project_id"]]
|
2026-04-02 06:55:21 +00:00
|
|
|
save_projects(projects)
|
|
|
|
|
# Unassign all sessions that belonged to this project
|
|
|
|
|
if SESSION_INDEX_FILE.exists():
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
index = json.loads(SESSION_INDEX_FILE.read_text(encoding="utf-8"))
|
2026-04-02 06:55:21 +00:00
|
|
|
for entry in index:
|
2026-04-12 07:11:41 +00:00
|
|
|
if entry.get("project_id") == body["project_id"]:
|
2026-04-02 06:55:21 +00:00
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
s = get_session(entry["session_id"])
|
2026-04-02 06:55:21 +00:00
|
|
|
s.project_id = None
|
|
|
|
|
s.save()
|
|
|
|
|
except Exception:
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger.debug("Failed to update session %s", entry.get("session_id"))
|
2026-04-02 06:55:21 +00:00
|
|
|
except Exception:
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger.debug("Failed to load session index for project unlink")
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True})
|
2026-04-02 06:55:21 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
# ── Session import from JSON (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/import":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return _handle_session_import(handler, body)
|
|
|
|
|
|
feat: self-update checker with one-click update for WebUI + Agent
Shows a blue banner when the webui or hermes-agent git repos are behind
their upstream branches. One-click 'Update Now' button does stash, pull
--ff-only, stash pop, then reloads the page.
Backend (api/updates.py):
- _check_repo(): git fetch + rev-list count with 15s timeout
- check_for_updates(): 30-min server-side cache, thread-safe, skips
Docker (no .git dir)
- apply_update(): stash (if dirty), pull --ff-only, pop, invalidate cache
Routes:
- GET /api/updates/check -- returns cached {webui, agent} with behind count
- POST /api/updates/apply -- {target: 'webui'|'agent'}
Frontend:
- Blue banner (matches reconnect-banner pattern) with 'Later' / 'Update Now'
- Non-blocking boot check via fire-and-forget .then(), once per tab session
- sessionStorage guards prevent re-checking and re-showing after dismiss
Settings:
- 'Check for updates' checkbox (default: on) -- when off, no git operations
- Removed 'Default Workspace' dropdown to keep settings panel compact
Performance:
- Server cache: git fetch at most 2x/hour regardless of client count
- sessionStorage: one check per browser tab session
- _check_in_progress flag prevents concurrent fetch storms
- Fire-and-forget: does NOT block the boot sequence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:11:44 +00:00
|
|
|
# ── Self-update (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/updates/apply":
|
|
|
|
|
target = body.get("target", "")
|
|
|
|
|
if target not in ("webui", "agent"):
|
feat: self-update checker with one-click update for WebUI + Agent
Shows a blue banner when the webui or hermes-agent git repos are behind
their upstream branches. One-click 'Update Now' button does stash, pull
--ff-only, stash pop, then reloads the page.
Backend (api/updates.py):
- _check_repo(): git fetch + rev-list count with 15s timeout
- check_for_updates(): 30-min server-side cache, thread-safe, skips
Docker (no .git dir)
- apply_update(): stash (if dirty), pull --ff-only, pop, invalidate cache
Routes:
- GET /api/updates/check -- returns cached {webui, agent} with behind count
- POST /api/updates/apply -- {target: 'webui'|'agent'}
Frontend:
- Blue banner (matches reconnect-banner pattern) with 'Later' / 'Update Now'
- Non-blocking boot check via fire-and-forget .then(), once per tab session
- sessionStorage guards prevent re-checking and re-showing after dismiss
Settings:
- 'Check for updates' checkbox (default: on) -- when off, no git operations
- Removed 'Default Workspace' dropdown to keep settings panel compact
Performance:
- Server cache: git fetch at most 2x/hour regardless of client count
- sessionStorage: one check per browser tab session
- _check_in_progress flag prevents concurrent fetch storms
- Fire-and-forget: does NOT block the boot sequence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:11:44 +00:00
|
|
|
return bad(handler, 'target must be "webui" or "agent"')
|
|
|
|
|
from api.updates import apply_update
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: self-update checker with one-click update for WebUI + Agent
Shows a blue banner when the webui or hermes-agent git repos are behind
their upstream branches. One-click 'Update Now' button does stash, pull
--ff-only, stash pop, then reloads the page.
Backend (api/updates.py):
- _check_repo(): git fetch + rev-list count with 15s timeout
- check_for_updates(): 30-min server-side cache, thread-safe, skips
Docker (no .git dir)
- apply_update(): stash (if dirty), pull --ff-only, pop, invalidate cache
Routes:
- GET /api/updates/check -- returns cached {webui, agent} with behind count
- POST /api/updates/apply -- {target: 'webui'|'agent'}
Frontend:
- Blue banner (matches reconnect-banner pattern) with 'Later' / 'Update Now'
- Non-blocking boot check via fire-and-forget .then(), once per tab session
- sessionStorage guards prevent re-checking and re-showing after dismiss
Settings:
- 'Check for updates' checkbox (default: on) -- when off, no git operations
- Removed 'Default Workspace' dropdown to keep settings panel compact
Performance:
- Server cache: git fetch at most 2x/hour regardless of client count
- sessionStorage: one check per browser tab session
- _check_in_progress flag prevents concurrent fetch storms
- Fire-and-forget: does NOT block the boot sequence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:11:44 +00:00
|
|
|
return j(handler, apply_update(target))
|
|
|
|
|
|
2026-04-04 02:54:54 +00:00
|
|
|
# ── CLI session import (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/session/import_cli":
|
2026-04-04 02:54:54 +00:00
|
|
|
return _handle_session_import_cli(handler, body)
|
|
|
|
|
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
# ── Auth endpoints (POST) ──
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/auth/login":
|
|
|
|
|
from api.auth import (
|
|
|
|
|
verify_password,
|
|
|
|
|
create_session,
|
|
|
|
|
set_auth_cookie,
|
|
|
|
|
is_auth_enabled,
|
|
|
|
|
)
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
from api.auth import _check_login_rate, _record_login_attempt
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
if not is_auth_enabled():
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "message": "Auth not enabled"})
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
client_ip = handler.client_address[0]
|
|
|
|
|
if not _check_login_rate(client_ip):
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{"error": "Too many attempts. Try again in a minute."},
|
|
|
|
|
status=429,
|
|
|
|
|
)
|
|
|
|
|
password = body.get("password", "")
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
if not verify_password(password):
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
_record_login_attempt(client_ip)
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Invalid password", 401)
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
cookie_val = create_session()
|
|
|
|
|
handler.send_response(200)
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header("Content-Type", "application/json")
|
|
|
|
|
handler.send_header("Cache-Control", "no-store")
|
2026-04-03 13:05:41 +00:00
|
|
|
_security_headers(handler)
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
set_auth_cookie(handler, cookie_val)
|
|
|
|
|
handler.end_headers()
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.wfile.write(json.dumps({"ok": True}).encode())
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
return True
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
if parsed.path == "/api/auth/logout":
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
from api.auth import clear_auth_cookie, invalidate_session, parse_cookie
|
2026-04-12 07:11:41 +00:00
|
|
|
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
cookie_val = parse_cookie(handler)
|
|
|
|
|
if cookie_val:
|
|
|
|
|
invalidate_session(cookie_val)
|
|
|
|
|
handler.send_response(200)
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header("Content-Type", "application/json")
|
|
|
|
|
handler.send_header("Cache-Control", "no-store")
|
2026-04-03 13:05:41 +00:00
|
|
|
_security_headers(handler)
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
clear_auth_cookie(handler)
|
|
|
|
|
handler.end_headers()
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.wfile.write(json.dumps({"ok": True}).encode())
|
feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost):
- New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt),
signed HMAC session cookies (24h TTL), auth middleware
- Enable via HERMES_WEBUI_PASSWORD env var or Settings panel
- Minimal dark-themed login page at /login (self-contained HTML)
- POST /api/auth/login, /api/auth/logout, GET /api/auth/status
- Settings panel: "Access Password" field + "Sign Out" button
- password_hash added to settings.json (null = auth disabled)
Security hardening:
- Security headers on all responses: X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin
- POST body size limit: 20MB cap in read_body() to prevent DoS
Closes #23. 9 new tests. Total: 304 passed, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:53:26 +00:00
|
|
|
return True
|
|
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return False # 404
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── GET route helpers ─────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
def _serve_static(handler, parsed):
|
2026-04-12 07:11:41 +00:00
|
|
|
static_root = (Path(__file__).parent.parent / "static").resolve()
|
2026-04-02 06:46:40 +00:00
|
|
|
# Strip the leading '/static/' prefix, then resolve and sandbox
|
2026-04-12 07:11:41 +00:00
|
|
|
rel = parsed.path[len("/static/") :]
|
2026-04-02 06:39:18 +00:00
|
|
|
static_file = (static_root / rel).resolve()
|
|
|
|
|
try:
|
|
|
|
|
static_file.relative_to(static_root)
|
|
|
|
|
except ValueError:
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"error": "not found"}, status=404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if not static_file.exists() or not static_file.is_file():
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"error": "not found"}, status=404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
ext = static_file.suffix.lower()
|
2026-04-12 07:11:41 +00:00
|
|
|
ct = {"css": "text/css", "js": "application/javascript", "html": "text/html"}.get(
|
|
|
|
|
ext.lstrip("."), "text/plain"
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.send_response(200)
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header("Content-Type", f"{ct}; charset=utf-8")
|
|
|
|
|
handler.send_header("Cache-Control", "no-store")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
raw = static_file.read_bytes()
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header("Content-Length", str(len(raw)))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.end_headers()
|
|
|
|
|
handler.wfile.write(raw)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_session_export(handler, parsed):
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = parse_qs(parsed.query).get("session_id", [""])[0]
|
|
|
|
|
if not sid:
|
|
|
|
|
return bad(handler, "session_id is required")
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(sid)
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
v0.46.0: security, Docker UID/GID, model discovery, i18n, cancel fix
* 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>
2026-04-11 17:17:52 +00:00
|
|
|
safe = redact_session_data(s.__dict__)
|
|
|
|
|
payload = json.dumps(safe, ensure_ascii=False, indent=2)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.send_response(200)
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header("Content-Type", "application/json; charset=utf-8")
|
|
|
|
|
handler.send_header(
|
|
|
|
|
"Content-Disposition", f'attachment; filename="hermes-{sid}.json"'
|
|
|
|
|
)
|
|
|
|
|
handler.send_header("Content-Length", str(len(payload.encode("utf-8"))))
|
|
|
|
|
handler.send_header("Cache-Control", "no-store")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.end_headers()
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.wfile.write(payload.encode("utf-8"))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_sessions_search(handler, parsed):
|
|
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
q = qs.get("q", [""])[0].lower().strip()
|
|
|
|
|
content_search = qs.get("content", ["1"])[0] == "1"
|
|
|
|
|
depth = int(qs.get("depth", ["5"])[0])
|
|
|
|
|
if not q:
|
|
|
|
|
return j(handler, {"sessions": all_sessions()})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
results = []
|
|
|
|
|
for s in all_sessions():
|
2026-04-12 07:11:41 +00:00
|
|
|
title_match = q in (s.get("title") or "").lower()
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if title_match:
|
2026-04-12 07:11:41 +00:00
|
|
|
results.append(dict(s, match_type="title"))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
continue
|
|
|
|
|
if content_search:
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
sess = get_session(s["session_id"])
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
msgs = sess.messages[:depth] if depth else sess.messages
|
|
|
|
|
for m in msgs:
|
2026-04-12 07:11:41 +00:00
|
|
|
c = m.get("content") or ""
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if isinstance(c, list):
|
2026-04-12 07:11:41 +00:00
|
|
|
c = " ".join(
|
|
|
|
|
p.get("text", "")
|
|
|
|
|
for p in c
|
|
|
|
|
if isinstance(p, dict) and p.get("type") == "text"
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if q in str(c).lower():
|
2026-04-12 07:11:41 +00:00
|
|
|
results.append(dict(s, match_type="content"))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
break
|
|
|
|
|
except (KeyError, Exception):
|
|
|
|
|
pass
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"sessions": results, "query": q, "count": len(results)})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_list_dir(handler, parsed):
|
|
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = qs.get("session_id", [""])[0]
|
|
|
|
|
if not sid:
|
|
|
|
|
return bad(handler, "session_id is required")
|
2026-04-10 01:18:38 +00:00
|
|
|
try:
|
|
|
|
|
s = get_session(sid)
|
|
|
|
|
workspace = s.workspace
|
|
|
|
|
except KeyError:
|
|
|
|
|
# Fallback for CLI sessions not loaded in WebUI memory
|
|
|
|
|
try:
|
|
|
|
|
cli_meta = None
|
|
|
|
|
for cs in get_cli_sessions():
|
2026-04-12 07:11:41 +00:00
|
|
|
if cs["session_id"] == sid:
|
2026-04-10 01:18:38 +00:00
|
|
|
cli_meta = cs
|
|
|
|
|
break
|
|
|
|
|
if not cli_meta:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
workspace = cli_meta.get("workspace", "")
|
2026-04-10 01:18:38 +00:00
|
|
|
except Exception:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Session not found", 404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{
|
|
|
|
|
"entries": list_dir(Path(workspace), qs.get("path", ["."])[0]),
|
|
|
|
|
"path": qs.get("path", ["."])[0],
|
|
|
|
|
},
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
except (FileNotFoundError, ValueError) as e:
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
return bad(handler, _sanitize_error(e), 404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_sse_stream(handler, parsed):
|
2026-04-12 07:11:41 +00:00
|
|
|
stream_id = parse_qs(parsed.query).get("stream_id", [""])[0]
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
q = STREAMS.get(stream_id)
|
2026-04-12 07:11:41 +00:00
|
|
|
if q is None:
|
|
|
|
|
return j(handler, {"error": "stream not found"}, status=404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.send_response(200)
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header("Content-Type", "text/event-stream; charset=utf-8")
|
|
|
|
|
handler.send_header("Cache-Control", "no-cache")
|
|
|
|
|
handler.send_header("X-Accel-Buffering", "no")
|
|
|
|
|
handler.send_header("Connection", "keep-alive")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.end_headers()
|
|
|
|
|
try:
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
event, data = q.get(timeout=30)
|
|
|
|
|
except queue.Empty:
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.wfile.write(b": heartbeat\n\n")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.wfile.flush()
|
|
|
|
|
continue
|
|
|
|
|
_sse(handler, event, data)
|
2026-04-12 07:11:41 +00:00
|
|
|
if event in ("done", "error", "cancel"):
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
break
|
|
|
|
|
except (BrokenPipeError, ConnectionResetError):
|
|
|
|
|
pass
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
feat: real-time gateway session sync (Phase 1) (#274)
* feat: add real-time gateway session sync (Phase 1)
- Add gateway_watcher.py: background daemon polling state.db every 5s
for gateway session changes (telegram, discord, slack, etc.)
- Extend get_cli_sessions() to include all non-webui sources
- Add SSE endpoint /api/sessions/gateway/stream for real-time push
- Add dynamic source badges (telegram=blue, discord=purple, slack=dark purple)
- Rename 'Show CLI sessions' to 'Show agent sessions'
- Wire watcher lifecycle into server start/stop
- 10 tests covering metadata, filtering, SSE, and watcher lifecycle
- Activated via the same checkbox as CLI session import
Addresses GitHub issue #272
* fix: SSE event name mismatch, TLS attribute, remove PLAN.md
- Fix critical SSE bug: frontend listened for 'gateway_session_update'
but backend sends 'sessions_changed' -- events were silently dropped
- Fix frontend field check: data.changed -> data.sessions (matches
the actual payload structure from gateway_watcher)
- Fix TLS: ssl.TLSv1_2 -> ssl.TLSVersion.TLSv1_2 (the bare attribute
does not exist, would crash TLS setup and silently fall back to HTTP)
- Remove PLAN.md: implementation plan should not be committed to repo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: test isolation and slow-consumer sentinel in gateway sync
tests/test_gateway_sync.py:
- Fix _get_test_state_dir() path mismatch: the function was computing
HERMES_HOME/webui-mvp-test but conftest.py sets HERMES_HOME=TEST_STATE_DIR,
so state.db was written to a double-nested path the server never read.
Now uses HERMES_WEBUI_STATE_DIR first (which conftest sets directly to
TEST_STATE_DIR), fixing the 7/10 test failures in full-suite ordering.
- Fix conn cleanup: removed conn.close() from inside try blocks so the
connection stays valid for _remove_test_sessions() in the finally block.
Previously the closed conn caused ProgrammingError in finally (swallowed
by bare except), leaving ghost sessions in state.db on test failure.
api/gateway_watcher.py:
- Fix slow-consumer queue eviction: when a subscriber queue fills (>10 events)
and is removed from _subscribers, now puts a None sentinel into it so the
SSE handler unblocks and closes the connection, letting EventSource
auto-reconnect. Without this the connection stayed open but received no
further events.
* fix: test isolation — set HERMES_WEBUI_TEST_STATE_DIR in conftest
The gateway sync tests write directly to state.db and must use the same
path the test server reads from. Previously they computed the path
independently, which broke when test_auth_sessions.py set a different
HERMES_WEBUI_STATE_DIR in the test-process environment at import time.
tests/conftest.py:
- Set HERMES_WEBUI_TEST_STATE_DIR=TEST_STATE_DIR in the test process's
os.environ (via setdefault) so gateway tests can read it reliably.
Using setdefault preserves any explicit override the caller may pass.
tests/test_gateway_sync.py:
- Simplify _get_test_state_dir(): check HERMES_WEBUI_TEST_STATE_DIR first
(now reliably set by conftest), fall back to HERMES_HOME/webui-mvp-test.
Remove the workaround that tried to snapshot HERMES_HOME at import time.
Result: 658/658 tests pass in full-suite ordering (was 651 pass / 7 fail).
---------
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 03:53:12 +00:00
|
|
|
def _handle_gateway_sse_stream(handler):
|
|
|
|
|
"""SSE endpoint for real-time gateway session updates.
|
|
|
|
|
Streams change events from the gateway watcher background thread.
|
|
|
|
|
Only active when show_cli_sessions (show_agent_sessions) setting is enabled.
|
|
|
|
|
"""
|
|
|
|
|
# Check if the feature is enabled
|
|
|
|
|
settings = load_settings()
|
|
|
|
|
if not settings.get('show_cli_sessions'):
|
|
|
|
|
return j(handler, {'error': 'agent sessions not enabled'}, status=404)
|
|
|
|
|
|
|
|
|
|
from api.gateway_watcher import get_watcher
|
|
|
|
|
watcher = get_watcher()
|
|
|
|
|
if watcher is None:
|
|
|
|
|
return j(handler, {'error': 'watcher not started'}, status=503)
|
|
|
|
|
|
|
|
|
|
handler.send_response(200)
|
|
|
|
|
handler.send_header('Content-Type', 'text/event-stream; charset=utf-8')
|
|
|
|
|
handler.send_header('Cache-Control', 'no-cache')
|
|
|
|
|
handler.send_header('X-Accel-Buffering', 'no')
|
|
|
|
|
handler.send_header('Connection', 'keep-alive')
|
|
|
|
|
handler.end_headers()
|
|
|
|
|
|
|
|
|
|
q = watcher.subscribe()
|
|
|
|
|
try:
|
|
|
|
|
# Send initial snapshot immediately
|
|
|
|
|
from api.models import get_cli_sessions
|
|
|
|
|
initial = get_cli_sessions()
|
|
|
|
|
_sse(handler, 'sessions_changed', {'sessions': initial})
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
event_data = q.get(timeout=30)
|
|
|
|
|
except queue.Empty:
|
|
|
|
|
handler.wfile.write(b': keepalive\n\n')
|
|
|
|
|
handler.wfile.flush()
|
|
|
|
|
continue
|
|
|
|
|
if event_data is None:
|
|
|
|
|
break # watcher is stopping
|
|
|
|
|
_sse(handler, event_data.get('type', 'sessions_changed'), event_data)
|
|
|
|
|
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
|
|
|
|
|
pass
|
|
|
|
|
finally:
|
|
|
|
|
watcher.unsubscribe(q)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
def _handle_file_raw(handler, parsed):
|
|
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = qs.get("session_id", [""])[0]
|
|
|
|
|
if not sid:
|
|
|
|
|
return bad(handler, "session_id is required")
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(sid)
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
rel = qs.get("path", [""])[0]
|
|
|
|
|
force_download = qs.get("download", [""])[0] == "1"
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
target = safe_resolve(Path(s.workspace), rel)
|
|
|
|
|
if not target.exists() or not target.is_file():
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"error": "not found"}, status=404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
ext = target.suffix.lower()
|
2026-04-12 07:11:41 +00:00
|
|
|
mime = MIME_MAP.get(ext, "application/octet-stream")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
raw_bytes = target.read_bytes()
|
|
|
|
|
import urllib.parse as _up
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
safe_name = _up.quote(target.name, safe="")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.send_response(200)
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header("Content-Type", mime)
|
|
|
|
|
handler.send_header("Content-Length", str(len(raw_bytes)))
|
|
|
|
|
handler.send_header("Cache-Control", "no-store")
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
# Security: force download for dangerous MIME types to prevent XSS
|
2026-04-12 07:11:41 +00:00
|
|
|
dangerous_types = {"text/html", "application/xhtml+xml", "image/svg+xml"}
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
if force_download or mime in dangerous_types:
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header(
|
|
|
|
|
"Content-Disposition",
|
|
|
|
|
f"attachment; filename=\"{target.name}\"; filename*=UTF-8''{safe_name}",
|
|
|
|
|
)
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
else:
|
2026-04-12 07:11:41 +00:00
|
|
|
handler.send_header(
|
|
|
|
|
"Content-Disposition",
|
|
|
|
|
f"inline; filename=\"{target.name}\"; filename*=UTF-8''{safe_name}",
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
handler.end_headers()
|
|
|
|
|
handler.wfile.write(raw_bytes)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_file_read(handler, parsed):
|
|
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = qs.get("session_id", [""])[0]
|
|
|
|
|
if not sid:
|
|
|
|
|
return bad(handler, "session_id is required")
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(sid)
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
rel = qs.get("path", [""])[0]
|
|
|
|
|
if not rel:
|
|
|
|
|
return bad(handler, "path is required")
|
|
|
|
|
try:
|
|
|
|
|
return j(handler, read_file_content(Path(s.workspace), rel))
|
|
|
|
|
except (FileNotFoundError, ValueError) as e:
|
|
|
|
|
return bad(handler, _sanitize_error(e), 404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_approval_pending(handler, parsed):
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = parse_qs(parsed.query).get("session_id", [""])[0]
|
v0.44.0: approval fix, login CSP, update diagnostics, Lucide icons
* fix: approval pending check broken by stale has_pending import (#228)
api/routes.py imported has_pending/pop_pending from tools.approval, but the
agent module renamed has_pending to has_blocking_approval (checks gateway
queue, not _pending dict) and removed pop_pending. The import fell through
to fallback lambdas that always returned False, making GET /api/approval/pending
always return {pending:null} even after a successful inject_test.
Fix: check _pending directly under _lock — same dict submit_pending writes to.
Stale imports removed.
Before: 554 pass, 1 fail | After: 555 pass, 0 fail
* fix: move login JS into external file, remove inline handlers (#226)
Login page used inline onsubmit/onkeydown handlers and an inline <script>
block — all blocked by strict script-src CSP, causing silent login failure.
Fix: extract doLogin() and Enter key listener into static/login.js (served
from /static/, already a public path). Form uses id='login-form' and
data-* attributes for i18n strings instead of injected JS literals.
Also guards res.json() parse with try/catch so non-JSON error bodies
(e.g. HTTP 500) show the password-error fallback instead of 'Connection failed'.
Fixes #222.
* fix: improve update error messages when pull fails (#227)
_apply_update_inner() ran git pull --ff-only and returned only raw stderr
on failure, making all failure modes indistinguishable.
Fix: explicit git fetch before pull; if fetch fails, returns human-readable
network error. Diverged history and missing upstream tracking branch each
get distinct messages with exact recovery commands. Generic fallback
truncates to 300 chars and shows sentinel when git produces no output.
Also adds tests/test_update_checker.py with 13 tests covering all 4 new
diagnostic code paths (0 tests existed before).
Fixes #223.
* fix: stabilize 30s terminal approval prompt visibility (#225)
Adds minimum 30-second visibility guard for the approval card using
_approvalVisibleSince, _approvalHideTimer, and a signature fingerprint
to deduplicate repeated poll ticks.
Fix: respondApproval() and all stream-end paths (done/cancel/apperror/
error/start-error) now call hideApprovalCard(true) so the card hides
immediately when the user responds or the session ends. The 30s guard
only applies to mid-session poll ticks where the approval is still live
but briefly absent.
Adds 11 structural tests covering the new timer variables, force
parameter, force-on-respond, force-on-stream-end, and poll-loop
no-force behavior.
* feat: replace emoji icons with self-hosted Lucide SVG icons (#221)
Replaces all sidebar/button emoji icons with SVG paths from Lucide bundled
in static/icons.js (no CDN dependency). Adds li(name) function returning
inline SVG geometry from a hardcoded whitelist — unknown keys return '' so
dynamic server-supplied names never inject arbitrary SVG.
Changes:
- static/icons.js: new file with 21 icon paths + li() renderer
- static/index.html: all nav/action buttons now use li() icons
- static/ui.js: toolIcon(), fileIcon() use li() for tool/file icons
- static/messages.js: cancelStream button uses SVG square stop icon
- .gitignore: adds node_modules/ entry
Verified: all 35 onclick= functions exist in JS, all 21 li() calls
reference defined icons, applyBotName() selectors intact, version
label present, no removed IDs referenced by JS.
* docs: v0.44.0 release notes, bump version, update test counts
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-10 17:02:28 +00:00
|
|
|
with _lock:
|
|
|
|
|
p = _pending.get(sid)
|
|
|
|
|
if p:
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"pending": dict(p)})
|
|
|
|
|
return j(handler, {"pending": None})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_approval_inject(handler, parsed):
|
2026-04-02 06:46:40 +00:00
|
|
|
"""Inject a fake pending approval -- loopback-only, used by automated tests."""
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = qs.get("session_id", [""])[0]
|
|
|
|
|
key = qs.get("pattern_key", ["test_pattern"])[0]
|
|
|
|
|
cmd = qs.get("command", ["rm -rf /tmp/test"])[0]
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if sid:
|
2026-04-12 07:11:41 +00:00
|
|
|
submit_pending(
|
|
|
|
|
sid,
|
|
|
|
|
{
|
|
|
|
|
"command": cmd,
|
|
|
|
|
"pattern_key": key,
|
|
|
|
|
"pattern_keys": [key],
|
|
|
|
|
"description": "test pattern",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
return j(handler, {"ok": True, "session_id": sid})
|
|
|
|
|
return j(handler, {"error": "session_id required"}, status=400)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_cron_output(handler, parsed):
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import OUTPUT_DIR as CRON_OUT
|
2026-04-12 07:11:41 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
job_id = qs.get("job_id", [""])[0]
|
|
|
|
|
limit = int(qs.get("limit", ["5"])[0])
|
|
|
|
|
if not job_id:
|
|
|
|
|
return j(handler, {"error": "job_id required"}, status=400)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
out_dir = CRON_OUT / job_id
|
|
|
|
|
outputs = []
|
|
|
|
|
if out_dir.exists():
|
2026-04-12 07:11:41 +00:00
|
|
|
files = sorted(out_dir.glob("*.md"), reverse=True)[:limit]
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
for f in files:
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
txt = f.read_text(encoding="utf-8", errors="replace")
|
|
|
|
|
outputs.append({"filename": f.name, "content": txt[:8000]})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
except Exception:
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger.debug("Failed to read cron output file %s", f)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"job_id": job_id, "outputs": outputs})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_cron_recent(handler, parsed):
|
|
|
|
|
"""Return cron jobs that have completed since a given timestamp."""
|
|
|
|
|
import datetime
|
2026-04-12 07:11:41 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
qs = parse_qs(parsed.query)
|
2026-04-12 07:11:41 +00:00
|
|
|
since = float(qs.get("since", ["0"])[0])
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
try:
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import list_jobs
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-02 07:05:09 +00:00
|
|
|
jobs = list_jobs(include_disabled=True)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
completions = []
|
|
|
|
|
for job in jobs:
|
2026-04-12 07:11:41 +00:00
|
|
|
last_run = job.get("last_run_at")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if not last_run:
|
|
|
|
|
continue
|
|
|
|
|
if isinstance(last_run, str):
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
ts = datetime.datetime.fromisoformat(
|
|
|
|
|
last_run.replace("Z", "+00:00")
|
|
|
|
|
).timestamp()
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
continue
|
|
|
|
|
else:
|
|
|
|
|
ts = float(last_run)
|
|
|
|
|
if ts > since:
|
2026-04-12 07:11:41 +00:00
|
|
|
completions.append(
|
|
|
|
|
{
|
|
|
|
|
"job_id": job.get("id", ""),
|
|
|
|
|
"name": job.get("name", "Unknown"),
|
|
|
|
|
"status": job.get("last_status", "unknown"),
|
|
|
|
|
"completed_at": ts,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
return j(handler, {"completions": completions, "since": since})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
except ImportError:
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"completions": [], "since": since})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_memory_read(handler):
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
try:
|
|
|
|
|
from api.profiles import get_active_hermes_home
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
mem_dir = get_active_hermes_home() / "memories"
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
except ImportError:
|
2026-04-12 07:11:41 +00:00
|
|
|
mem_dir = Path.home() / ".hermes" / "memories"
|
|
|
|
|
mem_file = mem_dir / "MEMORY.md"
|
|
|
|
|
user_file = mem_dir / "USER.md"
|
|
|
|
|
memory = (
|
|
|
|
|
mem_file.read_text(encoding="utf-8", errors="replace")
|
|
|
|
|
if mem_file.exists()
|
|
|
|
|
else ""
|
|
|
|
|
)
|
|
|
|
|
user = (
|
|
|
|
|
user_file.read_text(encoding="utf-8", errors="replace")
|
|
|
|
|
if user_file.exists()
|
|
|
|
|
else ""
|
|
|
|
|
)
|
|
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{
|
|
|
|
|
"memory": _redact_text(memory),
|
|
|
|
|
"user": _redact_text(user),
|
|
|
|
|
"memory_path": str(mem_file),
|
|
|
|
|
"user_path": str(user_file),
|
|
|
|
|
"memory_mtime": mem_file.stat().st_mtime if mem_file.exists() else None,
|
|
|
|
|
"user_mtime": user_file.stat().st_mtime if user_file.exists() else None,
|
|
|
|
|
},
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── POST route helpers ────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
def _handle_sessions_cleanup(handler, body, zero_only=False):
|
|
|
|
|
cleaned = 0
|
2026-04-12 07:11:41 +00:00
|
|
|
for p in SESSION_DIR.glob("*.json"):
|
|
|
|
|
if p.name.startswith("_"):
|
|
|
|
|
continue
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
try:
|
|
|
|
|
s = Session.load(p.stem)
|
|
|
|
|
if zero_only:
|
|
|
|
|
should_delete = s and len(s.messages) == 0
|
|
|
|
|
else:
|
2026-04-12 07:11:41 +00:00
|
|
|
should_delete = s and s.title == "Untitled" and len(s.messages) == 0
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if should_delete:
|
2026-04-12 07:11:41 +00:00
|
|
|
with LOCK:
|
|
|
|
|
SESSIONS.pop(p.stem, None)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
p.unlink(missing_ok=True)
|
|
|
|
|
cleaned += 1
|
|
|
|
|
except Exception:
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger.debug("Failed to clean up session file %s", p)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if SESSION_INDEX_FILE.exists():
|
|
|
|
|
SESSION_INDEX_FILE.unlink(missing_ok=True)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "cleaned": cleaned})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_chat_start(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
require(body, "session_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
msg = str(body.get("message", "")).strip()
|
|
|
|
|
if not msg:
|
|
|
|
|
return bad(handler, "message is required")
|
|
|
|
|
attachments = [str(a) for a in (body.get("attachments") or [])][:20]
|
|
|
|
|
workspace = str(Path(body.get("workspace") or s.workspace).expanduser().resolve())
|
|
|
|
|
model = body.get("model") or s.model
|
|
|
|
|
s.workspace = workspace
|
|
|
|
|
s.model = model
|
|
|
|
|
s.save()
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
set_last_workspace(workspace)
|
|
|
|
|
stream_id = uuid.uuid4().hex
|
|
|
|
|
q = queue.Queue()
|
2026-04-12 07:11:41 +00:00
|
|
|
with STREAMS_LOCK:
|
|
|
|
|
STREAMS[stream_id] = q
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
thr = threading.Thread(
|
|
|
|
|
target=_run_agent_streaming,
|
|
|
|
|
args=(s.session_id, msg, model, workspace, stream_id, attachments),
|
|
|
|
|
daemon=True,
|
|
|
|
|
)
|
|
|
|
|
thr.start()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"stream_id": stream_id, "session_id": s.session_id})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_chat_sync(handler, body):
|
|
|
|
|
"""Fallback synchronous chat endpoint (POST /api/chat). Not used by frontend."""
|
|
|
|
|
from api.config import _get_session_agent_lock
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
msg = str(body.get("message", "")).strip()
|
|
|
|
|
if not msg:
|
|
|
|
|
return j(handler, {"error": "empty message"}, status=400)
|
|
|
|
|
workspace = Path(body.get("workspace") or s.workspace).expanduser().resolve()
|
|
|
|
|
s.workspace = str(workspace)
|
|
|
|
|
s.model = body.get("model") or s.model
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
from api.streaming import _ENV_LOCK
|
2026-04-12 07:11:41 +00:00
|
|
|
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
with _ENV_LOCK:
|
2026-04-12 07:11:41 +00:00
|
|
|
old_cwd = os.environ.get("TERMINAL_CWD")
|
|
|
|
|
os.environ["TERMINAL_CWD"] = str(workspace)
|
|
|
|
|
old_exec_ask = os.environ.get("HERMES_EXEC_ASK")
|
|
|
|
|
old_session_key = os.environ.get("HERMES_SESSION_KEY")
|
|
|
|
|
os.environ["HERMES_EXEC_ASK"] = "1"
|
|
|
|
|
os.environ["HERMES_SESSION_KEY"] = s.session_id
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
try:
|
|
|
|
|
from run_agent import AIAgent
|
2026-04-12 07:11:41 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
with CHAT_LOCK:
|
2026-04-02 07:05:09 +00:00
|
|
|
from api.config import resolve_model_provider
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-02 07:05:09 +00:00
|
|
|
_model, _provider, _base_url = resolve_model_provider(s.model)
|
2026-04-04 20:03:02 +00:00
|
|
|
# Resolve API key via Hermes runtime provider (matches gateway behaviour)
|
|
|
|
|
_api_key = None
|
|
|
|
|
try:
|
|
|
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-06 21:10:26 +00:00
|
|
|
_rt = resolve_runtime_provider(requested=_provider)
|
2026-04-04 20:03:02 +00:00
|
|
|
_api_key = _rt.get("api_key")
|
|
|
|
|
# Also use runtime provider/base_url if the webui config didn't resolve them
|
|
|
|
|
if not _provider:
|
|
|
|
|
_provider = _rt.get("provider")
|
|
|
|
|
if not _base_url:
|
|
|
|
|
_base_url = _rt.get("base_url")
|
|
|
|
|
except Exception as _e:
|
2026-04-12 07:11:41 +00:00
|
|
|
print(
|
|
|
|
|
f"[webui] WARNING: resolve_runtime_provider failed: {_e}",
|
|
|
|
|
flush=True,
|
|
|
|
|
)
|
|
|
|
|
agent = AIAgent(
|
|
|
|
|
model=_model,
|
|
|
|
|
provider=_provider,
|
|
|
|
|
base_url=_base_url,
|
|
|
|
|
api_key=_api_key,
|
|
|
|
|
platform="cli",
|
|
|
|
|
quiet_mode=True,
|
|
|
|
|
enabled_toolsets=CLI_TOOLSETS,
|
|
|
|
|
session_id=s.session_id,
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
workspace_ctx = f"[Workspace: {s.workspace}]\n"
|
|
|
|
|
workspace_system_msg = (
|
|
|
|
|
f"Active workspace at session start: {s.workspace}\n"
|
|
|
|
|
"Every user message is prefixed with [Workspace: /absolute/path] indicating the "
|
|
|
|
|
"workspace the user has selected in the web UI at the time they sent that message. "
|
|
|
|
|
"This tag is the single authoritative source of the active workspace and updates "
|
|
|
|
|
"with every message. It overrides any prior workspace mentioned in this system "
|
|
|
|
|
"prompt, memory, or conversation history. Always use the value from the most recent "
|
|
|
|
|
"[Workspace: ...] tag as your default working directory for ALL file operations: "
|
|
|
|
|
"write_file, read_file, search_files, terminal workdir, and patch. "
|
|
|
|
|
"Never fall back to a hardcoded path when this tag is present."
|
|
|
|
|
)
|
2026-04-04 05:13:12 +00:00
|
|
|
from api.streaming import _sanitize_messages_for_api
|
2026-04-12 07:11:41 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
result = agent.run_conversation(
|
|
|
|
|
user_message=workspace_ctx + msg,
|
|
|
|
|
system_message=workspace_system_msg,
|
2026-04-04 05:13:12 +00:00
|
|
|
conversation_history=_sanitize_messages_for_api(s.messages),
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
task_id=s.session_id,
|
|
|
|
|
persist_user_message=msg,
|
|
|
|
|
)
|
|
|
|
|
finally:
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
with _ENV_LOCK:
|
2026-04-12 07:11:41 +00:00
|
|
|
if old_cwd is None:
|
|
|
|
|
os.environ.pop("TERMINAL_CWD", None)
|
|
|
|
|
else:
|
|
|
|
|
os.environ["TERMINAL_CWD"] = old_cwd
|
|
|
|
|
if old_exec_ask is None:
|
|
|
|
|
os.environ.pop("HERMES_EXEC_ASK", None)
|
|
|
|
|
else:
|
|
|
|
|
os.environ["HERMES_EXEC_ASK"] = old_exec_ask
|
|
|
|
|
if old_session_key is None:
|
|
|
|
|
os.environ.pop("HERMES_SESSION_KEY", None)
|
|
|
|
|
else:
|
|
|
|
|
os.environ["HERMES_SESSION_KEY"] = old_session_key
|
|
|
|
|
s.messages = result.get("messages") or s.messages
|
2026-04-12 17:51:48 +00:00
|
|
|
# Only auto-generate title when still default; preserves user renames
|
|
|
|
|
if s.title == "Untitled":
|
|
|
|
|
s.title = title_from(s.messages, s.title)
|
2026-04-12 07:11:41 +00:00
|
|
|
s.save()
|
2026-04-05 03:07:05 +00:00
|
|
|
# Sync to state.db for /insights (opt-in setting)
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
if load_settings().get("sync_to_insights"):
|
2026-04-05 03:07:05 +00:00
|
|
|
from api.state_sync import sync_session_usage
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-05 03:07:05 +00:00
|
|
|
sync_session_usage(
|
|
|
|
|
session_id=s.session_id,
|
|
|
|
|
input_tokens=s.input_tokens or 0,
|
|
|
|
|
output_tokens=s.output_tokens or 0,
|
|
|
|
|
estimated_cost=s.estimated_cost,
|
|
|
|
|
model=s.model,
|
|
|
|
|
title=s.title,
|
2026-04-07 05:56:27 +00:00
|
|
|
message_count=len(s.messages),
|
2026-04-05 03:07:05 +00:00
|
|
|
)
|
|
|
|
|
except Exception:
|
security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
* security: fix bandit security issues (B310, B324)
- Add usedforsecurity=False to MD5 hash in gateway_watcher.py
- Add URL scheme validation to prevent file:// access in config.py
- Add URL validation to bootstrap.py health check
- Add nosec comments where runtime validation exists
* fix: handle ConnectionResetError gracefully and add debug logging
- Add QuietHTTPServer class to suppress noisy connection reset errors
caused by clients disconnecting abruptly (fixes log spam from
'ConnectionResetError: [Errno 54] Connection reset by peer')
- Replace silent 'pass' statements with logger.debug() calls across
api/auth.py, api/config.py, api/gateway_watcher.py, api/models.py,
and api/onboarding.py for better observability during troubleshooting
- All tests pass (25 passed in test_regressions.py)
* chore: add debug logging to profiles and routes modules
- Replace silent 'pass' statements with logger.debug() calls in
api/profiles.py for better error visibility during profile switching
and module patching
- Add logger initialization to api/routes.py
* security: fix B110 bare except/pass issues (bandit security scan)
- Replace bare except/pass patterns with logger.debug() calls
- Fixes CWE-703 (improper check/handling of exceptional conditions)
- Files affected: routes.py, state_sync.py, streaming.py, workspace.py, server.py
- All tests pass successfully
* security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354)
- api/gateway_watcher.py: MD5 usedforsecurity=False (B324)
- api/config.py, bootstrap.py: URL scheme validation before urlopen (B310)
- 12 files: replace bare except/pass with logger.debug() (B110)
- server.py: QuietHTTPServer suppresses client disconnect log noise
- server.py: fix sys.exc_info() (was traceback.sys.exc_info(), impl detail)
- tests/test_sprint43.py: 19 new tests covering all security fixes
- CHANGELOG.md: v0.50.14 entry; 841 tests total (up from 822)
---------
Co-authored-by: lawrencel1ng <lawrence.ling@global.ntt>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-13 18:11:56 +00:00
|
|
|
logger.debug("Failed to update session cost tracking")
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{
|
|
|
|
|
"answer": result.get("final_response") or "",
|
|
|
|
|
"status": "done" if result.get("completed", True) else "partial",
|
|
|
|
|
"session": s.compact() | {"messages": s.messages},
|
|
|
|
|
"result": {k: v for k, v in result.items() if k != "messages"},
|
|
|
|
|
},
|
|
|
|
|
)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_cron_create(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
require(body, "prompt", "schedule")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
try:
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import create_job
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-02 07:05:09 +00:00
|
|
|
job = create_job(
|
2026-04-12 07:11:41 +00:00
|
|
|
prompt=body["prompt"],
|
|
|
|
|
schedule=body["schedule"],
|
|
|
|
|
name=body.get("name") or None,
|
|
|
|
|
deliver=body.get("deliver") or "local",
|
|
|
|
|
skills=body.get("skills") or [],
|
|
|
|
|
model=body.get("model") or None,
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "job": job})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
except Exception as e:
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"error": str(e)}, status=400)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_cron_update(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
require(body, "job_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import update_job
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
updates = {k: v for k, v in body.items() if k != "job_id" and v is not None}
|
|
|
|
|
job = update_job(body["job_id"], updates)
|
|
|
|
|
if not job:
|
|
|
|
|
return bad(handler, "Job not found", 404)
|
|
|
|
|
return j(handler, {"ok": True, "job": job})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_cron_delete(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
require(body, "job_id")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import remove_job
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
ok = remove_job(body["job_id"])
|
|
|
|
|
if not ok:
|
|
|
|
|
return bad(handler, "Job not found", 404)
|
|
|
|
|
return j(handler, {"ok": True, "job_id": body["job_id"]})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_cron_run(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
job_id = body.get("job_id", "")
|
|
|
|
|
if not job_id:
|
|
|
|
|
return bad(handler, "job_id required")
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import get_job
|
|
|
|
|
from cron.scheduler import run_job
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-02 07:05:09 +00:00
|
|
|
job = get_job(job_id)
|
2026-04-12 07:11:41 +00:00
|
|
|
if not job:
|
|
|
|
|
return bad(handler, "Job not found", 404)
|
2026-04-02 07:05:09 +00:00
|
|
|
threading.Thread(target=run_job, args=(job,), daemon=True).start()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "job_id": job_id, "status": "triggered"})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_cron_pause(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
job_id = body.get("job_id", "")
|
|
|
|
|
if not job_id:
|
|
|
|
|
return bad(handler, "job_id required")
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import pause_job
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
result = pause_job(job_id, reason=body.get("reason"))
|
|
|
|
|
if result:
|
|
|
|
|
return j(handler, {"ok": True, "job": result})
|
|
|
|
|
return bad(handler, "Job not found", 404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_cron_resume(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
job_id = body.get("job_id", "")
|
|
|
|
|
if not job_id:
|
|
|
|
|
return bad(handler, "job_id required")
|
2026-04-02 07:05:09 +00:00
|
|
|
from cron.jobs import resume_job
|
2026-04-12 07:11:41 +00:00
|
|
|
|
2026-04-02 07:05:09 +00:00
|
|
|
result = resume_job(job_id)
|
2026-04-12 07:11:41 +00:00
|
|
|
if result:
|
|
|
|
|
return j(handler, {"ok": True, "job": result})
|
|
|
|
|
return bad(handler, "Job not found", 404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_file_delete(handler, body):
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
require(body, "session_id", "path")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
try:
|
|
|
|
|
target = safe_resolve(Path(s.workspace), body["path"])
|
|
|
|
|
if not target.exists():
|
|
|
|
|
return bad(handler, "File not found", 404)
|
|
|
|
|
if target.is_dir():
|
|
|
|
|
return bad(handler, "Cannot delete directories via this endpoint")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
target.unlink()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "path": body["path"]})
|
|
|
|
|
except (ValueError, PermissionError) as e:
|
|
|
|
|
return bad(handler, _sanitize_error(e))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_file_save(handler, body):
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
require(body, "session_id", "path")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
try:
|
|
|
|
|
target = safe_resolve(Path(s.workspace), body["path"])
|
|
|
|
|
if not target.exists():
|
|
|
|
|
return bad(handler, "File not found", 404)
|
|
|
|
|
if target.is_dir():
|
|
|
|
|
return bad(handler, "Cannot save: path is a directory")
|
|
|
|
|
target.write_text(body.get("content", ""), encoding="utf-8")
|
|
|
|
|
return j(
|
|
|
|
|
handler, {"ok": True, "path": body["path"], "size": target.stat().st_size}
|
|
|
|
|
)
|
|
|
|
|
except (ValueError, PermissionError) as e:
|
|
|
|
|
return bad(handler, _sanitize_error(e))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_file_create(handler, body):
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
require(body, "session_id", "path")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
try:
|
|
|
|
|
target = safe_resolve(Path(s.workspace), body["path"])
|
|
|
|
|
if target.exists():
|
|
|
|
|
return bad(handler, "File already exists")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
2026-04-12 07:11:41 +00:00
|
|
|
target.write_text(body.get("content", ""), encoding="utf-8")
|
|
|
|
|
return j(
|
|
|
|
|
handler, {"ok": True, "path": str(target.relative_to(Path(s.workspace)))}
|
|
|
|
|
)
|
|
|
|
|
except (ValueError, PermissionError) as e:
|
|
|
|
|
return bad(handler, _sanitize_error(e))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_file_rename(handler, body):
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
require(body, "session_id", "path", "new_name")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
try:
|
|
|
|
|
source = safe_resolve(Path(s.workspace), body["path"])
|
|
|
|
|
if not source.exists():
|
|
|
|
|
return bad(handler, "File not found", 404)
|
|
|
|
|
new_name = body["new_name"].strip()
|
|
|
|
|
if not new_name or "/" in new_name or ".." in new_name:
|
|
|
|
|
return bad(handler, "Invalid file name")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
dest = source.parent / new_name
|
2026-04-12 07:11:41 +00:00
|
|
|
if dest.exists():
|
|
|
|
|
return bad(handler, f'A file named "{new_name}" already exists')
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
source.rename(dest)
|
|
|
|
|
new_rel = str(dest.relative_to(Path(s.workspace)))
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "old_path": body["path"], "new_path": new_rel})
|
|
|
|
|
except (ValueError, PermissionError, OSError) as e:
|
|
|
|
|
return bad(handler, _sanitize_error(e))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_create_dir(handler, body):
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
require(body, "session_id", "path")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
try:
|
|
|
|
|
s = get_session(body["session_id"])
|
|
|
|
|
except KeyError:
|
|
|
|
|
return bad(handler, "Session not found", 404)
|
|
|
|
|
try:
|
|
|
|
|
target = safe_resolve(Path(s.workspace), body["path"])
|
|
|
|
|
if target.exists():
|
|
|
|
|
return bad(handler, "Path already exists")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
target.mkdir(parents=True)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(
|
|
|
|
|
handler, {"ok": True, "path": str(target.relative_to(Path(s.workspace)))}
|
|
|
|
|
)
|
|
|
|
|
except (ValueError, PermissionError, OSError) as e:
|
|
|
|
|
return bad(handler, _sanitize_error(e))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_workspace_add(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
path_str = body.get("path", "").strip()
|
|
|
|
|
name = body.get("name", "").strip()
|
|
|
|
|
if not path_str:
|
|
|
|
|
return bad(handler, "path is required")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
p = Path(path_str).expanduser().resolve()
|
2026-04-12 07:11:41 +00:00
|
|
|
if not p.exists():
|
|
|
|
|
return bad(handler, f"Path does not exist: {p}")
|
|
|
|
|
if not p.is_dir():
|
|
|
|
|
return bad(handler, f"Path is not a directory: {p}")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
wss = load_workspaces()
|
2026-04-12 07:11:41 +00:00
|
|
|
if any(w["path"] == str(p) for w in wss):
|
|
|
|
|
return bad(handler, "Workspace already in list")
|
|
|
|
|
wss.append({"path": str(p), "name": name or p.name})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
save_workspaces(wss)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "workspaces": wss})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_workspace_remove(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
path_str = body.get("path", "").strip()
|
|
|
|
|
if not path_str:
|
|
|
|
|
return bad(handler, "path is required")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
wss = load_workspaces()
|
2026-04-12 07:11:41 +00:00
|
|
|
wss = [w for w in wss if w["path"] != path_str]
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
save_workspaces(wss)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "workspaces": wss})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_workspace_rename(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
path_str = body.get("path", "").strip()
|
|
|
|
|
name = body.get("name", "").strip()
|
|
|
|
|
if not path_str or not name:
|
|
|
|
|
return bad(handler, "path and name are required")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
wss = load_workspaces()
|
|
|
|
|
for w in wss:
|
2026-04-12 07:11:41 +00:00
|
|
|
if w["path"] == path_str:
|
|
|
|
|
w["name"] = name
|
|
|
|
|
break
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
else:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Workspace not found", 404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
save_workspaces(wss)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "workspaces": wss})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_approval_respond(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = body.get("session_id", "")
|
|
|
|
|
if not sid:
|
|
|
|
|
return bad(handler, "session_id is required")
|
|
|
|
|
choice = body.get("choice", "deny")
|
|
|
|
|
if choice not in ("once", "session", "always", "deny"):
|
|
|
|
|
return bad(handler, f"Invalid choice: {choice}")
|
fix: surface approval prompt in UI instead of getting stuck in Thinking (#187)
* fix: surface approval prompt in UI instead of getting stuck in Thinking
When a dangerous command was detected during streaming, the approval system
would call submit_pending() but no SSE 'approval' event would be emitted to
the frontend. The agent thread either blocked indefinitely (gateway path) or
returned an approval_required status the UI never saw (EXEC_ASK path). Either
way the chat UI stayed stuck in 'Thinking...' with no prompt shown.
Root cause: streaming.py used HERMES_EXEC_ASK=1 but never registered a
register_gateway_notify() callback. Without it, check_all_command_guards()
fell back to the legacy polling path (submit_pending only), which relies on
on_tool() polling -- but on_tool() fires *before* the tool runs, so by the
time the terminal tool detected the dangerous command and called submit_pending,
the approval event had already missed its window.
Fix (streaming.py):
- Register a gateway-style notify_cb via register_gateway_notify() before the
agent runs. The callback calls put('approval', ...) to emit the SSE event
the moment a dangerous command is detected, regardless of on_tool() timing.
- Unregister via unregister_gateway_notify() in the finally block to unblock
any threads still waiting if the stream ends or is cancelled mid-approval.
- Keep the on_tool() fallback poll for older approval module versions.
Fix (routes.py):
- Import and call resolve_gateway_approval() in _handle_approval_respond().
This unblocks the agent thread parked in entry.event.wait() when the user
clicks Allow or Deny in the UI. Without this call the thread would block
until the 5-minute gateway timeout.
Tests (tests/test_approval_unblock.py):
- 16 new tests covering: resolve_gateway_approval() event signalling, deny/
session/once choices, resolve_all, notify_cb registration/firing/cleanup,
unregister signals blocked entries, full end-to-end streaming simulation,
module symbol exports, and HTTP endpoint regressions.
515 tests pass (499 existing + 16 new).
* feat: full approval UI — i18n buttons, keyboard shortcut, loading state, scoping fix
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-09 03:16:22 +00:00
|
|
|
# Pop the legacy polling-mode pending entry (no-op when gateway path is active).
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
with _lock:
|
|
|
|
|
pending = _pending.pop(sid, None)
|
|
|
|
|
if pending:
|
2026-04-12 07:11:41 +00:00
|
|
|
keys = pending.get("pattern_keys") or [pending.get("pattern_key", "")]
|
|
|
|
|
if choice in ("once", "session"):
|
|
|
|
|
for k in keys:
|
|
|
|
|
approve_session(sid, k)
|
|
|
|
|
elif choice == "always":
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
for k in keys:
|
2026-04-12 07:11:41 +00:00
|
|
|
approve_session(sid, k)
|
|
|
|
|
approve_permanent(k)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
save_permanent_allowlist(_permanent_approved)
|
fix: surface approval prompt in UI instead of getting stuck in Thinking (#187)
* fix: surface approval prompt in UI instead of getting stuck in Thinking
When a dangerous command was detected during streaming, the approval system
would call submit_pending() but no SSE 'approval' event would be emitted to
the frontend. The agent thread either blocked indefinitely (gateway path) or
returned an approval_required status the UI never saw (EXEC_ASK path). Either
way the chat UI stayed stuck in 'Thinking...' with no prompt shown.
Root cause: streaming.py used HERMES_EXEC_ASK=1 but never registered a
register_gateway_notify() callback. Without it, check_all_command_guards()
fell back to the legacy polling path (submit_pending only), which relies on
on_tool() polling -- but on_tool() fires *before* the tool runs, so by the
time the terminal tool detected the dangerous command and called submit_pending,
the approval event had already missed its window.
Fix (streaming.py):
- Register a gateway-style notify_cb via register_gateway_notify() before the
agent runs. The callback calls put('approval', ...) to emit the SSE event
the moment a dangerous command is detected, regardless of on_tool() timing.
- Unregister via unregister_gateway_notify() in the finally block to unblock
any threads still waiting if the stream ends or is cancelled mid-approval.
- Keep the on_tool() fallback poll for older approval module versions.
Fix (routes.py):
- Import and call resolve_gateway_approval() in _handle_approval_respond().
This unblocks the agent thread parked in entry.event.wait() when the user
clicks Allow or Deny in the UI. Without this call the thread would block
until the 5-minute gateway timeout.
Tests (tests/test_approval_unblock.py):
- 16 new tests covering: resolve_gateway_approval() event signalling, deny/
session/once choices, resolve_all, notify_cb registration/firing/cleanup,
unregister signals blocked entries, full end-to-end streaming simulation,
module symbol exports, and HTTP endpoint regressions.
515 tests pass (499 existing + 16 new).
* feat: full approval UI — i18n buttons, keyboard shortcut, loading state, scoping fix
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-09 03:16:22 +00:00
|
|
|
# Unblock the agent thread waiting in the gateway approval queue.
|
|
|
|
|
# This is the primary signal when streaming is active — the agent
|
|
|
|
|
# thread is parked in entry.event.wait() and needs to be woken up.
|
|
|
|
|
resolve_gateway_approval(sid, choice, resolve_all=False)
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "choice": choice})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_skill_save(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
require(body, "name", "content")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
skill_name = body["name"].strip().lower().replace(" ", "-")
|
|
|
|
|
if not skill_name or "/" in skill_name or ".." in skill_name:
|
|
|
|
|
return bad(handler, "Invalid skill name")
|
|
|
|
|
category = body.get("category", "").strip()
|
|
|
|
|
if category and ("/" in category or ".." in category):
|
|
|
|
|
return bad(handler, "Invalid category")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
from tools.skills_tool import SKILLS_DIR
|
2026-04-12 07:11:41 +00:00
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if category:
|
|
|
|
|
skill_dir = SKILLS_DIR / category / skill_name
|
|
|
|
|
else:
|
|
|
|
|
skill_dir = SKILLS_DIR / skill_name
|
release: v0.39.0 — security hardening, 12 fixes (#171)
* Security: harden auth, CSRF, SSRF, XSS, and env race conditions
Twelve fixes from a full security audit:
CRITICAL
- Add CSRF Origin/Referer validation on all POST endpoints
(prevents cross-origin abuse of self-update, settings, file ops)
HIGH
- Unify password hashing: config.py now uses PBKDF2 (600k iters)
instead of single-iteration SHA-256
- Add per-IP rate limiting on login (5 attempts/60s, 429 on excess)
MEDIUM
- Validate session IDs as hex-only before filesystem operations
(prevents path traversal via crafted session ID)
- SSRF: resolve DNS before private-IP check in model fetching
(prevents DNS rebinding to internal services)
- Warn loudly when binding non-loopback without password set
- SSE env var mutations: wrap sync chat + streaming restore in _ENV_LOCK
- Force Content-Disposition:attachment for HTML/XHTML/SVG uploads
(prevents stored XSS via uploaded files)
LOW
- Extend HMAC session signature from 64 to 128 bits
- Add resolve()+relative_to() check on skills path construction
- Set Secure flag on session cookie when connection is HTTPS
- Sanitize exception messages to strip filesystem paths
No breaking changes. All fixes are backward-compatible.
* fix: use getattr for Secure cookie SSL detection
handler.request.getpeercert raises AttributeError on plain sockets
(non-SSL). Use getattr(..., None) to safely check for SSL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* tests: add sprint 29 security hardening coverage (PR #171)
33 tests covering all 12 security fixes:
- CSRF origin/referer validation
- Login rate limiting (5 attempts/60s)
- Session ID hex validation (path traversal prevention)
- Error path sanitization (_sanitize_error)
- Secure cookie getattr safety
- HMAC signature length (64->128 bit)
- Skills path traversal prevention
- Content-Disposition for HTML/SVG/XHTML
- PBKDF2 password hashing verification
- Non-loopback startup warning
- SSRF DNS guard code presence
- _ENV_LOCK export from streaming module
* release: v0.39.0 — security hardening, 12 fixes (#171)
---------
Co-authored-by: betamod <matthew.sloly@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 05:26:03 +00:00
|
|
|
# Validate resolved path stays within SKILLS_DIR
|
|
|
|
|
try:
|
|
|
|
|
skill_dir.resolve().relative_to(SKILLS_DIR.resolve())
|
|
|
|
|
except ValueError:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Invalid skill path")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
skill_dir.mkdir(parents=True, exist_ok=True)
|
2026-04-12 07:11:41 +00:00
|
|
|
skill_file = skill_dir / "SKILL.md"
|
|
|
|
|
skill_file.write_text(body["content"], encoding="utf-8")
|
|
|
|
|
return j(handler, {"ok": True, "name": skill_name, "path": str(skill_file)})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_skill_delete(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
require(body, "name")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
from tools.skills_tool import SKILLS_DIR
|
|
|
|
|
import shutil
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
matches = list(SKILLS_DIR.rglob(f"{body['name']}/SKILL.md"))
|
|
|
|
|
if not matches:
|
|
|
|
|
return bad(handler, "Skill not found", 404)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
skill_dir = matches[0].parent
|
|
|
|
|
shutil.rmtree(str(skill_dir))
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "name": body["name"]})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_memory_write(handler, body):
|
2026-04-12 07:11:41 +00:00
|
|
|
try:
|
|
|
|
|
require(body, "section", "content")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
try:
|
|
|
|
|
from api.profiles import get_active_hermes_home
|
2026-04-12 07:11:41 +00:00
|
|
|
|
|
|
|
|
mem_dir = get_active_hermes_home() / "memories"
|
feat: multi-profile support -- create, switch, delete profiles from web UI (Issue #28)
Add full profile management to the web UI, matching the hermes-agent CLI
profile system. Profiles are isolated HERMES_HOME instances with their own
config, skills, memory, cron, and API keys.
Backend: new api/profiles.py wrapping hermes_cli.profiles, dynamic config
reloading, 5 new API endpoints, profile-aware path resolution, HERMES_HOME
env save/restore in streaming, module-level cache patching for skills_tool
and cron/jobs.
Frontend: profile chip in topbar with dropdown, Profiles sidebar panel with
CRUD UI, boot-time profile fetch, cascade refresh on switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 17:50:21 +00:00
|
|
|
except ImportError:
|
2026-04-12 07:11:41 +00:00
|
|
|
mem_dir = Path.home() / ".hermes" / "memories"
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
mem_dir.mkdir(parents=True, exist_ok=True)
|
2026-04-12 07:11:41 +00:00
|
|
|
section = body["section"]
|
|
|
|
|
if section == "memory":
|
|
|
|
|
target = mem_dir / "MEMORY.md"
|
|
|
|
|
elif section == "user":
|
|
|
|
|
target = mem_dir / "USER.md"
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
else:
|
|
|
|
|
return bad(handler, 'section must be "memory" or "user"')
|
2026-04-12 07:11:41 +00:00
|
|
|
target.write_text(body["content"], encoding="utf-8")
|
|
|
|
|
return j(handler, {"ok": True, "section": section, "path": str(target)})
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
|
|
|
|
|
|
2026-04-04 02:54:54 +00:00
|
|
|
def _handle_session_import_cli(handler, body):
|
|
|
|
|
"""Import a single CLI session into the WebUI store."""
|
|
|
|
|
try:
|
2026-04-12 07:11:41 +00:00
|
|
|
require(body, "session_id")
|
2026-04-04 02:54:54 +00:00
|
|
|
except ValueError as e:
|
|
|
|
|
return bad(handler, str(e))
|
|
|
|
|
|
2026-04-12 07:11:41 +00:00
|
|
|
sid = str(body["session_id"])
|
2026-04-04 02:54:54 +00:00
|
|
|
|
|
|
|
|
# Check if already imported — idempotent
|
|
|
|
|
existing = Session.load(sid)
|
|
|
|
|
if existing:
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{
|
|
|
|
|
"session": existing.compact()
|
|
|
|
|
| {
|
|
|
|
|
"messages": existing.messages,
|
|
|
|
|
"is_cli_session": True,
|
|
|
|
|
},
|
|
|
|
|
"imported": False,
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-04-04 02:54:54 +00:00
|
|
|
|
|
|
|
|
# Fetch messages from CLI store
|
|
|
|
|
msgs = get_cli_session_messages(sid)
|
|
|
|
|
if not msgs:
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Session not found in CLI store", 404)
|
2026-04-04 02:54:54 +00:00
|
|
|
|
|
|
|
|
# Derive title from first user message
|
2026-04-12 07:11:41 +00:00
|
|
|
title = title_from(msgs, "CLI Session")
|
|
|
|
|
model = "unknown"
|
2026-04-04 02:54:54 +00:00
|
|
|
|
|
|
|
|
# Get profile and model from CLI session metadata
|
|
|
|
|
profile = None
|
|
|
|
|
for cs in get_cli_sessions():
|
2026-04-12 07:11:41 +00:00
|
|
|
if cs["session_id"] == sid:
|
|
|
|
|
profile = cs.get("profile")
|
|
|
|
|
model = cs.get("model", "unknown")
|
2026-04-04 02:54:54 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
|
|
s = import_cli_session(sid, title, msgs, model, profile=profile)
|
|
|
|
|
s.is_cli_session = True
|
|
|
|
|
s._cli_origin = sid
|
|
|
|
|
s.save()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(
|
|
|
|
|
handler,
|
|
|
|
|
{
|
|
|
|
|
"session": s.compact()
|
|
|
|
|
| {
|
|
|
|
|
"messages": msgs,
|
|
|
|
|
"is_cli_session": True,
|
|
|
|
|
},
|
|
|
|
|
"imported": True,
|
2026-04-04 02:54:54 +00:00
|
|
|
},
|
2026-04-12 07:11:41 +00:00
|
|
|
)
|
2026-04-04 02:54:54 +00:00
|
|
|
|
|
|
|
|
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
def _handle_session_import(handler, body):
|
|
|
|
|
"""Import a session from a JSON export. Creates a new session with a new ID."""
|
|
|
|
|
if not body or not isinstance(body, dict):
|
2026-04-12 07:11:41 +00:00
|
|
|
return bad(handler, "Request body must be a JSON object")
|
|
|
|
|
messages = body.get("messages")
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
if not isinstance(messages, list):
|
|
|
|
|
return bad(handler, 'JSON must contain a "messages" array')
|
2026-04-12 07:11:41 +00:00
|
|
|
title = body.get("title", "Imported session")
|
|
|
|
|
workspace = body.get("workspace", str(DEFAULT_WORKSPACE))
|
|
|
|
|
model = body.get("model", DEFAULT_MODEL)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
s = Session(
|
2026-04-12 07:11:41 +00:00
|
|
|
title=title,
|
|
|
|
|
workspace=workspace,
|
|
|
|
|
model=model,
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
messages=messages,
|
2026-04-12 07:11:41 +00:00
|
|
|
tool_calls=body.get("tool_calls", []),
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
)
|
2026-04-12 07:11:41 +00:00
|
|
|
s.pinned = body.get("pinned", False)
|
Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish
Sprint 11 (v0.13): multi-provider model support, streaming smoothness
- Dynamic model dropdown populated from configured API keys (OpenAI, Anthropic,
Google, DeepSeek, GLM, Kimi, MiniMax, OpenRouter, Nous Portal)
- Scroll pinning during streaming (no forced scroll when user has scrolled up)
- All route handlers extracted to api/routes.py (server.py now ~76 lines)
Sprint 12 (v0.14): settings panel, SSE reconnect, session QoL
- Settings panel (gear icon) -- persist default model and workspace server-side
- SSE auto-reconnect on network blips
- Pin/star sessions to top of sidebar
- Import session from JSON export
Sprint 13 (v0.15): cron alerts, background errors, session duplicate, tab title
- Cron completion alerts: toast per completion + unread badge on Tasks tab
- Background agent error banner when a non-active session errors mid-stream
- Session duplicate button
- Browser tab title reflects active session name
Sprint 14 (v0.16): Mermaid diagrams, file ops, session archive/tags, timestamps
- Mermaid diagram rendering inline (dark theme, lazy CDN load)
- File rename (double-click in file tree) and create folder
- Session archive (hide without deleting, toggle to show)
- Session tags -- #hashtag in title becomes colored chip + click-to-filter
- Message timestamps (HH:MM on hover, full date as tooltip)
Test suite: 224 tests across 14 sprint files + regression gate, 0 failures.
2026-03-31 07:02:47 +00:00
|
|
|
with LOCK:
|
|
|
|
|
SESSIONS[s.session_id] = s
|
|
|
|
|
SESSIONS.move_to_end(s.session_id)
|
|
|
|
|
while len(SESSIONS) > SESSIONS_MAX:
|
|
|
|
|
SESSIONS.popitem(last=False)
|
|
|
|
|
s.save()
|
2026-04-12 07:11:41 +00:00
|
|
|
return j(handler, {"ok": True, "session": s.compact() | {"messages": s.messages}})
|