1240 lines
61 KiB
Markdown
1240 lines
61 KiB
Markdown
# Hermes Web UI -- Changelog
|
|
|
|
> Living document. Updated at the end of every sprint.
|
|
> Repository: https://github.com/nesquena/hermes-webui
|
|
|
|
---
|
|
|
|
## [v0.35.1] Model dropdown fixes
|
|
*April 5, 2026 | 433 tests*
|
|
|
|
### Bug Fixes
|
|
- **Custom providers invisible in model dropdown (#117).** `cfg_base_url` was scoped inside a conditional block but referenced unconditionally, causing a `NameError` for users with a `base_url` in config.yaml. Fix: initialize to `''` before the block. (#118)
|
|
- **Configured default model missing from dropdown (#116).** OpenRouter and other providers replaced the model list with a hardcoded fallback that didn't include `model.default` values like `openrouter/free` or custom local model names. Fix: after building all groups, inject the configured `default_model` at the top of its provider group if absent. (#119)
|
|
|
|
---
|
|
|
|
## [v0.35] Security hardening
|
|
*April 5, 2026 | 433 tests*
|
|
|
|
### Security fixes
|
|
- **ENV race condition (HIGH):** Two concurrent sessions could interleave `os.environ` writes, clobbering workspace and session keys. Fixed with a global `_ENV_LOCK` in `streaming.py` that serializes the env save/restore block across all sessions. (#108)
|
|
- **Predictable signing key (MEDIUM):** Session cookies were signed with `sha256(STATE_DIR)` -- deterministic and forgeable if the install path is known. Now generates a cryptographically random 32-byte key on first startup, persisted to `STATE_DIR/.signing_key` (chmod 600). (#108)
|
|
- **Upload path traversal (MEDIUM):** Filenames like `..` survived the `[^\w.\-]` sanitization regex because dots are allowed. Fixed by rejecting dot-only filenames and validating the resolved path stays within the workspace sandbox via `safe_resolve_ws()`. (#108)
|
|
- **Weak password hashing (MEDIUM):** Bare SHA-256 with a predictable salt replaced with PBKDF2-SHA256 at 600k iterations (OWASP recommendation) using the random signing key as salt. No new dependencies (stdlib `hashlib.pbkdf2_hmac`). (#108)
|
|
|
|
**Breaking change:** Existing session cookies and password hashes are invalidated on first restart after upgrade. Users with password auth enabled will need to re-set their password.
|
|
|
|
---
|
|
|
|
## [v0.34.3] Light theme final polish
|
|
*April 5, 2026 | 433 tests*
|
|
|
|
### Bug Fixes
|
|
- **Light theme: sidebar, role labels, chips, and interactive elements all broken.** Session titles were too faint, active session used washed-out gold, pin stars were near-invisible bright yellow, and all hover/border effects used dark-theme white `rgba(255,255,255,.XX)` values invisible on cream. Fixed with 46 scoped `[data-theme="light"]` selector overrides covering session items, role labels, project chips, topbar chips, composer, suggestions, tool cards, cron list, and more. (#105)
|
|
- Active session now uses blue accent (`#2d6fa3`) for strong contrast. Pin stars use deep gold (`#996b15`). Role labels are solid and high contrast.
|
|
|
|
---
|
|
|
|
## [v0.34.2] Theme text colors
|
|
*April 5, 2026 | 433 tests*
|
|
|
|
### Bug Fixes
|
|
- **Light mode text unreadable.** Bold text was hardcoded white (invisible on cream), italic was light purple on cream, inline code had a dark box on a light background. Fixed by introducing 5 new per-theme CSS variables (`--strong`, `--em`, `--code-text`, `--code-inline-bg`, `--pre-text`) defined for every theme. (#102)
|
|
- Also replaced remaining `rgba(255,255,255,.08)` border references with `var(--border)`, and darkened light theme `--code-bg` slightly for better contrast.
|
|
|
|
---
|
|
|
|
## [v0.34.1] Theme variable polish
|
|
*April 5, 2026 | 433 tests*
|
|
|
|
### Bug Fixes
|
|
- **All non-dark themes had broken surfaces, topbar, and dropdowns.** 30+ hardcoded dark-navy rgba/hex values in style.css were stuck on the Dark palette regardless of active theme. Fixed by introducing 7 new CSS variables (`--surface`, `--topbar-bg`, `--main-bg`, `--input-bg`, `--hover-bg`, `--focus-ring`, `--focus-glow`) defined per-theme, replacing every hardcoded reference. (#100)
|
|
|
|
---
|
|
|
|
## [v0.34] Sprint 26 -- Pluggable UI Themes
|
|
*April 5, 2026 | 433 tests*
|
|
|
|
### Features
|
|
- **6 built-in themes.** Dark (default), Light, Slate, Solarized Dark, Monokai,
|
|
Nord. Defined as CSS variable overrides on `:root[data-theme="name"]` — the
|
|
entire UI adapts automatically.
|
|
- **Theme picker in Settings.** Dropdown with instant live preview. Changes
|
|
apply immediately as you click through options.
|
|
- **`/theme` slash command.** `/theme dark`, `/theme light`, etc.
|
|
- **Theme persistence.** Saved server-side in `settings.json` and client-side
|
|
in `localStorage` for flicker-free loading on page refresh.
|
|
- **Flash prevention.** Inline `<script>` in `<head>` reads localStorage before
|
|
the stylesheet loads — no flash of the wrong theme.
|
|
- **Custom theme support.** Any theme name is accepted (no enum gate). Create a
|
|
`:root[data-theme="name"]` CSS block and it works. See `THEMES.md`.
|
|
- **Unsaved changes guard.** Settings panel now tracks dirty state and shows a
|
|
"You have unsaved changes" bar with Save/Discard buttons when closing with
|
|
unpersisted changes. Theme preview reverts on discard.
|
|
|
|
### Architecture
|
|
- `static/style.css`: 6 theme blocks using CSS variable overrides. Light theme
|
|
includes scrollbar and selection overrides.
|
|
- `static/commands.js`: `/theme` command with validation.
|
|
- `static/panels.js`: Settings dirty tracking, revert-on-discard, unsaved bar.
|
|
- `static/boot.js`: Theme applied from server settings on boot.
|
|
- `api/config.py`: `theme` field in `_SETTINGS_DEFAULTS` (no enum gate).
|
|
- `THEMES.md`: Full documentation for creating custom themes.
|
|
|
|
### Tests
|
|
- 9 new tests in `test_sprint26.py`: default theme, round-trip persistence for
|
|
all 6 built-in themes, custom theme acceptance, settings isolation.
|
|
Total: **433 tests**.
|
|
|
|
---
|
|
|
|
## [v0.33] /insights Sync + state.db Bridge Fix
|
|
*April 5, 2026 | 424 tests*
|
|
|
|
### Features
|
|
- **Opt-in `/insights` sync.** New "Sync usage to /insights" setting (default: off). When enabled, after each turn the WebUI mirrors session token usage, cost, model, and title into `state.db` so `hermes /insights` includes browser session activity. (#92, #93)
|
|
|
|
### Bug Fixes
|
|
- **state_sync.py correctness fixes.** Three bugs in the initial implementation caught during code review: wrong class name (`HermesState` → `SessionDB`), wrong constructor argument type (`str` → `Path`), wrong title update method (`_execute_write` with bad signature → `set_session_title`). Also fixed a SQLite connection leak (persistent connection opened per call, never closed). (#95)
|
|
|
|
---
|
|
|
|
## [v0.32] Auto-Compaction Handling + /compact Command (Issue #90)
|
|
*April 5, 2026 | 424 tests*
|
|
|
|
### Features
|
|
- **Auto-compaction detection.** When the agent's `run_conversation()` triggers
|
|
context compression and rotates the session ID, the WebUI detects the mismatch
|
|
and renames the session file + cache entry so messages don't split across files.
|
|
- **`compressed` SSE event.** Frontend receives a notification when compression
|
|
fires, shows a system message ("Context was auto-compressed") and a toast.
|
|
- **`/compact` slash command.** Type `/compact` to request the agent compress
|
|
the conversation context. Sends a natural-language message that triggers the
|
|
agent's compression preflight.
|
|
- **Real context window data.** The context usage indicator now uses actual
|
|
`context_length`, `threshold_tokens`, and `last_prompt_tokens` from the agent's
|
|
compressor instead of the client-side model name lookup. Tooltip shows the
|
|
auto-compress threshold. Hides gracefully when the agent has no compressor.
|
|
|
|
### Architecture
|
|
- `api/streaming.py`: Session ID mismatch detection after `run_conversation()`,
|
|
file rename, SESSIONS cache update under lock, `compressed` SSE event,
|
|
`context_length`/`threshold_tokens`/`last_prompt_tokens` in usage dict.
|
|
- `static/commands.js`: `/compact` command.
|
|
- `static/messages.js`: `compressed` SSE event handler.
|
|
- `static/ui.js`: `_syncCtxIndicator()` rewritten to use server-side compressor
|
|
data instead of client-side model estimates.
|
|
|
|
---
|
|
|
|
## [v0.31.2] CLI session delete fix
|
|
*April 5, 2026 | 424 tests*
|
|
|
|
### Bug Fixes
|
|
- **CLI sessions could not be deleted from the sidebar.** The delete handler only
|
|
removed the WebUI JSON session file, so CLI-backed sessions came back on refresh.
|
|
Added `delete_cli_session(sid)` in `api/models.py` and call it from
|
|
`/api/session/delete` so the SQLite `state.db` row and messages are removed too.
|
|
(#87, #88)
|
|
|
|
### Notes
|
|
- The public test suite still passes at 424/424.
|
|
- Issue #87 already had a comment confirming the root cause, so no new issue comment
|
|
was needed here.
|
|
|
|
## [v0.31] UI Polish + Deployment Hardening
|
|
*April 4, 2026 | 424 tests*
|
|
|
|
### Bug Fixes
|
|
- **Profile dropdown overlaps chat messages.** `.topbar` had no stacking context,
|
|
causing the dropdown to paint over `.messages`. Added `position:relative;z-index:10`
|
|
to `.topbar`. (#71)
|
|
- **Workspace dropdown clipped by sidebar.** `.sidebar overflow:hidden` swallowed
|
|
the upward-opening workspace dropdown entirely. Changed to `overflow:visible`
|
|
(scroll lives on `.session-list`); added `position:relative;z-index:10` to
|
|
`.sidebar-bottom`. (#71)
|
|
- **Slash-command autocomplete behind tool cards.** `.composer-wrap` had
|
|
`position:relative` but no `z-index`, letting tool cards bleed over it.
|
|
Added `z-index:10`. (#71)
|
|
- **Skill picker clipped inside Settings modal.** `.settings-panel overflow-y:auto`
|
|
clipped the absolute-positioned skill picker. Moved scroll to `.settings-body`,
|
|
set panel to `overflow:visible`, raised skill picker to `z-index:1100`. (#71)
|
|
- **CLI session badge blocks action buttons on hover.** Added
|
|
`.session-item.cli-session:hover::after { display:none }` so the gold "cli"
|
|
label hides on hover, making archive/delete/pin fully reachable. (#71)
|
|
- **Workspace dropdown name and path crowded on same line.** `.ws-opt` was a plain
|
|
block with inline spans. Added `flex-direction:column;gap:4px` so name and path
|
|
stack cleanly. (#71)
|
|
- **Both servers sharing same state directory.** `api/config.py` and `start.sh`
|
|
both defaulted to `~/.hermes/webui-mvp` (an internal dev name). Changed default
|
|
to `~/.hermes/webui` -- generic, appropriate for any deployment. Override with
|
|
`HERMES_WEBUI_STATE_DIR`. (#72, #73)
|
|
|
|
---
|
|
|
|
## [v0.30.1] CLI Session Bridge Fixes
|
|
*April 4, 2026 | 424 tests*
|
|
|
|
### Bug Fixes
|
|
- **CLI sessions not appearing in sidebar.** Three frontend gaps: `sessions.js`
|
|
wasn't rendering CLI sessions (missing `is_cli_session` check in render loop),
|
|
sidebar click handler didn't trigger import, and the "cli" badge CSS selector
|
|
wasn't matching the rendered DOM structure. (#58)
|
|
- **CLI bridge read wrong profile's state.db.** `get_cli_sessions()` resolved
|
|
`HERMES_HOME` at server launch time, not at call time. After a profile switch,
|
|
it kept reading the original profile's database. Now resolves dynamically via
|
|
`get_active_hermes_home()`. (#59)
|
|
- **Silent SQL error swallowed all CLI sessions.** The `sessions` table in
|
|
`state.db` has no `profile` column — the query referenced `s.profile` which
|
|
caused a silent `OperationalError`. The `except Exception: return []` handler
|
|
swallowed it, returning zero CLI sessions. Removed the column reference and
|
|
added explicit column-existence checks. (#60)
|
|
|
|
### Features
|
|
- **"Show CLI sessions" toggle in Settings.** New checkbox in the Settings panel
|
|
to show/hide CLI sessions in the sidebar. Persisted server-side in
|
|
`settings.json` (`show_cli_sessions`, default `true`). When disabled, CLI
|
|
sessions are excluded from `/api/sessions` responses. (#61)
|
|
|
|
---
|
|
|
|
## [v0.30] CLI Session Bridge (Community: @thadreber-web)
|
|
*April 4, 2026 | 424 tests*
|
|
|
|
### Features
|
|
- **CLI session bridge.** The WebUI now reads sessions from the hermes-agent's
|
|
SQLite store (`state.db`). CLI sessions appear in the sidebar with a gold
|
|
"cli" indicator badge. Click to import into the WebUI store with full message
|
|
history — replies then work through the normal agent pipeline.
|
|
- **`/api/session/import_cli` endpoint.** Imports a CLI session into the WebUI
|
|
JSON store. Idempotent — returns existing session if already imported.
|
|
Derives title from first message, inherits active profile and workspace.
|
|
- **`/api/sessions` merges CLI sessions.** Sidebar shows both WebUI and CLI
|
|
sessions sorted by last activity. Deduplication ensures WebUI sessions take
|
|
priority when the same session_id exists in both stores.
|
|
- **CLI session fallback on `/api/session`.** If a session_id isn't found in
|
|
the WebUI store, falls back to reading from the CLI SQLite store.
|
|
|
|
### Architecture
|
|
- `api/models.py`: `get_cli_sessions()`, `get_cli_session_messages()`,
|
|
`import_cli_session()`. All use parameterized SQL queries and `with` for
|
|
connection management. Graceful fallback on missing sqlite3 or state.db.
|
|
- `api/routes.py`: CLI fallback in GET `/api/session`, merged list in
|
|
GET `/api/sessions`, POST `/api/session/import_cli`.
|
|
- `static/style.css`: `.cli-session` indicator styles (gold border + badge).
|
|
|
|
---
|
|
|
|
## [v0.29] Sprint 23: Agentic Transparency + Polish
|
|
*April 4, 2026 | 424 tests*
|
|
|
|
### Features
|
|
|
|
- **Token/cost display.** Agent usage (input tokens, output tokens, estimated
|
|
cost) is now read after each conversation and persisted on the session.
|
|
A muted badge appears below the last assistant message when enabled.
|
|
Off by default — toggle via the Settings panel checkbox or `/usage` slash
|
|
command. Persists server-side across refreshes.
|
|
|
|
- **Subagent delegation cards.** `subagent_progress` events now render with
|
|
a 🔀 icon and a blue indented left border to visually distinguish child
|
|
tool activity from parent tool calls. `delegate_task` cards display as
|
|
"Delegate task" with cleaner formatting.
|
|
|
|
- **Skill picker in cron create form.** The "New Job" form now has a search
|
|
input + tag chip picker for attaching skills to cron jobs. Skills fetched
|
|
from `/api/skills`, filtered on keyup, added/removed as tag chips.
|
|
`submitCronCreate()` sends `skills` array in the POST body. Backend already
|
|
supported the field — this was a pure frontend gap.
|
|
|
|
- **Skill linked files viewer.** Skill preview panel now renders a "Linked
|
|
Files" section below SKILL.md content when a skill has `references/`,
|
|
`templates/`, `scripts/`, or `assets/` subdirectories. Clicking a file
|
|
loads it in the preview panel with syntax highlighting.
|
|
New `file` query param on `GET /api/skills/content` serves linked files
|
|
with path traversal protection.
|
|
|
|
- **Workspace tree state persists across refreshes.** Expanded directory
|
|
paths are saved to `localStorage` keyed by workspace path
|
|
(`hermes-webui-expanded:{path}`). On every root load (page refresh,
|
|
session switch), the saved state is restored and previously-expanded
|
|
directories are pre-fetched so the tree renders fully on first paint.
|
|
|
|
- **Timestamps fixed.** `api/streaming.py` now stamps `timestamp` on every
|
|
message that lacks one at conversation completion. The `done` SSE event
|
|
also stamps `_ts` on the last assistant message immediately. Timestamps
|
|
were already rendered in the UI (Sprint 14, hover-to-reveal) but most
|
|
messages had no timestamp field, so nothing ever showed.
|
|
|
|
- **`/usage` slash command.** Instant toggle for token usage display.
|
|
Shows a toast, persists to server, updates the Settings checkbox if open,
|
|
re-renders immediately.
|
|
|
|
### Bug Fixes
|
|
|
|
- **XSS via inline onclick + esc().** Skill names and file paths embedded in
|
|
`onclick` HTML attributes used `esc()` for encoding. `esc()` converts `'`
|
|
to `'` (HTML-safe) but browsers decode it back before executing JS,
|
|
allowing skill names with apostrophes to break out of string literals.
|
|
Fixed by switching to `data-*` attributes + `addEventListener`.
|
|
|
|
- **rglob wildcard injection.** The `name` query param for
|
|
`/api/skills/content?file=` was passed directly to `SKILLS_DIR.rglob()`,
|
|
which accepts glob patterns. `name=*` would match an arbitrary directory
|
|
and use it as the trust base for path traversal checking.
|
|
Fixed by rejecting names containing `* ? [ ]` metacharacters with 400.
|
|
|
|
- **`_fmtTokens(null)` returned "null".** `String(null)` = `"null"` would
|
|
appear in the usage badge for sessions missing fields. Fixed with a
|
|
`!n || n < 0` guard returning `'0'`.
|
|
|
|
- **Usage badge on wrong row.** Badge used `:last-child` which could target
|
|
a user message row. Fixed by adding `data-role` to message rows and
|
|
scanning backwards for the last `assistant` row.
|
|
|
|
- **Tool name resolution.** Tool call entries in session JSON sometimes
|
|
stored the literal string `"tool"` as the name when the call ID couldn't
|
|
be resolved. Fixed: defaults to empty string and skips unresolvable entries.
|
|
|
|
- **Inline import inside loop.** `import json as _j2` inside the done-handler
|
|
loop in `streaming.py` moved to module-level.
|
|
|
|
### Session Model
|
|
|
|
- Added `input_tokens`, `output_tokens`, `estimated_cost` fields to Session
|
|
(defaults: 0, 0, None). Included in `compact()`, session JSON, and all
|
|
API responses. Backward-compatible via `**kwargs`.
|
|
|
|
- Added `args` capture to `tool_calls` session JSON entries (truncated
|
|
snapshot of tool inputs, up to 6 keys / 120 chars each).
|
|
|
|
### Settings
|
|
|
|
- New `show_token_usage` boolean setting (default: `false`). Stored in
|
|
`settings.json`, loaded on boot alongside `send_key`.
|
|
|
|
### Tests
|
|
|
|
- Renamed `test_sprint24.py` → `test_sprint23.py`.
|
|
- Strengthened session usage assertions (explicit field presence checks).
|
|
- Added: path traversal rejection test, wildcard name rejection test,
|
|
cron create with skills array test.
|
|
- Total: 424 tests (up from 415).
|
|
|
|
---
|
|
|
|
## [v0.28.1] CI Pipeline + Multi-Arch Docker Builds
|
|
*April 3, 2026 | 426 tests*
|
|
|
|
### Features
|
|
- **GitHub Actions CI.** New workflow triggers on tag push (`v*`). Builds
|
|
multi-arch Docker images (linux/amd64 + linux/arm64), pushes to
|
|
`ghcr.io/nesquena/hermes-webui`, and creates a GitHub Release with
|
|
auto-generated release notes. Uses GHA layer caching for fast rebuilds.
|
|
- **Pre-built container images.** Users can now `docker pull ghcr.io/nesquena/hermes-webui:latest`
|
|
instead of building locally.
|
|
|
|
---
|
|
|
|
## [v0.27] Profile Creation Fallback for Docker (Issue #44)
|
|
*April 3, 2026 | 426 tests*
|
|
|
|
### Bug Fixes
|
|
- **Profile creation works without hermes-agent.** In Docker containers where
|
|
`hermes_cli` is not importable, profile creation now falls back to a local
|
|
implementation that creates the directory structure and optionally clones
|
|
config files. Previously returned `RuntimeError` with "hermes-agent required".
|
|
- **Name validation uses `fullmatch()`.** Prevents trailing-newline bypass of
|
|
the `$` anchor in `re.match()`. Not reachable from the web UI (name is
|
|
stripped), but fixed for defense-in-depth.
|
|
- **`clone_from` validated in `create_profile_api()`.** Defense-in-depth:
|
|
prevents path traversal if called by a non-HTTP client.
|
|
- **Fallback return uses full 9-key schema.** Previously returned only 2 keys
|
|
(`name`, `path`), inconsistent with the normal response shape.
|
|
- **Atomic directory creation.** `mkdir(exist_ok=False)` prevents TOCTOU race
|
|
on concurrent profile creates.
|
|
|
|
### Architecture
|
|
- `api/profiles.py`: `_validate_profile_name()`, `_create_profile_fallback()`,
|
|
`_PROFILE_ID_RE`, `_PROFILE_DIRS`, `_CLONE_CONFIG_FILES` constants matching
|
|
upstream `hermes_cli.profiles`.
|
|
- `docker-compose.yml`: Removed `:ro` from `~/.hermes` mount (required for
|
|
profile writes). Localhost-only binding preserved.
|
|
|
|
---
|
|
|
|
## [v0.26] Profile System Polish -- 10 Post-Sprint-23 Fixes
|
|
*April 3, 2026 | 426 tests*
|
|
|
|
### Bug Fixes
|
|
- **Profile switch base dir bug.** When `HERMES_HOME` was mutated to a
|
|
`profiles/` subdir at startup, `switch_profile()` doubled the path
|
|
(e.g. `~/.hermes/profiles/X/profiles/X`). New `_resolve_base_hermes_home()`
|
|
detects profile subdirs and walks up to the actual base.
|
|
- **Cross-provider model routing.** Picking a model from a different provider
|
|
than the config's default now routes through OpenRouter instead of trying
|
|
a direct API call to a provider whose key may not exist.
|
|
- **Legacy sessions missing profile tag.** `all_sessions()` now backfills
|
|
`profile='default'` for pre-Sprint-22 sessions so the profile filter works.
|
|
- **Workspace list cleanup.** Stale paths, test artifacts, and cross-profile
|
|
entries are now cleaned on load. Legacy global workspace file migrated
|
|
once for the default profile.
|
|
- **API error messages.** `api()` helper now parses JSON error bodies and
|
|
surfaces the human-readable message instead of raw JSON.
|
|
- **Workspace dropdown moved to sidebar.** The workspace picker now opens
|
|
upward from the sidebar bottom instead of clipping behind the topbar.
|
|
|
|
### Features
|
|
- **Rate limit error display.** Rate limit errors (429) now show a distinct
|
|
card with a rate limit icon and hint, instead of the generic error message.
|
|
- **SSE `apperror`/`warning` events.** Server can send typed error events
|
|
that the frontend handles with appropriate UX (rate limit card, fallback
|
|
notice, etc.).
|
|
- **Smart model resolver.** `_findModelInDropdown()` handles name mismatches
|
|
between config model IDs and dropdown values (e.g. `claude-sonnet-4-6` vs
|
|
`anthropic/claude-sonnet-4.6`).
|
|
- **Profile switch starts new session.** When the current session has messages,
|
|
switching profiles automatically starts a fresh session to prevent
|
|
cross-profile tagging.
|
|
- **Per-profile toolsets.** Agent now reads `platform_toolsets.cli` from the
|
|
active profile's config at call time, not the boot-time snapshot.
|
|
- **Per-profile fallback model.** `fallback_model` config is read from the
|
|
active profile and passed to AIAgent.
|
|
|
|
### Architecture
|
|
- `api/profiles.py`: `_resolve_base_hermes_home()` replaces naive env var read.
|
|
- `api/workspace.py`: `_clean_workspace_list()`, `_migrate_global_workspaces()`.
|
|
- `api/streaming.py`: Per-profile toolsets and fallback model at call time.
|
|
- `api/models.py`: `all_sessions()` backfills `profile='default'`.
|
|
- `static/ui.js`: `_findModelInDropdown()`, `_applyModelToDropdown()`.
|
|
- `static/messages.js`: `apperror` and `warning` SSE event handlers.
|
|
|
|
---
|
|
|
|
## [v0.25] Sprint 23 -- Profile/Workspace/Model Coherence
|
|
*April 3, 2026 | 423 tests*
|
|
|
|
### Features
|
|
- **Profile-local workspace storage.** Each named profile now stores its own
|
|
`workspaces.json` and `last_workspace.txt` under `{profile_home}/webui_state/`.
|
|
Default profile continues using the global STATE_DIR for backward compat.
|
|
- **Profile switch returns defaults.** `POST /api/profile/switch` response now
|
|
includes `default_model` and `default_workspace` from the new profile's
|
|
config.yaml, enabling one-round-trip state sync.
|
|
- **Session profile filter.** Session sidebar filters to the active profile by
|
|
default. "Show N from other profiles" toggle reveals sessions from all
|
|
profiles, modeled on the existing archived toggle. Resets on profile switch.
|
|
|
|
### Bug Fixes
|
|
- **Model picker ignores profile on switch.** `switchToProfile()` now clears
|
|
the `hermes-webui-model` localStorage key so the profile's default model
|
|
applies instead of a stale preference from another profile.
|
|
- **Workspace list was global.** Switching profiles no longer shows the wrong
|
|
profile's workspaces.
|
|
- **`DEFAULT_WORKSPACE` was a boot-time singleton.** Now resolved dynamically
|
|
through `_profile_default_workspace()`.
|
|
- **Session list showed all profiles.** Now filtered to active profile.
|
|
- **`switchToProfile()` didn't refresh workspaces or sessions.** Now refreshes
|
|
workspace list, session list, and resets profile filter on switch.
|
|
|
|
### Architecture
|
|
- `api/workspace.py` rewritten with profile-aware path resolution.
|
|
- `api/profiles.py`: `switch_profile()` returns `default_model` and
|
|
`default_workspace`.
|
|
- `static/sessions.js`: Profile filter with toggle UI.
|
|
- `static/panels.js`: Full cascade refresh on profile switch.
|
|
- 8 new tests in `test_sprint23.py`.
|
|
|
|
---
|
|
|
|
## [v0.24] Sprint 22 -- Multi-Profile Support (Issue #28)
|
|
*April 3, 2026 | 415 tests*
|
|
|
|
### Features
|
|
- **Profile picker (topbar).** Purple-accented chip with SVG user icon. Click
|
|
to open dropdown listing all profiles with gateway status dots (green =
|
|
running), model info, and skill count. Click any profile to switch; "Manage
|
|
profiles" link opens the sidebar panel.
|
|
- **Profiles management panel.** New sidebar tab with full CRUD UI. Profile
|
|
cards show name, model/provider, skill count, API key status, and gateway
|
|
status badge. "Use" button switches profile, delete button removes non-default
|
|
profiles (with confirmation).
|
|
- **Profile creation.** "+ New profile" form with name validation (`[a-z0-9_-]`),
|
|
optional "clone config from active" checkbox. Wraps the CLI's
|
|
`hermes_cli.profiles.create_profile()`.
|
|
- **Profile deletion.** Confirm dialog. Auto-switches to default if deleting
|
|
the active profile. Blocked while agent is running.
|
|
- **Seamless profile switching.** No server restart. Profile switch updates
|
|
`HERMES_HOME`, patches module-level caches in hermes-agent's `skills_tool`
|
|
and `cron/jobs`, reloads `.env` API keys and `config.yaml`, refreshes the
|
|
model dropdown, skills, memory, and cron panels.
|
|
- **Per-session profile tracking.** `profile` field on Session records which
|
|
profile was active at creation. Backward-compatible (`null` for old sessions).
|
|
|
|
### Bug Fixes
|
|
- **Hardcoded `~/.hermes` paths.** Memory read/write and model discovery used
|
|
hardcoded paths. Now resolved through `get_active_hermes_home()`.
|
|
- **Module-level path caching.** hermes-agent modules snapshot `HERMES_HOME`
|
|
at import time. Profile switch now monkey-patches `SKILLS_DIR`, `CRON_DIR`,
|
|
`JOBS_FILE`, `OUTPUT_DIR` so they track the active profile.
|
|
|
|
### Architecture
|
|
- New `api/profiles.py`: profile state management wrapping `hermes_cli.profiles`.
|
|
Thread-safe (`_profile_lock`). Lazy imports avoid circular deps.
|
|
- `api/config.py`: module-level `cfg` replaced with reloadable `get_config()`
|
|
/ `reload_config()`. Dynamic `_get_config_path()` resolves through profile.
|
|
- `api/streaming.py`: `HERMES_HOME` added to env save/restore block.
|
|
- Profile switch blocked while agent streams are active.
|
|
- 5 new API endpoints: `GET /api/profiles`, `GET /api/profile/active`,
|
|
`POST /api/profile/switch`, `POST /api/profile/create`,
|
|
`POST /api/profile/delete`.
|
|
- Zero modifications to hermes-agent code.
|
|
|
|
---
|
|
|
|
## [v0.23] Sprint 21 -- Mobile Responsive + Docker
|
|
*April 3, 2026 | 415 tests*
|
|
|
|
### Features
|
|
- **Mobile responsive layout (Issue #21).** Full mobile experience with
|
|
hamburger sidebar (slide-in overlay), bottom navigation bar (5-tab iOS
|
|
pattern), and files slide-over panel. Touch targets minimum 44px. Composer
|
|
positioned above bottom nav. Session clicks auto-close sidebar. Desktop
|
|
layout completely unchanged — all mobile elements hidden via `@media`.
|
|
- **Docker support (Issue #7).** Dockerfile (`python:3.12-slim`), docker-compose.yml
|
|
with named volume for state persistence, optional `~/.hermes` mount for
|
|
agent features. Binds to `127.0.0.1` by default for security.
|
|
|
|
### Bug Fixes (from review)
|
|
- **CSS cascade broke mobile slide-in.** `position:relative` rules after the
|
|
media query overrode `position:fixed` on mobile. Wrapped in `@media(min-width:641px)`.
|
|
- **mobileSwitchPanel() always reopened sidebar.** Chat tab now closes sidebar
|
|
instead of reopening it over the main chat area.
|
|
- **Dockerfile missing pip install.** Added `pip install -r requirements.txt`.
|
|
- **No .dockerignore.** Added exclusions for `.git`, `tests/`, `.env*`.
|
|
- **docker-compose tilde expansion.** Changed `~/.hermes` default to
|
|
`${HOME}/.hermes` (Docker Compose doesn't shell-expand `~`).
|
|
|
|
### Architecture
|
|
- Mobile navigation functions in `boot.js`: `toggleMobileSidebar()`,
|
|
`closeMobileSidebar()`, `toggleMobileFiles()`, `mobileSwitchPanel()`.
|
|
- `sessions.js`: `closeMobileSidebar()` called after session click.
|
|
- 69 new CSS lines in `@media(max-width:640px)` block.
|
|
- New files: `Dockerfile`, `docker-compose.yml`, `.dockerignore`.
|
|
|
|
---
|
|
|
|
## [v0.22] Sprint 20 -- Voice Input + Send Button Polish
|
|
*April 3, 2026 | 415 tests*
|
|
|
|
### Features
|
|
- **Voice input via Web Speech API.** Microphone button in the composer.
|
|
Tap to start recording, tap again (or send) to stop. Live interim
|
|
transcription appears in the textarea. Auto-stops after ~2s of silence.
|
|
Final text stays editable before sending. Appends to existing textarea
|
|
content rather than replacing it. Button hidden when browser doesn't
|
|
support Web Speech API. No API keys, no external libraries, no server
|
|
changes. Works in Chrome, Edge, Safari (partial). Firefox unsupported
|
|
(button stays hidden).
|
|
- **Send button polish.** Send button redesigned as a 34px icon-only circle
|
|
with upward arrow SVG. Hidden by default — appears with pop-in spring
|
|
animation when textarea has content or files are attached. Disappears
|
|
on send or when content is cleared. Hidden while agent is responding.
|
|
Blue fill (#7cb9ff) with glow, scale hover/active for tactile feedback.
|
|
|
|
### Architecture
|
|
- Voice input IIFE in `boot.js`: SpeechRecognition lifecycle with
|
|
`continuous=false`, `interimResults=true`, error handling via `showToast()`.
|
|
- `_prefix` variable snapshots existing textarea content on recording start
|
|
so dictation appends rather than overwrites.
|
|
- `btnSend.onclick` stops active recognition before sending (send guard).
|
|
- CSS: `.mic-btn`, `.mic-btn.recording` (red pulse), `.mic-status`,
|
|
`.mic-dot`, `@keyframes mic-pulse`.
|
|
- `updateSendBtn()` in `ui.js` tracks textarea content, pending files,
|
|
and busy state. Hooked into `setBusy()`, `renderTray()`, `autoResize()`,
|
|
and input event listener.
|
|
- CSS: `.send-btn` redesigned (circle, glow), `.send-btn.visible` +
|
|
`@keyframes send-pop-in` (spring animation).
|
|
|
|
### Tests
|
|
- 52 new tests in `test_sprint20.py`: voice input HTML, CSS, JS, append
|
|
behaviour, error handling, regressions.
|
|
- 33 new tests in `test_sprint20b.py`: send button HTML, CSS, JS,
|
|
animation, visibility logic, regressions. Total: **415 tests**.
|
|
|
|
---
|
|
|
|
## [v0.21] Sprint 19 -- Auth + Security Hardening
|
|
*April 3, 2026 | 328 tests*
|
|
|
|
### Features
|
|
- **Password authentication (Issue #23).** Optional password auth, off by default.
|
|
Enable via `HERMES_WEBUI_PASSWORD` env var or Settings panel. Password-only
|
|
(single-user app). Signed HMAC HTTP-only cookie with 24h TTL. Minimal dark-themed
|
|
login page at `/login`. API calls without auth return 401; page loads redirect.
|
|
New `api/auth.py` module with hashing, verification, session management.
|
|
- **Security headers.** All responses now include `X-Content-Type-Options: nosniff`,
|
|
`X-Frame-Options: DENY`, `Referrer-Policy: same-origin`.
|
|
- **POST body size limit.** Non-upload POST bodies capped at 20MB via `read_body()`.
|
|
- **Settings panel additions.** "Access Password" field and "Sign Out" button
|
|
(only visible when auth is active).
|
|
|
|
### Architecture
|
|
- New `api/auth.py`: password hashing (SHA-256 + STATE_DIR salt), signed cookies,
|
|
auth middleware, public path allowlist.
|
|
- Auth check in `server.py` do_GET/do_POST before routing.
|
|
- `password_hash` added to `_SETTINGS_DEFAULTS`.
|
|
|
|
### Tests
|
|
- 10 new tests in `test_sprint19.py`: auth status, login flow, security headers,
|
|
cache-control, settings password field, request size limit. Total: **328 tests (328 passing)**.
|
|
|
|
---
|
|
|
|
## [v0.20] Sprint 18 -- File Preview Auto-Close + Thinking Display + Workspace Tree
|
|
*April 3, 2026 | 318 tests*
|
|
|
|
### Features
|
|
- **File preview auto-close on directory navigation.** When viewing a file in
|
|
the right panel and navigating directories (breadcrumbs, up button, folder
|
|
clicks), the preview now automatically closes instead of showing stale
|
|
content. `clearPreview()` extracted as named function and called from
|
|
`loadDir()`. Unsaved preview edits prompt for confirmation before discarding.
|
|
- **Thinking/reasoning display.** Assistant messages with structured content
|
|
arrays containing `type:'thinking'` or `type:'reasoning'` blocks (Claude
|
|
extended thinking, o3 reasoning) now render as collapsible gold-themed cards
|
|
above the response text. Collapsed by default. Click the header to expand and
|
|
see the model's reasoning process. Uses `esc()` on all content for XSS safety.
|
|
- **Workspace tree view (Issue #22).** Directories expand/collapse in-place
|
|
with toggle arrows. Single-click toggles a directory open/closed. Double-click
|
|
navigates into it (breadcrumb view). Subdirectory contents fetched lazily from
|
|
the API and cached in `S._dirCache`. Nesting depth shown via indentation.
|
|
Empty directories show "(empty)" placeholder. Breadcrumb navigation still
|
|
works alongside the tree view.
|
|
|
|
### Bug Fixes
|
|
- **Stale tree cache on session switch.** `S._dirCache` and `S._expandedDirs`
|
|
are now cleared when navigating to the root directory, preventing session B
|
|
from showing session A's cached file listings.
|
|
- **clearPreview() discards unsaved edits.** Navigation now checks
|
|
`_previewDirty` and prompts before discarding unsaved preview changes.
|
|
|
|
### Architecture
|
|
- `clearPreview()` extracted from inline handler to named function in `boot.js`.
|
|
- Thinking card styles added to `style.css` (gold-themed, collapsible).
|
|
- Tree toggle and empty-directory styles added to `style.css`.
|
|
|
|
---
|
|
|
|
## [v0.19] Sprint 17 -- Workspace Polish + Slash Commands + Settings
|
|
*April 3, 2026 | 318 tests*
|
|
|
|
### Features
|
|
- **Workspace breadcrumb navigation.** Clicking into subdirectories now shows a
|
|
breadcrumb path bar (e.g. `~ / src / components`) with clickable segments to
|
|
navigate back. An "up" button appears in the panel header when inside a
|
|
subdirectory. File operations (rename, delete, new file/folder) stay in the
|
|
current directory instead of jumping back to root. Foundation for Issue #22
|
|
(tree view).
|
|
- **Slash commands.** Type `/` in the composer to see an autocomplete dropdown
|
|
of built-in commands. New `commands.js` module with command registry. Built-in
|
|
commands: `/help`, `/clear`, `/model <name>`, `/workspace <name>`, `/new`.
|
|
Arrow keys navigate, Tab/Enter select, Escape closes. Unrecognized commands
|
|
pass through to the agent normally.
|
|
- **Send key setting (Issue #26).** New setting in Settings panel to choose
|
|
between Enter (default) and Ctrl/Cmd+Enter as the send key. Persisted to
|
|
`settings.json` via the existing settings API. Setting loads on boot.
|
|
Server-side validation ensures only valid values (`enter`, `ctrl+enter`).
|
|
|
|
### Architecture
|
|
- New `static/commands.js` module (7th JS module): command registry, parser,
|
|
autocomplete dropdown, and built-in command handlers.
|
|
- `send_key` added to `_SETTINGS_DEFAULTS` in `api/config.py` with enum validation
|
|
(`_SETTINGS_ENUM_VALUES` rejects unknown values server-side).
|
|
- `S.currentDir` state tracking added to `ui.js` for workspace navigation.
|
|
|
|
### Tests
|
|
- 6 new tests in `test_sprint17.py`: send_key default, round-trip save with
|
|
cleanup, invalid value rejection, unknown key ignored, commands.js served,
|
|
workspace root listing. Total: **318 passed**.
|
|
|
|
---
|
|
|
|
## [v0.18.1] Safe HTML Rendering + Sprint 16 Tests
|
|
*April 2, 2026 | 289 tests*
|
|
|
|
### Features
|
|
- **Safe HTML rendering in AI responses.** AI models sometimes emit HTML tags
|
|
(`<strong>`, `<em>`, `<code>`, `<br>`) in their responses. Previously these
|
|
showed as literal escaped text. A new pre-pass in `renderMd()` converts safe
|
|
HTML tags to markdown equivalents before the pipeline runs. Code blocks and
|
|
backtick spans are stashed first so their content is never touched.
|
|
- **`inlineMd()` helper.** New function for processing inline formatting inside
|
|
list items, blockquotes, and headings. The old code called `esc()` directly,
|
|
which escaped tags that had already been converted by the pre-pass.
|
|
- **Safety net.** After the full pipeline, any HTML tags not in the output
|
|
allowlist (`SAFE_TAGS`) are escaped via `esc()`. XSS fully blocked -- 7
|
|
attack vectors tested.
|
|
- **Active session gold style.** Active session uses gold/amber (`#e8a030`)
|
|
instead of blue, matching the logo gradient. Project border-left skipped
|
|
when active (gold always wins).
|
|
|
|
### Tests
|
|
- **74 new tests** in `test_sprint16.py`: static analysis (6), behavioral (10),
|
|
exact regression (1), XSS security (7), edge cases (51). Total: 289 passed.
|
|
|
|
---
|
|
|
|
## [v0.18] Sprint 16 -- Session Sidebar Visual Polish
|
|
*April 2, 2026 | 237 tests*
|
|
|
|
### Features
|
|
- **SVG action icons.** Replaced all emoji HTML entities (star, folder, box,
|
|
duplicate, trash) with monochrome SVG line icons that inherit `currentColor`.
|
|
Consistent rendering across macOS, Linux, and Windows. Defined in a top-level
|
|
`ICONS` constant in `sessions.js`.
|
|
- **Action buttons overlay.** All session action buttons (pin, move, archive,
|
|
duplicate, trash) wrapped in a `.session-actions` container with
|
|
`position:absolute`. Titles now use full available width instead of being
|
|
truncated by invisible buttons. Actions appear on hover with a gradient fade
|
|
from the right edge. Overlay auto-hides during inline rename via
|
|
`:has(.session-title-input)`.
|
|
- **Pin indicator.** Small gold filled-star icon rendered inline before the
|
|
title only when pinned. Unpinned sessions get full title width with zero
|
|
space reservation.
|
|
- **Project border indicator.** Sessions assigned to a project show a colored
|
|
left border matching the project color, replacing the old always-visible
|
|
blue folder button.
|
|
|
|
### Bug Fixes
|
|
- **Session title truncation.** Action icons reserved ~30px of space even when
|
|
invisible, truncating titles. Fixed by overlay container approach.
|
|
- **Folder button felt sticky.** Replaced `.has-project` persistent blue button
|
|
with colored left border. Folder button now only appears in hover overlay.
|
|
|
|
---
|
|
|
|
## [v0.17.3] Bug Fixes
|
|
*April 2, 2026*
|
|
|
|
### Bug Fixes
|
|
- **NameError crash in model discovery.** `logger.debug()` was called in the
|
|
custom endpoint `except` block in `config.py`, but `logger` was never
|
|
imported. Every failed custom endpoint fetch crashed with `NameError`,
|
|
returning HTTP 500 for `/api/models`. Replaced with silent `pass` since
|
|
unreachable endpoints are expected. (PR #24)
|
|
- **Project picker clipping and width.** Picker was clipped by
|
|
`overflow:hidden` on ancestor elements. Width calculation improved with
|
|
dynamic sizing (min 160px, max 220px). Event listener `close` handler
|
|
moved after DOM append to fix reference-before-definition. Reordered
|
|
`picker.remove()` before `removeEventListener` for correct cleanup. (PR #25)
|
|
|
|
---
|
|
|
|
## [v0.17.2] Model Update
|
|
*April 2, 2026*
|
|
|
|
### Enhancements
|
|
- **GLM-5.1 added to Z.AI model list.** New model available in the dropdown
|
|
for Z.AI provider users. (Fixes #17)
|
|
|
|
---
|
|
|
|
## [v0.17.1] Security + Bug Fixes
|
|
*April 2, 2026 | 237 tests*
|
|
|
|
### Security
|
|
- **Path traversal in static file server.** `_serve_static()` now sandboxes
|
|
resolved paths inside `static/` via `.relative_to()`. Previously
|
|
`GET /static/../../.hermes/config.yaml` could expose API keys.
|
|
- **XSS in markdown renderer.** All captured groups in bold, italic, headings,
|
|
blockquotes, list items, table cells, and link labels now run through `esc()`
|
|
before `innerHTML` insertion.
|
|
- **Skill category path traversal.** Category param validated to reject `/`
|
|
and `..` to prevent writing outside `~/.hermes/skills/`.
|
|
- **Debug endpoint locked to localhost.** `/api/approval/inject_test` returns
|
|
404 to any non-loopback client.
|
|
- **CDN resources pinned with SRI hashes.** PrismJS and Mermaid tags now have
|
|
`integrity` + `crossorigin` attributes. Mermaid pinned to `@10.9.3`.
|
|
- **Project color CSS injection.** Color field validated against
|
|
`^#[0-9a-fA-F]{3,8}$` to prevent `style.background` injection.
|
|
- **Project name length limit.** Capped at 128 chars, empty-after-strip rejected.
|
|
|
|
### Bug Fixes
|
|
- **OpenRouter model routing regression.** `resolve_model_provider()` was
|
|
incorrectly stripping provider prefixes from OpenRouter model IDs (e.g.
|
|
`openai/gpt-5.4-mini` became `gpt-5.4-mini` with provider `openai`),
|
|
causing AIAgent to look for OPENAI_API_KEY and crash. Fix: only strip
|
|
prefix when `config.provider` explicitly matches that direct-API provider.
|
|
- **Project picker invisible.** Dropdown was clipped by `.session-item`
|
|
`overflow:hidden`. Now appended to `document.body` with `position:fixed`.
|
|
- **Project picker stretched full width.** Added `max-width:220px;
|
|
width:max-content` to constrain the fixed-positioned picker.
|
|
- **No way to create project from picker.** Added "+ New project" item at
|
|
the bottom of the picker dropdown.
|
|
- **Folder button undiscoverable.** Now shows persistently (blue, 60%
|
|
opacity) when session belongs to a project.
|
|
- **Picker event listener leak.** `removeEventListener` added to all picker
|
|
item onclick handlers.
|
|
- **Redundant sys.path.insert calls removed.** Two cron handler imports no
|
|
longer prepend the agent dir (already on sys.path via config.py).
|
|
|
|
---
|
|
|
|
## [v0.17] Sprint 15 -- Session Projects + Code Copy + Tool Card Toggle
|
|
*April 1, 2026 | 237 tests*
|
|
|
|
### Features
|
|
- **Session projects.** Named groups for organizing sessions. A project filter
|
|
bar (subtle chips) sits between the search input and the session list. Each
|
|
project has a name and color. Click a chip to filter; "All" shows everything.
|
|
Create inline (+), rename (double-click), delete (right-click). Assign sessions
|
|
via folder icon button with dropdown picker. Projects stored in `projects.json`.
|
|
Session model gains `project_id` field. 5 new API endpoints.
|
|
- **Code block copy button.** Every code block gets a "Copy" button in the
|
|
language header bar (or top-right for plain blocks). Click copies to clipboard,
|
|
shows "Copied!" for 1.5s.
|
|
- **Tool card expand/collapse.** When a message has 2+ tool cards, "Expand all /
|
|
Collapse all" toggle appears above the card group.
|
|
|
|
---
|
|
|
|
## [v0.16.2] Model List Updates + base_url Passthrough
|
|
*April 1, 2026 | 247 tests*
|
|
|
|
### Bug Fixes
|
|
- **MiniMax model list updated.** Replaced stale ABAB 6.5 models with current
|
|
MiniMax-M2.7, M2.7-highspeed, M2.5, M2.5-highspeed, M2.1 lineup matching
|
|
hermes-agent upstream. (Fixes #6)
|
|
- **Z.AI/GLM model list updated.** Replaced GLM-4 series with current GLM-5,
|
|
GLM-5 Turbo, GLM-4.7, GLM-4.5, GLM-4.5 Flash lineup.
|
|
- **base_url passthrough to AIAgent.** `resolve_model_provider()` now reads
|
|
`base_url` from config.yaml and passes it to AIAgent, so providers with
|
|
custom endpoints (MiniMax, Z.AI, local LLMs) route to the correct API.
|
|
|
|
---
|
|
|
|
## [v0.16.1] Community Fixes -- Mobile + Auth + Provider Routing
|
|
*April 1, 2026 | 247 tests*
|
|
|
|
Community contributions from @deboste, reviewed and refined.
|
|
|
|
### Bug Fixes
|
|
- **Mobile responsive layout.** Comprehensive `@media(max-width:640px)` rules
|
|
for topbar, messages, composer, tool cards, approval cards, and settings modal.
|
|
Uses `100dvh` with `100vh` fallback to fix composer cutoff on mobile browsers.
|
|
Textarea `font-size:16px` prevents iOS/Android auto-zoom on focus.
|
|
- **Reverse proxy basic auth support.** All `fetch()` and `EventSource` URLs now
|
|
constructed via `new URL(path, location.origin)` to strip embedded credentials
|
|
per Fetch spec. `credentials:'include'` on fetch, `withCredentials:true` on
|
|
EventSource ensure auth headers are forwarded through reverse proxies.
|
|
- **Model provider routing.** New `resolve_model_provider()` helper in
|
|
`api/config.py` strips provider prefix from dropdown model IDs (e.g.
|
|
`anthropic/claude-sonnet-4.6` → `claude-sonnet-4.6`) and passes the correct
|
|
`provider` to AIAgent. Handles cross-provider selection by matching against
|
|
known direct-API providers.
|
|
|
|
---
|
|
|
|
## [v0.16] Sprint 14 -- Visual Polish + Workspace Ops + Session Organization
|
|
*March 30, 2026 | 233 tests*
|
|
|
|
### Features
|
|
- **Mermaid diagram rendering.** Code blocks tagged `mermaid` render as
|
|
diagrams inline. Mermaid.js loaded lazily from CDN on first encounter.
|
|
Dark theme with matching colors. Falls back to code block on parse error.
|
|
- **Message timestamps.** Subtle HH:MM time next to each role label. Full
|
|
date/time on hover tooltip. User messages get `_ts` field when sent.
|
|
- **File rename.** Double-click any filename in workspace panel to rename
|
|
inline. `POST /api/file/rename` endpoint with path traversal protection.
|
|
- **Folder create.** Folder icon button in workspace panel header. Prompt
|
|
for name, `POST /api/file/create-dir` endpoint.
|
|
- **Session tags.** Add `#tag` to session titles. Tags shown as colored
|
|
chips in sidebar. Click a tag to filter the session list.
|
|
- **Session archive.** Archive icon on each session. Archived sessions
|
|
hidden by default; "Show N archived" toggle at top of list. Backend
|
|
`POST /api/session/archive` with `archived` field on Session model.
|
|
|
|
### Bug Fixes
|
|
- **Date grouping fix.** Session list groups (Today/Yesterday/Earlier) now
|
|
use `created_at` instead of `updated_at`, preventing sessions from jumping
|
|
between groups when auto-titling touches `updated_at`.
|
|
|
|
---
|
|
|
|
## [v0.15] Sprint 13 -- Alerts + Session QoL + Polish
|
|
*March 30, 2026 | 221 tests*
|
|
|
|
### Features
|
|
- **Cron completion alerts.** New `GET /api/crons/recent` endpoint. UI polls every
|
|
30s (pauses when tab is hidden). Toast notification per completion with status icon.
|
|
Red badge count on Tasks nav tab, cleared when tab is opened.
|
|
- **Background agent error alerts.** When a streaming session errors out and the user
|
|
is viewing a different session, a persistent red banner appears above the messages:
|
|
"Session X has encountered an error." View button navigates, Dismiss clears.
|
|
- **Session duplicate.** Copy icon on each session in the sidebar (visible on hover).
|
|
Creates a new session with the same workspace and model, titled "(copy)".
|
|
- **Browser tab title.** `document.title` updates to show the active session title
|
|
(e.g. "My Task -- Hermes"). Resets to "Hermes" when no session is active.
|
|
|
|
### Bug Fixes
|
|
- Click guard added for duplicate button to prevent accidental session navigation.
|
|
|
|
---
|
|
|
|
## [v0.14] Sprint 12 -- Settings Panel + Reliability + Session QoL
|
|
*March 30, 2026 | 211 tests*
|
|
|
|
### Features
|
|
- **Settings panel.** Gear icon in topbar opens slide-in overlay. Persist default
|
|
model and workspace server-side in `settings.json`. Server reads on startup.
|
|
- **SSE auto-reconnect.** When EventSource drops mid-stream, attempts one reconnect
|
|
using the same stream_id after 1.5s. Shared `_wireSSE()` function eliminates
|
|
handler duplication.
|
|
- **Pin sessions.** Star icon on each session. Pinned sessions float to top of sidebar
|
|
under a gold "Pinned" header. Persisted in session JSON.
|
|
- **Import session from JSON.** Upload button in sidebar. Creates new session with
|
|
fresh ID from exported JSON file.
|
|
|
|
### Bug Fixes
|
|
- `models.py` uses `_cfg.DEFAULT_MODEL` module reference so `save_settings()` changes
|
|
take effect for `new_session()`.
|
|
- Full-scan fallback sort in `all_sessions()` now accounts for pinned sessions.
|
|
- `save_settings()` whitelists known keys only, rejecting arbitrary data.
|
|
- Escape key closes settings overlay.
|
|
|
|
---
|
|
|
|
## [v0.13] Sprint 11 -- Multi-Provider Models + Streaming Smoothness
|
|
*March 30, 2026 | 201 tests*
|
|
|
|
### Features
|
|
- **Multi-provider model support.** New `GET /api/models` endpoint discovers configured
|
|
providers from `config.yaml`, `auth.json`, and API key environment variables. The model
|
|
dropdown now populates dynamically from whatever providers the user has set up (Anthropic,
|
|
OpenAI, Google, DeepSeek, Nous Portal, OpenRouter, etc.). Falls back to the hardcoded
|
|
OpenRouter list when no providers are detected. Sessions with unlisted models auto-add
|
|
them to the dropdown.
|
|
- **Smooth scroll pinning.** During streaming, auto-scroll only when the user is near the
|
|
bottom of the message area. If the user scrolls up to read earlier content, new tokens
|
|
no longer yank them back down. Pinning resumes when they scroll back to the bottom.
|
|
|
|
### Architecture
|
|
- **Routes extracted to api/routes.py.** All 49 GET/POST route handlers moved from server.py
|
|
into `api/routes.py` (802 lines). server.py is now a 76-line thin shell: Handler class
|
|
with structured logging, dispatch to `handle_get()`/`handle_post()`, and `main()`.
|
|
Completes the server split started in Sprint 10.
|
|
- **Cleaned up duplicate dead-code routes** that existed in the old `do_GET` (skills/save,
|
|
skills/delete, memory/write were duplicated in both GET and POST handlers).
|
|
|
|
### Bug Fixes
|
|
- Regression tests updated for new route module structure.
|
|
|
|
---
|
|
|
|
## [v0.12.2] Concurrency + Correctness Sweeps
|
|
*March 31, 2026 | 190 tests*
|
|
|
|
Two systematic audits of all concurrent multi-session scenarios. Each finding
|
|
became a regression test so it cannot silently return.
|
|
|
|
### Sweep 1 (R10-R12)
|
|
- **R10: Approval response to wrong session.** `respondApproval()` used
|
|
`S.session.session_id` -- whoever you were viewing. If session A triggered
|
|
a dangerous command requiring approval and you switched to B then clicked
|
|
Allow, the approval went to B's session_id. Agent on A stayed stuck. Fixed:
|
|
approval events tag `_approvalSessionId`; `respondApproval()` uses that.
|
|
- **R11: Activity bar showed cross-session tool status.** Session A's tool
|
|
name appeared in session B's activity bar while you were viewing B. Fixed:
|
|
`setStatus()` in the tool SSE handler is now inside the `activeSid` guard.
|
|
- **R12: Live tool cards vanished on switch-away and back.** Switching back to
|
|
an in-flight session showed empty live cards even though tools had fired.
|
|
Fixed: `loadSession()` INFLIGHT branch now restores cards from `S.toolCalls`.
|
|
|
|
### Sweep 2 (R13-R15)
|
|
- **R13: Settled tool cards never rendered after response completes.**
|
|
`renderMessages()` has a `!S.busy` guard on tool card rendering. It was
|
|
called with `S.busy=true` in the done handler -- tool cards were skipped
|
|
every time. Fixed: `S.busy=false` set inline before `renderMessages()`.
|
|
- **R14: Wrong model sent for sessions with unlisted model.** `send()` used
|
|
`$('modelSelect').value` which could be stale if the session's model isn't
|
|
in the dropdown. Fixed: now uses `S.session.model || $('modelSelect').value`.
|
|
- **R15: Stale live tool cards in new sessions.** `newSession()` didn't call
|
|
`clearLiveToolCards()`. Fixed.
|
|
|
|
---
|
|
|
|
## [v0.12.1] Sprint 10 Post-Release Fixes
|
|
*March 31, 2026 | 177 tests*
|
|
|
|
Critical regressions introduced during the server.py split, caught by users and fixed immediately.
|
|
|
|
- **`uuid` not imported in server.py** -- `chat/start` returned 500 (NameError) on every new message
|
|
- **`AIAgent` not imported in api/streaming.py** -- agent thread crashed immediately, SSE returned 404
|
|
- **`has_pending` not imported in api/streaming.py** -- NameError during tool approval checks
|
|
- **`Session.__init__` missing `tool_calls` param** -- 500 on any session with tool history
|
|
- **SSE loop did not break on `cancel` event** -- connection hung after cancel
|
|
- **Regression test file added** (`tests/test_regressions.py`): 10 tests, one per introduced bug. These form a permanent regression gate so each class of error can never silently return.
|
|
|
|
---
|
|
|
|
## [v0.12] Sprint 10 -- Server Health + Operational Polish
|
|
*March 31, 2026 | 167 tests*
|
|
|
|
### Post-sprint Bug Fixes
|
|
- SSE loop now breaks on `cancel` event (was hanging after cancel)
|
|
- `setBusy(false)` now always hides the Cancel button
|
|
- `S.activeStreamId` properly initialized in the S global state object
|
|
- Tool card "Show more" button uses data attributes instead of inline JSON.stringify (XSS/parse safety)
|
|
- Version label updated to v0.2
|
|
- `Session.__init__` accepts `**kwargs` for forward-compatibility with future JSON fields
|
|
- Test cron jobs now isolated via `HERMES_HOME` env var in conftest (no more pollution of real jobs.json)
|
|
- `last_workspace` reset after each test in conftest (prevents workspace state bleed between tests)
|
|
- Tool cards now grouped per assistant turn instead of piled before last message
|
|
- Tool card insertion uses `data-msg-idx` attribute correctly (was `msgIdx`, matching HTML5 dataset API)
|
|
|
|
### Architecture
|
|
- **server.py split into api/ modules.** 1,150 lines -> 673 lines in server.py.
|
|
Extracted modules: `api/config.py` (101), `api/helpers.py` (57), `api/models.py` (114),
|
|
`api/workspace.py` (77), `api/upload.py` (77), `api/streaming.py` (187).
|
|
server.py is now the thin routing shell only. All business logic is independently importable.
|
|
|
|
### Features
|
|
- **Background task cancel.** Red "Cancel" button appears in the activity bar while a task
|
|
is running. Calls `GET /api/chat/cancel?stream_id=X`. The agent thread receives a cancel
|
|
event, emits a 'cancel' SSE event, and the UI shows "*Task cancelled.*" in the conversation.
|
|
Note: a tool call already in progress (e.g. a long terminal command) completes before
|
|
the cancel takes effect -- same behavior as CLI Ctrl+C.
|
|
- **Cron run history viewer.** Each job in the Tasks panel now has an "All runs" button.
|
|
Click to expand a list of up to 20 past runs with timestamps, each collapsible to show
|
|
the full output. Click again to hide.
|
|
- **Tool card UX polish.** Three improvements:
|
|
1. Pulsing blue dot on cards for in-progress tools (distinct from completed cards)
|
|
2. Smart snippet truncation at sentence boundaries instead of hard byte cutoff
|
|
3. "Show more / Show less" toggle on tool results longer than 220 chars
|
|
|
|
---
|
|
|
|
## [v0.11] Sprint 9 -- Codebase Health + Daily Driver Gaps
|
|
*March 31, 2026 | 149 tests*
|
|
|
|
The sprint that closed the last gaps for heavy agentic use.
|
|
|
|
### Architecture
|
|
- **app.js replaced by 6 modules.** `app.js` is deleted. The browser now loads 6 focused files:
|
|
`ui.js` (530), `workspace.js` (132), `sessions.js` (189), `messages.js` (221),
|
|
`panels.js` (555), `boot.js` (142). The modules are a superset of the original app.js
|
|
(two functions -- `loadTodos`, `toolIcon` -- were added directly to the modules after the split).
|
|
No single file exceeds 555 lines.
|
|
|
|
### Features
|
|
- **Tool call cards inline.** Every tool Hermes uses now appears as a collapsible card
|
|
in the conversation between the user message and the response. Live during streaming,
|
|
restored from session history on reload. Shows tool name, preview, args, result snippet.
|
|
- **Attachment metadata persists on reload.** File badges on user messages survive page
|
|
refresh. Server stores filenames on the user message in session JSON.
|
|
- **Todo list panel.** New checkmark tab in the sidebar. Shows current task list parsed
|
|
from the most recent todo tool result in message history. Status icons: pending (○),
|
|
in-progress (◉), completed (✓), cancelled (✗). Auto-refreshes when panel is active.
|
|
- **Model preference persists.** Last-used model saved to localStorage. Restored on page
|
|
load. New sessions inherit it automatically.
|
|
|
|
### Bug Fixes
|
|
- Tool card toggle arrow only shown when card has expandable content
|
|
- Attachment tagging matches by message content to avoid wrong-turn tagging
|
|
- SSE tool event was missing `args` field
|
|
- `/api/session` GET was not returning `tool_calls` (history lost on reload)
|
|
|
|
---
|
|
|
|
## [v0.10] Sprint 8 -- Daily Driver Finish Line
|
|
*March 31, 2026 | 139 tests*
|
|
|
|
### Features
|
|
- **Edit user message + regenerate.** Hover any user bubble, click the pencil icon.
|
|
Inline textarea, Enter submits, Escape cancels. Truncates session at that point and re-runs.
|
|
- **Regenerate last response.** Retry icon on the last assistant bubble only.
|
|
- **Clear conversation.** "Clear" button in topbar. Wipes messages, keeps session slot.
|
|
- **Syntax highlighting.** Prism.js via CDN (deferred). Python, JS, bash, JSON, SQL and more.
|
|
|
|
### Bug Fixes
|
|
- Reconnect banner false positive on normal loads (90-second window)
|
|
- Session list clipping on short screens
|
|
- Favicon 404 console noise (server now returns 204)
|
|
- Edit textarea auto-resize on open
|
|
- Send button guard while inline edit is active
|
|
- Escape closes dropdown, clears search, cancels active edit
|
|
- Approval polling not restarted on INFLIGHT session switch-back
|
|
- Version label updated to v0.10
|
|
|
|
### Hotfix: Message Queue + INFLIGHT
|
|
- **Message queue.** Sending while busy queues the message with toast + badge.
|
|
Drains automatically on completion. Cleared on session switch.
|
|
- **Message stays visible on switch-away/back.** loadSession checks INFLIGHT before
|
|
server fetch, so sent message and thinking dots persist correctly.
|
|
|
|
---
|
|
|
|
## [v0.9] Sprint 7 -- Wave 2 Core: CRUD + Search
|
|
*March 31, 2026 | 125 tests*
|
|
|
|
### Features
|
|
- **Cron edit + delete.** Inline edit form per job, save and delete with confirmation.
|
|
- **Skill create, edit, delete.** "+ New skill" form in Skills panel. Writes to `~/.hermes/skills/`.
|
|
- **Memory inline edit.** "Edit" button opens textarea for MEMORY.md. Saves via `/api/memory/write`.
|
|
- **Session content search.** Filter box searches message text (up to 5 messages per session)
|
|
in addition to titles. Debounced API call, results appended below title matches.
|
|
|
|
### Architecture
|
|
- `/health` now returns `active_streams` and `uptime_seconds`
|
|
- `git init` on `<repo>/`, pushed to GitHub
|
|
|
|
### Bug Fixes
|
|
- Activity bar overlap on short viewports
|
|
- Model chip stale after session switch
|
|
- Cron output overflow in tasks panel
|
|
|
|
---
|
|
|
|
## [v0.8] Sprint 6 -- Polish + Phase E Complete
|
|
*March 31, 2026 | 106 tests*
|
|
|
|
### Architecture
|
|
- **Phase E complete.** HTML extracted to `static/index.html`. server.py now pure Python.
|
|
Line count progression: 1778 (Sprint 1) → 1042 (Sprint 5) → 903 (Sprint 6).
|
|
- **Phase D complete.** All endpoints validated with proper 400/404 responses.
|
|
|
|
### Features
|
|
- **Resizable panels.** Sidebar and workspace panel drag-resizable. Widths persisted to localStorage.
|
|
- **Create cron job from UI.** "+ New job" form in Tasks panel with name, schedule, prompt, delivery.
|
|
- **Session JSON export.** Downloads full session as JSON via "JSON" button in sidebar footer.
|
|
- **Escape from file editor.** Cancels inline file edit without saving.
|
|
|
|
---
|
|
|
|
## [v0.7] Sprint 5 -- Phase A Complete + Workspace Management
|
|
*March 30, 2026 | 86 tests*
|
|
|
|
### Architecture
|
|
- **Phase A complete.** JS extracted to `static/app.js`. server.py: 1778 → 1042 lines.
|
|
- **LRU session cache.** `collections.OrderedDict` with cap of 100, oldest evicted automatically.
|
|
- **Session index.** `sessions/_index.json` for O(1) session list loads.
|
|
- **Isolated test server.** Port 8788 with own state dir, conftest autouse cleanup.
|
|
|
|
### Features
|
|
- **Workspace management panel.** Add/remove/rename workspaces. Persisted to `workspaces.json`.
|
|
- **Topbar workspace quick-switch.** Dropdown chip lists all workspaces, switches on click.
|
|
- **New sessions inherit last workspace.** `last_workspace.txt` tracks last used.
|
|
- **Copy message to clipboard.** Hover icon on each bubble with checkmark confirmation.
|
|
- **Inline file editor.** Preview any file, click Edit to modify, Save writes to disk.
|
|
|
|
---
|
|
|
|
## [v0.6] Sprint 4 -- Relocation + Session Power Features
|
|
*March 30, 2026 | 68 tests*
|
|
|
|
### Architecture
|
|
- **Source relocated** to `<repo>/` outside the hermes-agent git repo.
|
|
Safe from `git pull`, `git reset`, `git stash`. Symlink maintained at `hermes-agent/webui-mvp`.
|
|
- **CSS extracted (Phase A start).** All CSS moved to `static/style.css`.
|
|
- **Per-session agent lock (Phase B).** Prevents concurrent requests to same session from
|
|
corrupting environment variables.
|
|
|
|
### Features
|
|
- **Session rename.** Double-click any title in sidebar to edit inline. Enter saves, Escape cancels.
|
|
- **Session search/filter.** Live client-side filter box above session list.
|
|
- **File delete.** Hover trash icon on workspace files. Confirm dialog.
|
|
- **File create.** "+" button in workspace panel header.
|
|
|
|
---
|
|
|
|
## [v0.5] Sprint 3 -- Panel Navigation + Feature Viewers
|
|
*March 30, 2026 | 48 tests*
|
|
|
|
### Features
|
|
- **Sidebar panel navigation.** Four tabs: Chat, Tasks, Skills, Memory. Lazy-loads on first open.
|
|
- **Tasks panel.** Lists scheduled cron jobs with status badges. Run now, Pause, Resume.
|
|
Shows last run output automatically.
|
|
- **Skills panel.** All skills grouped by category. Search/filter. Click to preview SKILL.md.
|
|
- **Memory panel.** Renders MEMORY.md and USER.md as formatted markdown with timestamps.
|
|
|
|
### Bug Fixes
|
|
- B6: New session inherits current workspace
|
|
- B10: Tool events replace thinking dots (not stacked alongside)
|
|
- B14: Cmd/Ctrl+K creates new chat from anywhere
|
|
|
|
---
|
|
|
|
## [v0.4] Sprint 2 -- Rich File Preview
|
|
*March 30, 2026 | 27 tests*
|
|
|
|
### Features
|
|
- **Image preview.** PNG, JPG, GIF, SVG, WEBP displayed inline in workspace panel.
|
|
- **Rendered markdown.** `.md` files render as formatted HTML in the preview panel.
|
|
- **Table support.** Pipe-delimited markdown tables render as HTML tables.
|
|
- **Smart file icons.** Type-appropriate icons by extension in the file tree.
|
|
- **Preview path bar with type badge.** Colored badge shows file type.
|
|
|
|
---
|
|
|
|
## [v0.3] Sprint 1 -- Bug Fixes + Foundations
|
|
*March 30, 2026 | 19 tests*
|
|
|
|
The first sprint. Established the test suite, fixed critical bugs.
|
|
|
|
### Bug Fixes
|
|
- B1: Approval card now shows pattern keys
|
|
- B2: File input accepts valid types only
|
|
- B3: Model chip label correct for all 10 models (replaced substring check with dict)
|
|
- B4/B5: Reconnect banner on mid-stream reload (localStorage inflight tracking)
|
|
- B7: Session titles no longer overflow sidebar
|
|
- B9: Empty assistant messages no longer render as blank bubbles
|
|
- B11: `/api/session` GET returns 400 (not silent session creation) when ID missing
|
|
|
|
### Architecture
|
|
- Thread lock on SESSIONS dict
|
|
- Structured JSON request logging
|
|
- 10-model dropdown with 3 provider groups (OpenAI, Anthropic, Other)
|
|
- First test suite: 19 HTTP integration tests
|
|
|
|
---
|
|
|
|
## [v0.2] UI Polish Pass
|
|
*March 30, 2026*
|
|
|
|
Visual audit via screenshot analysis. No new features -- design refinement only.
|
|
|
|
- Nav tabs: icon-only with CSS tooltip (5 tabs, no overflow)
|
|
- Session list: grouped by Today / Yesterday / Earlier
|
|
- Active session: blue left border accent
|
|
- Role labels: Title Case, softened color, circular icons
|
|
- Code blocks: connected language header with separator
|
|
- Send button: gradient + hover lift
|
|
- Composer: blue glow ring on focus
|
|
- Toast: frosted glass with float animation
|
|
- Tool status moved from composer footer to activity bar above composer
|
|
- Empty session flood fixed (filter + cleanup endpoint + test autouse)
|
|
|
|
---
|
|
|
|
## [v0.1] Initial Build
|
|
*March 30, 2026*
|
|
|
|
Single-file web UI for Hermes. stdlib HTTP server, no external dependencies.
|
|
Three-panel layout: sessions sidebar, chat area, workspace panel.
|
|
|
|
**Core capabilities:**
|
|
- Send messages, receive SSE-streamed responses
|
|
- Session create/load/delete, auto-title from first message
|
|
- File upload with manual multipart parser
|
|
- Workspace file tree with directory navigation
|
|
- Tool approval card (4 choices: once, session, always, deny)
|
|
- INFLIGHT session-switch guard
|
|
- 10-model dropdown (OpenAI, Anthropic, Other)
|
|
- SSH tunnel access on port 8787
|
|
|
|
---
|
|
|
|
*Last updated: v0.34, April 5, 2026 | Tests: 433*
|