All LRU cache operations (get, set, move_to_end, popitem) are already
protected by SESSION_AGENT_CACHE_LOCK. This addresses the reviewer's
concern about thread safety in multi-threaded ASGI servers.
The agent cache stores full AIAgent instances (each holding complete
conversation history) without size limit. Long-running servers with
many sessions can accumulate unbounded memory usage.
Changes:
- Replace dict with OrderedDict for LRU tracking
- Add SESSION_AGENT_CACHE_MAX = 50 limit
- Evict least-recently-used entries when cache exceeds limit
- Call move_to_end() on cache hits to maintain LRU order
This prevents memory exhaustion on servers with many active sessions.
When using custom providers with private IPs (like AxonHub on internal
networks), the SSRF protection incorrectly blocks API calls to the user's
own configured endpoint.
This fix automatically adds the model.base_url hostname to the SSRF
trusted hosts list, since it's explicitly configured by the user.
Fixes issues where /api/models and /v1/* endpoints fail silently
when using custom providers with private IPs or IPv6 addresses.
Two artifacts from a contributor's local graphify (code-graph) tooling
slipped into PR #1233 (workspace drag-to-reorder):
.graphify_cached.json (3.5MB)
.graphify_uncached.txt (refs /home/fr33m1nd/hermes-webui-src/...)
Neither belongs in source control: the .json is an autogenerated cache
of node IDs for a graph visualisation tool, and the .txt is a
file-discovery index pointing at the contributor's local workspace
(/home/fr33m1nd/hermes-webui-src/) — paths that aren't valid for any
other developer.
The repo already ignores graphify-out/ but these two top-level dotfiles
weren't covered. Add explicit ignore entries and remove the tracked
copies.
No code change. CI remains green on 3.11/3.12/3.13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
B1: fix stored XSS in MCP delete button — replace inline onclick with
data-mcp-name attribute + event delegation (panels.js)
B2: fix zip/tar-slip via startswith prefix collision — use
is_relative_to(); track actual extracted bytes instead of trusting
member.file_size (upload.py)
B3: add NVIDIA NIM endpoint to _OPENAI_COMPAT_ENDPOINTS and
_SUPPORTED_PROVIDER_SETUPS so provider is reachable (routes.py,
onboarding.py)
H1: add terminalResizeHandle element to index.html and return it from
_terminalEls() so resize-by-drag works (index.html, terminal.js)
H2: fix dead get_terminal() branch — return None for dead terminals
instead of always returning term (terminal.py)
H3: replace os.environ.copy() with a safe allowlist in PTY shell env
so API keys are not exposed inside the terminal (terminal.py)
H5: make model dedup deterministic — sort groups by provider_id
alphabetically before first-occurrence assignment (config.py)
H7: add pid regex validation before OAuth probe; constrain key_source
to a closed set of safe values (providers.py)
M8: add double-run guard for cron run-now — reject if job is already
tracked as running (routes.py)
- Add _strip_masked_values() to skip masked placeholders in PUT endpoint,
preserving the original stored secret values instead of overwriting them
- Fix transport badge to gracefully handle unknown/future transport types
with a fallback that shows the raw string
- Add TestStripMaskedValues (5 tests) for the round-trip protection logic
- Addresses reviewer feedback on secret masking semantics and transport badge
- Add GET /api/mcp/servers (list with masked secrets)
- Add PUT /api/mcp/servers/<name> (add/update stdio and http servers)
- Add DELETE /api/mcp/servers/<name> (remove server)
- MCP section in System settings with server list, add/delete form
- Auto-detect transport type (stdio vs http) from server config
- Mask sensitive values (API keys, tokens, passwords) in list response
- Uses showConfirmDialog for delete confirmation (no native confirm)
- i18n: 21 keys across 7 locales
- 21 tests (list, save, delete, mask_secrets, validation)
- Add 512 KB cap for inline diff rendering to prevent DOM bloat on large patch files
- Add diff_error and diff_too_large i18n keys in all 7 locales for clear error messages
- Improve error state to show explanatory message instead of just filename
- Addresses reviewer feedback on file size cap and missing diff_error i18n key
- Fenced code blocks with diff/patch lang hint render with colored lines
(green +lines, red -lines, italic @@ hunks)
- MEDIA:.patch/.diff files render inline instead of download link
(async fetch via loadDiffInline() in post-render pipeline)
- CSS: diff-block, diff-line, diff-plus/minus/hunk classes
- i18n: diff_loading key in all 7 locales
- 12 tests: renderer, MEDIA inline, CSS classes, i18n parity
Closes#483
- Remove raw err.message from error toast to prevent leaking internal error
details to the UI (Path Trust Boundary Rule)
- Use i18n key workspace_reorder_failed for the sanitized message
- Addresses reviewer concern about optimistic vs confirmed reorder:
the reorder is confirmed (API-first), not optimistic
When context usage reaches 50% (yellow), a subtle hint button appears
in the context ring tooltip suggesting /compress. At 75%+ (red), the
hint intensifies with a warning style.
Clicking the button pre-fills /compress into the composer and focuses
it, so the user can add a focus topic or just hit send. No auto-fire
— the user stays in control.
- static/ui.js: conditional visibility + click handler in _syncCtxIndicator
- static/index.html: ctxCompressBtn element inside ctxTooltip
- static/style.css: muted button style, red variant for ctx-high
- static/i18n.js: ctx_compress_hint / ctx_compress_action in all 7 locales
Closes#524
_stopCronWatch() was only called when switching between cron job details
but not when the panel was cleared entirely (_clearCronDetail). This could
leave orphaned polling intervals if the user navigated away or the panel
was dismissed while a job was running.
Backend:
- Track running cron jobs in thread-safe dict (job_id → start_time)
- Wrapper _run_cron_tracked() marks done on completion
- New GET /api/crons/status?job_id=... returns {running, elapsed}
- New GET /api/crons/status returns all running jobs
Frontend:
- After 'Run Now', enters watch mode with 3s polling
- Shows running indicator (spinner + elapsed timer) in detail card
- Auto-detects running jobs when opening detail view
- Stops watch and refreshes output on job completion
- Cleanup on detail view switch
Note: True SSE streaming is not possible because the hermes-agent
scheduler writes output files only on completion. This polling
approach provides real-time status feedback within that constraint.
- Add cumulative extraction size limit (_MAX_EXTRACTED_BYTES = 200 MB)
that tracks uncompressed file sizes during extraction to guard against
zip/tar bombs (small compressed archives that expand to huge sizes).
- On any extraction failure (disk full, corrupted member, size limit),
clean up the partially-extracted destination directory to avoid
leaving orphaned folders in the workspace.
- Name dedup: 'Job (copy)', 'Job (copy 2)', 'Job (copy 3)' etc.
- Duplicates explicitly pass enabled:false to backend
- Normal cron create is unaffected (no enabled field sent)
Addresses reviewer feedback on #1225 (points 1 + 4).
- Add duplicate button in cron detail header
- Pre-fills create form with original job settings
- New job created as paused copy with '(copy)' suffix
- i18n keys in all 7 locales
The inline rename via double-click (nameEl.ondblclick) was not updating
the _expandedDirs and _dirCache when renaming a directory, unlike the
context-menu rename path (_inlineRenameFileItem) which already had this
logic. This could cause the tree view to show stale expand state after
a directory was renamed via double-click.
The file tree already supported file rename (double-click), file delete
(button), and create file/folder. This adds the missing directory
operations:
Backend:
- _handle_file_delete now supports directories when recursive=true
(uses shutil.rmtree instead of blocking with an error)
Frontend:
- Right-click context menu on all file/directory items with Rename
and Delete options (follows the project context menu pattern)
- Directory delete button (x) with confirmation dialog
- _inlineRenameFileItem() for renaming dirs via context menu prompt
- Expanded-dir cache is updated on rename/delete to stay consistent
- Context menu auto-positions within viewport bounds
i18n: delete_dir_confirm, rename_title, rename_prompt in all 7 locales
Closes#1104