Files
hermes-webui/ROADMAP.md
T
58ad315dca v0.50.216: compression chains, renderer fixes, HTML preview, approval z-index, /steer fix, reasoning chip (#1075)
* fix(workspace): add .html/.htm to MIME_MAP so HTML preview renders correctly

MIME_MAP was missing entries for .html and .htm. The server fell back to
Content-Type: application/octet-stream, which browsers refuse to render as
HTML in an iframe — causing a blank white preview.

The rest of the pipeline was already correct: the iframe exists in
static/index.html, openFile() in static/workspace.js routes .html to
showPreview('html'), and _handle_file_raw() in api/routes.py sets the
correct CSP sandbox header when ?inline=1 is present. The only missing
piece was the MIME type.

* test(workspace): lock in MIME_MAP entry for .html/.htm

PR #1070 added .html/.htm → text/html to MIME_MAP in api/config.py
to fix the blank workspace HTML preview iframe. Without a direct
assertion on the MIME_MAP entries, the fix could silently regress
(the existing test_779_html_preview.py tests cover the iframe wiring,
the inline=1 query handling, and the CSP sandbox header — but none of
them touch MIME_MAP itself).

Add a single regression test that asserts MIME_MAP['.html'] and
MIME_MAP['.htm'] are both 'text/html' so any future removal of those
entries fails CI immediately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(composer): raise .approval-card.visible z-index above .queue-card

.queue-card has z-index:2. .approval-card.visible had no z-index, so the
queue flyout would render on top of the approval card when both were visible
simultaneously — obscuring the Allow/Deny buttons.

Fix: add z-index:3 to .approval-card.visible so approvals always render
above the queue flyout. Approval is a blocking, security-relevant interaction
and must never be obscured by passive UI elements.

* test(composer): pin approval-card z-index > queue-card invariant

PR #1071 raises .approval-card.visible to z-index:3 so the security-
relevant Allow / Deny buttons stay clickable when the queue flyout is
also open. Without a regression test, a future CSS edit could silently
drop the z-index back below queue-card (z-index:2) and reintroduce the
bug — there is no automated UI test covering this stacking interaction.

Add a focused regex check that pins the invariant:
.approval-card.visible z-index must be strictly greater than
.queue-card z-index.

Modeled on the existing CSS-regex regression style in
tests/test_mobile_layout.py (test_profile_dropdown_not_clipped_by_overflow).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: intercept /steer /interrupt /queue before busy-mode routing in send()

Root cause: slash commands entered while the agent is busy never reached
the command dispatcher. send() enters the busy block and returns early at
line ~50, so the slash-command intercept (~line 56) is never reached.
The text was queued as a plain message. When it drained after the turn
ended, cmdSteer / cmdInterrupt ran on an idle session, saw no active stream,
and showed "No active task to stop."

Fix: at the top of the busy block, before checking busyMode, check if the
text starts with / and is one of the three control commands. If so, dispatch
the handler immediately and return. This lets the user type /steer, /interrupt,
or /queue at any time — including while the agent is mid-stream — and have
them execute against the live session.

Two new regression tests added:
- test_slash_commands_intercepted_before_busymode_routing: verifies the
  intercept appears before the busyMode routing in the busy block
- test_steer_intercept_calls_handler_directly: verifies the intercept calls
  _bc.fn(_pc.args) and returns, not queues

* test(busy-intercept): pin sync input-clear before await in slash intercept

PR #1072's intercept clears the msg input before awaiting the handler.
Order matters: if the await happens first (or if the clear is moved
inside the handler), the input still shows '/steer foo' for the duration
of the await. A reflexive second Enter press during that window — common
while waiting for the toast — re-runs send(): either re-fires the
handler (double-steer) or, if the turn just ended, falls through to the
non-busy slash dispatcher and drops a confusing "No active task to stop."

Add test_steer_intercept_clears_input_before_await pinning the order so
this UX invariant cannot silently regress.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: update steer i18n and settings copy — steer no longer interrupts

With the real /steer implementation (agent.steer() via /api/chat/steer),
steer injects a correction mid-turn WITHOUT interrupting the current stream.
The previous copy said "falls back to interrupt", "Steer (interrupt + send)",
etc. — accurate only for the old placeholder, not the real implementation.

Changes across all 6 locales (en/ru/es/de/zh/zh-Hant):
  cmd_steer:                  "falls back to interrupt" removed
  settings_busy_input_mode_steer: "interrupt + send" → "mid-turn correction"
  cmd_steer_fallback:         "interrupted" → "queued for next turn"
  busy_steer_fallback:        "interrupted instead" → "queued for next turn"
  settings_desc_busy_input_mode: "currently falls back to interrupt" removed

Also:
  static/index.html: inline fallback text updated to match
  static/commands.js: internal comment clarified (fallback = queue+cancel,
                      not "interrupt mode" which implies the primary action)

* fix(renderer): group consecutive blockquote lines into single element

Root cause: the old rule `s.replace(/^> (.+)$/gm, ...)` had three bugs:
  1. `.+` required at least one character — bare `>` lines (blank
     continuation lines) did not match and passed through as literal `>`
  2. Each matching line became its own `<blockquote>` element — a 10-line
     blockquote produced 10 stacked `<blockquote>` tags with no grouping
  3. When a fenced code block sat inside a blockquote, the fence-stash
     pass consumed the code content and left orphaned `>` lines that the
     old `.+` pattern could not match

Fix: replace the single-line regex with a group-based approach that matches
one or more consecutive `>` lines as a single block, strips the `>` prefix
from each line, passes each non-empty line through inlineMd(), turns blank
`>` lines into `<br>`, and wraps the entire group in one `<blockquote>`.

14 regression tests added covering:
- Single-line blockquotes (regression)
- Multi-line grouping (2 and 10 lines)
- Two separate blockquotes staying separate
- Bare `>` and `>text` (no space) edge cases
- Blank continuation lines → <br>
- Bold / italic / inline-code inside blockquotes
- Blockquote followed by normal paragraph

* fix(renderer): drop empty trailing line from blockquote match

The new group-based blockquote rule introduced in this PR captures the
trailing newline in its (?:\n|$) clause. After block.split('\n') that
trailing newline produces an empty final element. The original filter
only dropped lone bare '>' artifacts on the last line, so the empty
final element survived, and the .map(blank → '<br>') step turned it
into a phantom <br> immediately before </blockquote>.

Visible symptom: any blockquote whose source ends with \n (the common
case — a quote followed by another paragraph or end-of-message) renders
with an extra blank line at the bottom of the quote.

Reproducer:
  '> Hello\n\nThe rest of the message.'
    → '<blockquote>Hello\n<br></blockquote>\nThe rest of the message.'
                          ^^^ phantom <br>

Fix: replace the single-line filter with a while-loop that pops trailing
lines while they are either empty OR a bare '>'. This matches the
intent the Python test mirror in tests/test_blockquote_rendering.py
already had (the mirror was correct; the JS was not — that's why
the original tests passed despite the bug).

Also add four new regression tests in TestNoPhantomTrailingBr that pin
the no-trailing-<br> invariant for the common shapes:
  - input ending with \n
  - quote followed by paragraph (the real-world case)
  - multi-line quote ending with \n
  - quote with blank continuation + trailing \n (internal <br> stays,
    trailing <br> does not)

Verified end-to-end with node against the actual JS regex.
244 renderer-adjacent tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(renderer): comprehensive markdown fixes — strikethrough, task lists, CRLF, nested blockquotes

Five additional fixes on top of the blockquote grouping from the initial commit:

1. CRLF normalisation: strip \r\n → \n at start of renderMd so Windows
   line endings do not produce stray \r characters in rendered output

2. Strikethrough: ~~text~~ → <del>text</del> in both inlineMd() (for use
   inside blockquotes/lists) and the outer pass (for plain paragraphs).
   Added <del> to SAFE_TAGS and SAFE_INLINE so it is not HTML-escaped.

3. Task lists: - [x] / - [ ] items in unordered lists render as ✅/☐
   via task-done/task-todo span wrappers. Checks [X] (uppercase) too.

4. Nested blockquotes: >> / >>> etc. now recurse so each level gets its
   own <blockquote> element rather than passing through as literal >.
   Implemented by extracting the blockquote rule into _applyBlockquotes()
   which calls itself recursively on the stripped inner content.

5. Lists inside blockquotes: > - item now renders <ul><li> inside the
   blockquote instead of a literal "- item" string. Task list items work
   inside blockquotes too (> - [x] done → ✅ inside <blockquote><ul>).

Also fixed test_issue342.py search window (5000→10000 chars) — the CRLF
strip at the top of renderMd pushed the autolink regex past the old limit.

68 new tests in test_renderer_comprehensive.py + test_blockquote_rendering.py
covering all constructs, edge cases, and combinations.

* fix(renderer): restore space in blockquote prefix-strip regex

Commit 04e7b53 changed the blockquote prefix-strip regex from
  /^>[ \t]?/   (consume "> ", "\t>", or just ">")
to
  /^>[\t]?/    (only consume "\t>" or just ">")

The space character was dropped from the character class. Since
practically every blockquote an LLM produces is "> " (greater-than
followed by a space), this leaves a leading space artifact on every
stripped blockquote line. Worse, the leading space breaks the
list-detection regex `^(?:  )?[-*+] ` inside the new `_applyBlockquotes`
helper — that regex requires either zero or two leading spaces, never
one — so the new "list inside blockquote" feature never fired for
the canonical input shape `> - item`.

Reproducer (against the actual ui.js via node, before the fix):
  > Hello world         → <blockquote> Hello world</blockquote>
                                       ^ phantom leading space
  > Steps:              → <blockquote>Steps:
  > - one                  - one
  > - two                  - two</blockquote>
                          ^ literal text, NOT a <ul>; lists-in-quote feature broken
  > - [x] done          → blockquote with literal "[x] done", no checkbox span

Tests passed despite the bug because tests/test_blockquote_rendering.py
and tests/test_renderer_comprehensive.py validate against a Python
mirror (`_apply_blockquotes`) whose strip regex is `^>[ \t]?` — i.e.
the mirror is correct, the JS is not, and the static-mirror tests
can't catch the divergence. Same shape of bug as commit 94d63d0
(phantom <br> in trailing line) where the mirror was right and the JS
was wrong.

Fix: restore the space character in the strip regex's character class.

Add tests/test_renderer_js_behaviour.py — 11 tests that drive the
ACTUAL renderMd via node and assert on rendered output for the most
common LLM shapes (single-line quote, multi-line quote, list inside
quote, task list inside quote, nested >>>, strikethrough inside and
outside quote, top-level task list, quote followed by heading,
multi-paragraph quote with list, CRLF normalisation).

Verified: the buggy regex makes 6 of those 11 tests fail; the corrected
regex makes all 11 pass.

Suite: 2354 passed, 0 new failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Collapse agent session compression chains

* Restore upstream changelog entries

* fix(agent_sessions): bubble active compression chains to top by tip last_activity

The original PR merge kept the chain head's id/title/started_at and overrode
id/model/message_count/ended_at/end_reason from the tip — but did NOT override
last_activity. Since the projected list is sorted by last_activity DESC and
the WebUI sidebar surfaces updated_at = last_activity, an actively-used
compression chain whose tip is being edited NOW would sort by the ROOT's
old last_activity and fall below recently touched standalone sessions.

Reproducer (with the harness against actual code, before the fix):
  - root: started 30 days ago, last msg 30 days ago
  - tip:  started 28 days ago (parent_session_id=root), last msg 5 seconds ago
  - standalone: last msg 2 days ago

  Sidebar order with original PR:
    [0] standalone  (48h ago)
    [1] active_tip  (last_activity=root's 720h ago)  ← wrong

  Sidebar order after fix:
    [0] active_tip  (last_activity=tip's 0h ago)     ← correct
    [1] standalone  (48h ago)

This matches Hermes Agent's own list_sessions_rich projection at
hermes_state.py:903-909, which overrides "last_active" from the tip
exactly so that the agent CLI's session list orders the same way.

Add ``last_activity`` to the merge-from-tip key list, update the existing
test_compression_chain_collapses_to_latest_tip_in_sidebar assertion to
expect tip-derived updated_at, and add
test_compression_chain_bubbles_to_top_by_tip_activity locking in the
bubble-to-top invariant — without this regression test the previous
behaviour passed CI because no test exercised the sort order against a
mixed set of chains and standalone sessions.

The chain head's started_at (created_at) and title remain preserved, so
users can still find the conversation by its original date and name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: v0.50.216 release notes and version bump

Compression chains, renderer fixes, HTML preview, approval z-index, /steer fix.

* chore: gitignore local-only review harness directory

Adds .local-review/ to .gitignore so renderer drivers, sample inputs,
fixture builders, and other reviewer scratch files do not accidentally
get committed. Nothing under that path is ever shared in the repo;
keeping the entry tracked makes the boundary explicit for any future
contributor who creates the directory locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Keep reasoning chip visible for None effort

* test(reasoning): pin chip render output via node, not just source regex

The PR's static checks in test_reasoning_chip_btw_fixes.py validate the
shape of _applyReasoningChip (no display='none' literal, the right
classList.toggle call exists, the right label literals are in the
function body) but pass even if the runtime detail is wrong — for
example if `inactive` were inverted, _normalizeReasoningEffort
mishandled whitespace, or _formatReasoningEffortLabel returned the
wrong literal for an unknown input.

Add tests/test_reasoning_chip_js_behaviour.py — 11 tests that drive
the actual _applyReasoningChip() via node and assert on the rendered
DOM state for each effort value:

  TestChipAlwaysVisible
    - empty / null  -> "Default" label, inactive=true
    - "none"        -> "None" label, inactive=true
    - "low"/"high"  -> verbatim label, inactive=false
  TestNormalizationEdgeCases
    - "NONE"        -> normalises to "None"
    - "  none  "    -> trims and normalises
    - unknown junk  -> falls through visible, never hidden
  TestTitleAttributeAccessibility
    - title attribute carries the human-readable label for tooltip /
      screen-reader use

Sanity-checked against master's pre-fix ui.js: 11/11 fail (bug caught).
Against this PR's ui.js: 11/11 pass.

This pattern (drive the actual JS via node) caught two regex-only
regressions in PR #1073 where the Python mirror was correct while the
JS was broken. Same protection added here so the chip-visibility
contract can't silently break in a future refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add #1074 to v0.50.216 changelog, bump test count to 2428

* fix(i18n): restore broken Unicode in Russian and Spanish steer strings

Commit 56c7a14 (fix: update steer i18n and settings copy) accidentally
stripped the `\u` prefix from Unicode escape sequences in two locales,
producing garbled literal hex strings visible to users:

  Spanish (es):
    - cmd_steer:                   correcci00f3n  → corrección
    - cmd_steer_fallback:          2014 en cola   → — en cola
    - busy_steer_fallback:         2014 en cola   → — en cola
    - settings_desc_busy_input_mode: qu00e9, est00e1, correcci00f3n → qué, está, corrección
    - settings_busy_input_mode_steer: correcci00f3n  → corrección

  Russian (ru):
    - settings_desc_busy_input_mode: the entire Cyrillic string was
      replaced with raw 4-hex-char code-points without the \u prefix
      (041e043f... instead of actual Cyrillic). Decoded:
      "Определяет поведение при отправке сообщения во время работы
      агента. Очередь ждёт; Прерывание отменяет и начинает заново;
      Steer внедряет коррекцию без прерывания."

Fix: write the correct characters directly (UTF-8 is the file encoding
so embedding them literally is cleaner than \u escapes for long text).

All other locales (en, de, zh, zh-Hant) were not affected — confirmed
by grepping for bare hex run-ons in the updated file.

Verified: node --check static/i18n.js passes; full pytest suite green
(2365 passed, 47 skipped).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: remove duplicate compression chain entry from [Unreleased]

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Frank Song <franksong2702@gmail.com>
2026-04-25 21:06:31 -07:00

27 KiB
Raw Blame History

Hermes Web UI: Full Parity Roadmap

Goal: Full 1:1 parity with the Hermes CLI experience via a clean dark web UI. Everything you can do from the CLI terminal, you can do from this UI.

Last updated: v0.50.216 (April 26, 2026) — 2428 tests collected Tests: 2107 collected (pytest tests/ --collect-only -q) Source: /


Sprint History (Completed)

Sprint Theme Highlights Tests
Sprint 1 Bug fixes + foundations B1-B11 fixed, LOCK on SESSIONS, section headers, request logging 19
Sprint 2 Rich file preview Image preview, rendered markdown, table support, smart icons 27
Sprint 3 Panel nav + viewers Sidebar tabs, cron/skills/memory panels, B6/B10/B14, Phase D start 48
Sprint 4 Relocation + power features Source to /, CSS extracted, session rename/search, file ops 68
Sprint 5 Phase A complete + workspace JS extracted (server.py 1778->1042 lines), workspace management, copy message, file editor, session index 86
Test hardening Isolated test environment Port 8788 test server, conftest autouse, cleanup_zero_message, 5 test files rewritten 90
Sprint 6 Polish + Phase E complete HTML to static/, resizable panels, cron create, session JSON export, Escape from editor 106
Sprint 7 Wave 2 Core: CRUD + Search Cron edit/delete, skill create/edit/delete, memory write, session content search, health improvements, git init 125
Sprint 8 Daily Driver Finish Line Edit+regenerate user messages, regenerate last response, clear conversation, Prism.js syntax highlighting, reconnect banner fix, session list scroll fix 139
Sprint 8 hotfix Message queue + INFLIGHT fix Queue messages while busy (toast + badge + auto-drain), INFLIGHT-first loadSession (message stays on switch-away/back) 139
Sprint 9 Codebase health + daily driver gaps app.js deleted and replaced by 6 modules, tool call cards inline, attachment persistence on reload, todo list panel 149
Sprint 10 Server health + operational polish server.py split into api/ modules, background task cancel, cron run history viewer, tool card UX polish 167
Sprint 10 fixes Import regressions + regression tests uuid, AIAgent, has_pending, SSE cancel loop, Session.init tool_calls; test_regressions.py 177
Concurrency sweeps Multi-session correctness Approval cross-session (R10), activity bar per-session (R11), live cards on switch-back (R12), tool cards after done (R13), session model authoritative (R14), newSession cards (R15) 190
Sprint 11 Multi-provider models + streaming Dynamic model dropdown (any Hermes provider), smooth scroll pinning, routes extracted to api/routes.py (server.py 704→76 lines) 201
Sprint 12 Settings + reliability + session QoL Settings panel (gear icon, settings.json), SSE auto-reconnect, pin sessions, import session from JSON 211
Sprint 13 Alerts + polish Cron completion alerts (polling + badge), background error banner, session duplicate, browser tab title 221
Sprint 14 Visual polish + workspace ops Mermaid diagrams, message timestamps, file rename, folder create, session tags, session archive 233
Sprint 15 Session projects + code copy Session projects/folders, code block copy button, tool card expand/collapse toggle 237
Sprint 16 Session sidebar visual polish SVG action icons, session action dropdown, pin indicator, project border, safe HTML rendering 289
Sprint 17 Workspace polish + slash commands + settings Breadcrumb navigation, slash command autocomplete, send key setting (#26) 318
Sprint 18 Thinking display + workspace tree File preview auto-close, thinking/reasoning cards, expandable directory tree (#22) 318
Sprint 19 Auth + security hardening Password auth (off by default), login page, security headers, 20MB body limit (#23) 328
Sprint 20 Voice input + send button Voice input (Web Speech API), send button icon-circle with pop-in animation 415
Sprint 21 Mobile responsive + Docker Hamburger sidebar, mobile nav, files slide-over, Docker support (#21, #7) 415
Sprint 22 Multi-profile support Profile picker, management panel, seamless switching, per-session tracking (#28) 415
Sprint 23 Agentic transparency Token/cost display, subagent cards, skill picker in cron, skill linked files, workspace tree persistence, timestamp fixes 424
v0.44.0 patch Fix batch: approval card, login CSP, update diagnostics, Lucide icons PRs #221 #225 #226 #227 #228 579
v0.45.0 Custom endpoint in new profile form Base URL + API key fields; server-side URL validation; config.yaml merge; 9 new tests (PR #233, fixes #170) 604
v0.46.0 Security, Docker UID/GID, model discovery, i18n, cancel fix Credential redaction in API responses (PR #243); Docker UID/GID matching (PR #237); custom model API key discovery (PR #238); HTML entity decode + zh/zh-Hant i18n (PR #239); cancel interrupts agent (PR #244); +20 tests 624
v0.47.0 Dialogs, session menu, skills command, mobile fixes, mobile QA Shared app dialogs (#251); session ⋯ menu (#252); mobile QA suite (#254); custom provider slash routing fix (#255); Android Chrome mobile fixes (#256); /skills command (#257); +21 tests 645
v0.47.1 Spanish locale Full Spanish (es) locale, 175 keys, key-parity tests (#275 @gabogabucho); +3 tests 648
v0.48.0 Gateway session sync Real-time Telegram/Discord/Slack sessions in sidebar via SSE + DB polling (#274 @bergeouss); +10 tests 658
v0.48.1 Table inline formatting inlineMd() in table cells — bold, italic, code, links render correctly (PR #278); 0 new tests 658
v0.48.2 Provider mismatch warning Toast warning + auth_mismatch error type for provider/model mismatches (#283, fixes #266); +21 tests 679
v0.49.1 Docker docs + mobile Profiles button Two-container Docker compose (#291/#288); Profiles added to the mobile navigation flow with correct panel wiring and SVG sizing (#297/#265 @gabogabucho); +3 tests 700
v0.49.0 First-run onboarding wizard + self-update hardening One-shot bootstrap + guided setup wizard; provider config persisted to config.yaml + .env; OpenRouter/Anthropic/OpenAI/Custom; wizard hidden after completion (#285); self-update stderr/split-ref/conflict fixes (#287); skip flaky redaction test (#289); +18 tests 697
v0.32 Auto-compaction handling Compression detection, /compact command, real context window indicator 424
v0.33 /insights sync Opt-in state.db sync so hermes /insights includes WebUI sessions 424
v0.34 Sprint 26 — Pluggable themes Dark, Light, Slate, Solarized, Monokai, Nord; settings unsaved-changes guard; /theme command 433
v0.34.1 Theme variable polish 30+ hardcoded dark-navy colors replaced with theme-aware CSS variables 433
v0.34.2 Theme text colors 5 new per-theme typography variables (--strong, --em, --code-text, --code-inline-bg, --pre-text) 433
v0.34.3 Light theme final polish 46 light-scoped selector overrides for sidebar, roles, chips, interactive elements 433
v0.35 Security hardening Env race fix, random signing key, upload path traversal, PBKDF2 password hash 433
v0.36–v0.37 Model routing, personality config, tool card reload, duplicate model fixes Model routing by provider prefix, personality via config.yaml, tool cards reload on page refresh 466
v0.38.0–v0.38.6 Model selector, custom endpoints, OLED theme, reasoning display, insights sync Custom endpoint URL fix, OLED theme, top-level reasoning field fix, message_count sync to state.db 466
v0.39.0 Security hardening (Sprint 29) CSRF, PBKDF2, rate limiting, session ID validation, SSRF, ENV_LOCK, XSS, HMAC, skills traversal, secure cookie, error sanitization, startup warning 499
v0.40–v0.44.2 Approval card + Lucide icons + sprint auth Approval prompt surfaced in UI, emoji icons → Lucide SVG, login CSP inline fix, update diagnostics 579
v0.45–v0.46 Custom endpoints + security + i18n + cancel Custom endpoint Base URL + API key on profile create, credential redaction (PR #243), Docker UID/GID (PR #237), HTML entity decode + zh/zh-Hant i18n, cancel interrupts agent 624
v0.47–v0.47.1 Dialogs + session menu + skills + mobile QA + Spanish Shared app dialogs, session ⋯ menu, /skills command, mobile QA suite, Android Chrome fixes, Spanish locale (@gabogabucho) 648
v0.48–v0.48.2 Gateway session sync + table formatting + provider warnings Real-time Telegram/Discord/Slack sessions in sidebar (@bergeouss), inlineMd() in table cells, provider/model mismatch toast 679
v0.49–v0.49.1 Onboarding wizard + Docker two-container One-shot bootstrap + guided setup wizard, OpenRouter/Anthropic/OpenAI/Custom provider config, two-container Docker compose, mobile Profiles button 700
v0.50.0 v0.50.0 UI overhaul (Sprint 34) Composer-centric controls, Hermes Control Center modal, workspace panel state machine, collapsible date groups, rAF streaming throttle, context ring indicator (@aronprins) 742
v0.50.5–v0.50.10 Think-tag edge cases + onboarding hardening + mobile fixes MiniMax M2.5 leading-whitespace think-tag fix, skip-onboarding env var, OAuth provider path, Docker bridge networks fix, model dropdown dedup, title auto-generation fix, mobile close button 802
v0.50.11–v0.50.12 Chat table styles + URL autolink + profile env isolation .msg-body table borders, plain URL auto-linking, profile .env secret isolation on switch (prevents API key leakage across profiles, @Hinotoi-agent) 815
v0.50.13–v0.50.15 session_search + security sweep + KaTeX math SessionDB injection for session_search in WebUI (@DelightRun), bandit B310/B324/B110 + QuietHTTPServer (@lawrencel1ng), KaTeX math rendering with fence-before-math fix 871
v0.50.16–v0.50.17 CSRF reverse proxy + Docker uv pre-install Scheme-aware CSRF port normalization for non-standard ports (@lx3133584), Docker uv pre-installed at build time as root (fixes air-gapped startup, @mmartial-pattern) 900
v0.50.18–v0.50.19 Workspace fallback + Unicode filenames Cascading workspace path recovery (@Jordan-SkyLF), Unicode Content-Disposition headers with RFC 5987 filename* (@shaoxianbilly), silent auth error surfacing, stale model cleanup 924
v0.50.20–v0.50.21 Silent errors + live model fetching + durable streaming recovery apperror on empty agent response, /api/models/live endpoint with SSRF guard, live reasoning cards, tool_complete SSE events, SESSION_QUEUES, localStorage reload recovery (@Jordan-SkyLF) 961
v0.50.22–v0.50.36-local.1 Upstream sync + minimal local patch retention Synced to upstream v0.50.36; retained first-password session continuity in Settings/onboarding; removed local Assistant Reply Language enhancement; added legacy settings cleanup regression coverage 1059
v0.50.37–v0.50.40 Sprint 40 — rendering fixes + KaTeX CSP + MEDIA images Think-tag edge cases, renderMd link double-linking fix, MEDIA: inline image rendering, KaTeX CSP font-src fix 1117
v0.50.41–v0.50.43 Sprint 41/42 — context ring, session polish, renderMd hardening Context indicator live usage, session display fixes, renderMd bold+code stash, outer link pass ordering, _ob_stash, autolink double-link fixes (@multiple contributors) 1150
v0.50.44 Renderer formatting bug fixes (#486, #487) CSS: inline code sizing in table cells; JS: markdown image syntax alt → in renderMd + inlineMd; _img_stash for autolink protection 1195
v0.50.45–v0.50.100 Upstream sync + contributor sprint Sidebar declutter, SKIP_ONBOARDING, runtime route details, subpath mount, bug batch (light theme/panel/model cache/Docker), Docker UID/GID auto-detect, chat transcript redesign, favicon SVG+PNG+ICO, Docker UID-mismatch crash fix, auto-title markdown strip 1777
v0.50.101–v0.50.139 Contributor sprint wave Custom providers, Russian locale, collapsed timestamps, IME composition fixes, model-switch toast, approval queue multi-slot, live model fetching SSRF guard, orphaned tool-message sanitization, profile polish sprint (model routing, workspace cross-profile, legacy session backfill), font-size CSS fix 1777
v0.50.140–v0.50.147 Bug batch + appearance Font size setting visibly scales UI text (#843), slash command echoed as user message (#840), scroll selected item into view (#838), tasks refresh button (#835), font size toggle (#833), stale model fix (#829), session search clear on boot (#822), gateway SSE polling fallback (#635) 1858
v0.50.148–v0.50.150 Session index + read-path + profile Prune stale _index.json ghost rows after session-id rotation (#847 @franksong2702), GET /api/session side-effect-free model resolution (#848 @franksong2702), profile switching cookie persist + syncTopbar fix (#849 @migueltavares) 1858
v0.50.151 credential_pool + Ollama Cloud Providers added via auth store credential_pool now visible in model dropdown; Ollama Cloud support; ambient gh-cli token suppression; _apply_provider_prefix helper (#820 @starship-s) 1898
v0.50.152 Image rendering + auto-title image_generate MEDIA: token renders all https:// URLs as img regardless of extension (closes #853); auto-title strips Qwen3-style plain-text thinking preambles (closes #857) 1898
v0.50.153 Portal model routing Live-fetched models from portal providers (Nous, OpenCode) now get @provider: prefix so they route correctly instead of falling through to OpenRouter (closes #854) 1898
v0.50.154 Thinking card mirror fix _streamDisplay() early return removed — thinking card and main response now show distinct content when provider double-emits (closes #852) 1898
v0.50.155 Honcho session stability gateway_session_key=session_id passed to AIAgent so Honcho per-session strategy maintains one Honcho session per WebUI chat instead of one per turn (closes #855) 1903
v0.50.156 Auto-install security gate auto_install_agent_deps() is now opt-in; set HERMES_WEBUI_AUTO_INSTALL=1 to enable; _trusted_agent_dir() checks ownership/permission bits before running pip (⚠️ breaking: default changed) 1903

Current Architecture Status

Layer Location Status
Python server /server.py (~165 lines) + api/ modules (~5000 lines) Thin shell + QuietHTTPServer + auth middleware + business logic in api/
HTML template /static/index.html (~600 lines) Served from disk
CSS /static/style.css (~1050 lines) Served from disk, incl. mobile responsive, KaTeX, table styles
JavaScript /static/{ui,workspace,sessions,messages,panels,boot,commands,icons,i18n,login}.js 10 modules, ~7100 lines total
Docker Dockerfile, docker-compose.yml, .dockerignore python:3.12-slim, multi-arch (amd64+arm64)
CI/CD .github/workflows/release.yml Auto-release + GHCR publish on tag push
Runtime state ~/.hermes/webui-mvp/sessions/ Session JSON files
Test server Port 8788 (conftest.py), port 8789 (browser sanity) Isolated, wiped per run
Production server Port 8787 SSH tunnel from Mac

Feature Parity Checklist

Chat and Agent

  • Send messages, get SSE-streaming responses
  • Switch models per session (10 models, grouped by provider)
  • Composer-scoped model picker in footer (moved from sidebar to align with per-conversation model selection)
  • Multi-provider API support: use any Hermes agent API provider (OpenAI, Anthropic, Google, etc.) directly, not just OpenRouter (Sprint 11)
  • Custom endpoint model discovery: auto-detect models from Ollama, LM Studio, and other local LLM servers via base_url (PR #18)
  • Upload files to workspace (drag-drop, click, clipboard paste)
  • File tray with remove button
  • Tool progress shown inline in the conversation via live tool cards
  • Approval card for dangerous commands (Allow once/session/always, Deny)
  • Approval polling + SSE-pushed approval events
  • INFLIGHT guard: switch sessions mid-request without losing response
  • Session restores from localStorage on page load
  • Reconnect banner if page reloaded mid-stream
  • Copy message to clipboard (hover icon on each bubble)
  • Edit last user message and regenerate
  • Branch/fork conversation (Wave 3)
  • Token/cost estimate per message (Sprint 23)

Tool Visibility

  • Tool progress in live tool cards (kept out of the composer/footer chrome)
  • Approval card with all 4 choices
  • Tool call cards inline (collapsed, show name/args/result)

Workspace / Files

  • Workspace panel defaults closed and opens only for active browsing or preview
  • Browse workspace directory tree with type icons
  • Preview text/code files (read-only)
  • Preview markdown files (rendered, tables supported)
  • Preview image files (PNG, JPG, GIF, SVG, WEBP inline)
  • Edit files inline (Edit button, Enter to save, Escape to cancel)
  • Create new file (+ button in panel header)
  • Delete file (hover trash, confirmation modal)
  • File name truncation with tooltip for long names
  • Right panel resizable (drag inner edge)
  • Syntax highlighted code preview (Prism.js)
  • Rename file (Sprint 14)
  • Create folder (Sprint 14)
  • Shared app modal for confirm/input flows (Sprint 33)

Sessions

  • Create session (+ button or Cmd/Ctrl+K)
  • Load session (click in sidebar)
  • Delete session (hover trash, toast, correct fallback)
  • Auto-title from first user message
  • Rename session title (double-click in sidebar, Enter saves, Escape cancels)
  • Filter/search sessions by title (live filter box)
  • Date group headers (Today / Yesterday / Earlier)
  • Download session as Markdown transcript
  • Export session as JSON (full messages + metadata)
  • Session inherits last-used workspace on creation
  • Session content search (search message text across sessions)
  • Session tags / labels (Sprint 14)
  • Archive sessions (Sprint 14)
  • Clear conversation (wipe messages, keep session) (Wave 3)
  • Import session from JSON (Sprint 12)
  • Pin/star sessions to top of list (Sprint 12)
  • Duplicate session (Sprint 13)
  • Session projects / folders (Sprint 15)

Workspace Management

  • Add workspace with path validation (must be existing directory)
  • Remove workspace
  • Rename workspace display name
  • Quick-switch workspace from topbar dropdown
  • Sidebar live workspace display (name + path, updates in real time)
  • New sessions inherit last used workspace
  • Workspace list persists to workspaces.json
  • Workspace reorder (drag) (Wave 2)

Scheduled Tasks (Cron)

  • View all cron jobs (Tasks sidebar tab)
  • View last run output per job (auto-loaded on expand)
  • Expand job to see prompt, schedule, last output
  • Run job manually (Run now button)
  • Pause / Resume job
  • Create cron job from UI (+ New job form with name, schedule, prompt, delivery)
  • Edit existing cron job
  • Delete cron job
  • View full cron run history (expandable per job)
  • Skill picker in cron create form (Sprint 23)

Skills

  • List all skills grouped by category (Skills sidebar tab)
  • Search/filter skills by name, description, category
  • View full SKILL.md content in right preview panel
  • Create skill
  • Edit skill
  • Delete skill
  • View skill linked files (Sprint 23)

Memory

  • View personal notes (MEMORY.md) rendered as markdown (Memory tab)
  • View user profile (USER.md) rendered as markdown (Memory tab)
  • Last-modified timestamp on each section
  • Add/edit memory entry inline

Configuration

  • Settings panel (default model, default workspace) (Sprint 12)
  • Send key preference (Enter or Ctrl+Enter) (Sprint 17)
  • Password authentication (Sprint 19)
  • Enable/disable toolsets per session (deferred)

Notifications

  • Cron job completion alerts (Sprint 13)
  • Background agent error alerts (Sprint 13)

Workspace

  • Breadcrumb navigation in subdirectories (Sprint 17)
  • Workspace tree view with expand/collapse (Sprint 18, Issue #22)
  • File preview auto-close on directory navigation (Sprint 18)

Slash Commands

  • Command registry + autocomplete dropdown (Sprint 17)
  • Built-in: /help, /clear, /model, /workspace, /new (Sprint 17)

Security

  • Password auth with signed cookies (Sprint 19, Issue #23)
  • Security headers (X-Content-Type-Options, X-Frame-Options) (Sprint 19)
  • POST body size limit (20MB) (Sprint 19)

Thinking / Reasoning

  • Collapsible thinking cards for extended-thinking models (Sprint 18)

Voice

  • Voice input via Web Speech API (Sprint 20)

Mobile

  • Mobile responsive layout — hamburger sidebar, sidebar tabs on phones, files slide-over (Sprint 21 + later mobile nav simplification)

Profiles

  • Multi-profile support — create, switch, delete profiles (Sprint 22, Issue #28)

Advanced / Future

  • Subagent session tree -- show subagent hierarchy in sidebar with expand/collapse (PR #75)
  • Specialized tool card renderers -- diff viewer, terminal output, todo checklist views (PR #75)
  • Streaming performance -- rAF-throttled token rendering (Sprint 24, PR #81)
  • Workspace git detection -- branch name and dirty status badge (Sprint 24, PR #82)
  • Collapsible date groups -- click group headers to collapse (Sprint 24, PR #80)
  • Context usage indicator -- compact circular badge in composer footer (Sprint 24, PR #83; refreshed April 10, 2026)
  • LLM-generated session titles -- auto-title via small model instead of first-message substring (PR #75)
  • Workspace git detection -- show branch name, dirty status in workspace header (PR #75)
  • Clarify dialog -- agent can ask clarifying questions that block until user responds (PR #75)
  • Gateway approval polling -- support blocking approvals from messaging gateway (PR #75)
  • Unified session storage -- SessionDB shared between webui and CLI (PR #75)
  • TTS playback of responses (deferred)
  • Background task cancel (composer footer stop button)
  • Code execution cell (deferred)
  • Desktop application (Sprint 25, PLANNED)
  • Pluggable UI themes -- Dark, Light, Slate, Solarized, Monokai, Nord (Sprint 26, v0.34)
  • Extended slash command / skill integration (deferred)
  • Virtual scroll for large lists (deferred)

Sprint 7: Wave 2 Core -- Cron/Skill/Memory CRUD + Session Content Search (COMPLETED)

Theme: "Wave 2 Core -- Cron/Skill/Memory CRUD + Session Content Search"

Track A: Bug Fixes

Item Description
Activity bar sizing Activity bar sometimes overlaps first message on short viewports
Model dropdown sync Model chip in topbar sometimes shows stale model after session switch
Cron output truncation Long cron output in the tasks panel overflows its container

Track B: Features

Feature What Value
Session content search Search message text across all sessions, not just titles. GET /api/sessions/search already does title search; extend to message content with a configurable depth limit High: the single most-requested nav feature after rename
Cron edit + delete Edit an existing cron job (name, schedule, prompt, delivery) inline in the tasks panel. Delete with confirm. POST /api/crons/update and /api/crons/delete High: closes the cron CRUD gap (create was Sprint 6)
Skill create + edit A "New skill" form in the Skills panel. Name, category, SKILL.md content in a textarea editor. Save calls POST /api/skills/save (writes to ~/.hermes/skills/). Edit opens existing skill in the same editor High: biggest remaining CLI gap after cron

Track C: Architecture

Item What
Phase E: app.js module split (start) Split app.js (1332 lines) into logical modules: sessions.js, chat.js, workspace.js, panels.js, ui.js. Serve via ES module imports in index.html. This is Phase E completion.
Health endpoint improvement Add active_streams, uptime_seconds to /health response (Phase G)
Git init git init , first commit, push to private GitHub repo

Tests

  • ~20 new pytest tests (cron update/delete, skill save, session content search)
  • TESTING.md: Sections 29-31 (cron edit, skill edit, session search)
  • Estimated total after Sprint 7: ~126

Wave 2: Full CRUD and Interaction Parity

Status: In progress. Sprint 6 completed cron create and workspace management. Remaining Wave 2 items targeted for Sprints 7-8.

Sprint 2.0: Workspace Management (COMPLETE Sprint 5+6)

All workspace features delivered: add/validate/remove/rename workspaces, topbar quick-switch, sidebar live display, new sessions inherit last workspace. See Sprint 5 completed section.

Sprint 2.1: Cron Job Management (Partial -- Sprint 7 for remaining)

  • View all jobs (Sprint 3)
  • Run / pause / resume (Sprint 3)
  • Create job from UI (Sprint 6)
  • Edit job
  • Delete job
  • Full cron run history

Sprint 2.2: Skill Management (Partial -- Sprint 7 for remaining)

  • List all skills with categories (Sprint 3)
  • View SKILL.md content (Sprint 3)
  • Create skill
  • Edit skill
  • Delete skill

Sprint 2.3: Memory Write (Sprint 7)

  • View notes + profile (Sprint 3)
  • Edit notes inline

Sprint 2.4: Todo Management (Wave 2)

  • View current todo list (sidebar Todo panel, parsed from session history)

Sprint 2.5: Session Content Search (Sprint 7)

  • Session title search (Sprint 4)
  • Message content search across sessions

Sprint 2.6: Session Rename (COMPLETE Sprint 4)

Double-click any session title in the left sidebar to edit inline. Enter saves, Escape cancels. Topbar updates immediately.


Completed Waves (Summary)

Wave Theme Key Deliverables
Wave 2 Full CRUD + Interaction Cron/skill/memory CRUD, session search, workspace management, session rename
Wave 3 Power Features Tool call cards, multi-model dropdown, resizable panels, file actions, conversation controls
Wave 4 Settings + Notifications Settings panel, cron alerts, background error banner
Wave 5 Session Continuity Session tags, archive, projects/folders
Wave 6 Agentic Features Background task cancel, voice input (Web Speech API)
Wave 7 Production Hardening Password auth, security headers, mobile responsive, Docker + GHCR CI

User Requested Features

Community-requested enhancements tracked from GitHub issues. All shipped.

Feature Issue Shipped Sprint
Workspace tree view #22 Done Sprint 18
Docker container + GHCR images #7 Done Sprint 21 + v0.28.1 CI
Authentication #23 Done Sprint 19
Send key / personalization #26 Done Sprint 17
Multi-profile support #28 Done Sprint 22
Mobile responsive UI #21 Done Sprint 21
Profile creation in Docker #44 Done v0.27