Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d13a174
fix: keep compression anchor stable in windowed transcript
LumenYoung May 13, 2026
b7ac5a8
Trim session tail response overhead
May 13, 2026
be32b90
docs: refresh current project snapshot
May 13, 2026
65fa18c
docs: add agent onboarding entrypoint
May 13, 2026
155a727
docs: refresh current snapshot for v0.51.54
May 13, 2026
bc6a949
fix: place compression banner at persisted marker
LumenYoung May 13, 2026
9e45de4
fix: prevent 404 on /api/session/compress/status during session switch
fxd-jason May 13, 2026
da73c00
Harden session tail redaction prefilter
May 13, 2026
676d1f9
fix: prevent concurrent send() from losing messages or swallowing str…
fxd-jason May 13, 2026
7f01abf
fix: ignore stale compaction markers when placing banner
LumenYoung May 13, 2026
3289c44
fix: refresh context ring after compression
LumenYoung May 13, 2026
a8816fe
feat: show steer indicator as transient DOM element
fxd-jason May 13, 2026
2567242
test: widen _trySteer capture windows for steer indicator code
fxd-jason May 13, 2026
1a4e052
fix: keep background metering from overwriting visible session usage
xz-dev May 13, 2026
0b97f6d
fix: preserve thinking card state during reasoning updates
xz-dev May 13, 2026
3c20e10
Merge pull request #2189 into stage-348
May 13, 2026
acefaa1
Merge pull request #2190 into stage-348
May 13, 2026
5ba097c
Merge pull request #2162 into stage-348
May 13, 2026
785b1fe
Merge pull request #2186 into stage-348
May 13, 2026
63a1855
Merge pull request #2187 into stage-348
May 13, 2026
479e388
Merge pull request #2182 into stage-348
May 13, 2026
fbd1e27
Merge pull request #2185 into stage-348
May 13, 2026
ef042ad
Merge pull request #2188 into stage-348
May 13, 2026
39df1a1
Merge pull request #2171 into stage-348
May 13, 2026
3bfa0fc
docs: CHANGELOG stage-348 — close v0.51.54, open Unreleased for 9-PR …
May 13, 2026
7c2b278
stage-348: apply Opus SHOULD-FIX-pre-merge — add '://' to _SENSITIVE_…
May 13, 2026
32ba73c
stage-348: fix CI-vs-local divergence on Opus prefilter test
May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
# Default workspace directory shown on first launch
# HERMES_WEBUI_DEFAULT_WORKSPACE=~/workspace

# Optional model override. Leave unset to use the active Hermes provider default.
# HERMES_WEBUI_DEFAULT_MODEL=

# Base directory for all Hermes state (affects all paths above if set)
# HERMES_HOME=~/.hermes

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ archive/
!.env.docker.example
.claude/
CLAUDE.md
AGENTS.md
AGENTS.local.md
.cursorrules
.windsurfrules
.aider*
Expand Down
68 changes: 68 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Agent instructions for Hermes WebUI

This file is the shared entry point for AI assistants working in this
repository. Keep it project-specific and safe to publish. Do not put personal
machine setup, private network details, credentials, tokens, or local-only
workflow notes here.

## Read first

Before making changes, read:

1. `README.md`
2. `CONTRIBUTING.md`
3. `CHANGELOG.md`

For architecture, testing, or setup work, also read the matching reference:

- `ARCHITECTURE.md` for design constraints and current module layout
- `TESTING.md` for local verification commands and manual test guidance
- `docs/onboarding.md` for first-run onboarding behavior
- `docs/troubleshooting.md` for diagnostic flows

## Onboarding and reinstall support

If the task involves install, reinstall, bootstrap, first-run onboarding,
provider setup, local model server setup, Docker onboarding, WSL onboarding, or
support for a failed first run, read `docs/onboarding-agent-checklist.md`
before running commands or inspecting logs.

Follow that checklist's safety rules:

- use isolated `HERMES_HOME` and `HERMES_WEBUI_STATE_DIR` for trials unless the
human explicitly asks to use real state
- do not delete or overwrite a real `~/.hermes` directory without explicit
approval
- do not print API keys, OAuth tokens, cookies, full `.env` files, full
`auth.json` files, or password hashes
- collect non-secret status and log evidence before recommending a fix

## Contribution style

- Keep changes focused on one logical problem.
- Prefer the existing Python + vanilla JavaScript structure over new
dependencies or build steps.
- Update docs when changing setup, onboarding, runtime behavior, architecture,
or testing guidance.
- Update `CHANGELOG.md` for user-visible behavior, setup, workflow, or
documentation changes that should be release-note ready.
- For UI or UX changes, follow `CONTRIBUTING.md`: include before/after evidence
and test relevant responsive states.

## Local state and secrets

Hermes WebUI can read and write real agent state, sessions, workspaces,
credentials, and cron data. Treat local validation as potentially destructive
unless you have confirmed the active state directories.

Prefer isolated trial state for experiments:

```bash
HERMES_HOME=/tmp/hermes-webui-agent-home \
HERMES_WEBUI_STATE_DIR=/tmp/hermes-webui-agent-state \
HERMES_WEBUI_PORT=8789 \
python3 bootstrap.py
```

Do not include private machine instructions in this tracked file. Use a
git-ignored local note for personal workflow details.
181 changes: 89 additions & 92 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

## [Unreleased]

### Added

- **PR #2162** by @franksong2702 — Refresh project-snapshot docs and add an explicit agent-onboarding entrypoint. Updates `AGENTS.md` (newly added), `README.md`, `ARCHITECTURE.md`, `TESTING.md`, `.env.example`, `.gitignore`, `docker_init.bash`, `docs/onboarding.md`, plus a new `docs/onboarding-agent-checklist.md`. Refreshes stale current-state claims (test counts, default model semantics, state/log paths, file inventory line counts), adds an explicit agent-safe install/test/run path for AI assistants doing human-assisted reinstalls, and adds `tests/test_docs_gitignore_policy.py` to pin the gitignore policy. Docs/test-only — no runtime behavior change.

- **PR #2187** by @jasonjcwu (split from #2164) — Steer messages now appear in the chat transcript as semi-transparent italic user bubbles with a "Steer" badge while the turn is in flight, giving users clear feedback that their injected message reached the agent context. Previously, when `busy_input_mode` was `steer`, the injected message vanished entirely after a brief toast. The bubble is transient — the `done` event replaces `S.messages` with server state, so the badge disappears once the turn completes. CSS-only styling for the badge; no schema change.

### Fixed

- **PR #2171** by @franksong2702 — Session tail-window response (`/api/session?messages=1&resolve_model=0&msg_limit=30`) on long sessions is materially faster. Adds a cheap credential-marker prefilter before the full agent+fallback redaction pass — strings without known credential markers return immediately, while strings with likely markers still run the existing hard redaction. Skips the historical `session.tool_calls` list in the payload when returned messages already carry per-message tool metadata, avoiding sending the full historical list for every tail-window request. Same security and tool-card rendering behavior preserved. 173-line regression suite in `tests/test_session_tail_payload.py` + 81 LOC of new credential-prefilter tests in `tests/test_security_redaction.py`.

- **PR #2182** by @LumenYoung — Compression banner no longer drifts away from the actual compaction boundary in long WebUI conversations. Fixes two related cases: (1) windowed transcript rendering — `renderMessages()` renders only a sliced `renderVisWithIdx` window, and when the compression anchor index wasn't found in the rendered window, the previous code passed the full visible index directly into the rendered-window array (usually out-of-window for long sessions), so the compression card fell back to `inner.appendChild(node)` and appeared near the newest messages instead of near the boundary; (2) persisted compaction reference messages — the `[CONTEXT COMPACTION — REFERENCE ONLY]` marker is now used as a stronger placement signal than anchor metadata when both are present. 60-line regression suite covering both cases.

- **PR #2185** by @jasonjcwu — Switching sessions no longer surfaces a `Compression failed: not found` toast on the common case (no active compression). The `/api/session/compress/status` route handler was returning `None` from `j()` instead of `True`, so in edge cases (stale process state, exception during response write) the `do_GET` 404 fallback fired. Backend: `handle_get` now explicitly returns `True` after the status handler call. Frontend: `resumeManualCompressionForSession` catches 404 silently — no compression job means no error to surface. 139-line regression suite in `tests/test_compress_status_404_fix.py`.

- **PR #2186** by @jasonjcwu (split from #2164) — Concurrent `send()` no longer drops user messages or swallows stream output. Two messages sent in rapid succession (queue drain + user click) could both pass the `S.busy` check because `setBusy(true)` only runs **after** the first `await` inside `send()`, leaving a window where two async `send()` calls ran concurrently. Adds a synchronous `_sendInProgress` flag at the very top of `send()` (before any `await`). Concurrent calls re-queue the message instead of silently dropping. `try/finally` ensures the flag resets on all exit paths.

- **PR #2188** by @LumenYoung — Context progress ring refreshes immediately when automatic context compression completes. Previously the `compressed` SSE event only updated the compression card/toast; the context ring kept showing pre-compression token usage until a later `metering`/`done` update or the next message — making the UI look like compression had completed while the session was still near the limit. Backend now includes a live usage snapshot in the `compressed` SSE payload; frontend reads it and updates `S.lastUsage` + the composer context indicator atomically with the compression-card transition.

- **PR #2189** by @xz-dev — Live metering usage updates are now scoped to the session currently visible in the chat pane. Pre-fix, background streams could overwrite `S.lastUsage` and the composer context indicator with metering data from a session the user wasn't looking at, making the indicator misleading on the active session. Four-line scope check inside the metering update path; no schema or SSE payload change.

- **PR #2190** by @xz-dev — Thinking-card reasoning updates now update text in place during reasoning deltas instead of rebuilding the card DOM on every append. Preserves expand state and scroll position across reasoning streaming, so users reading a long reasoning block don't get bounced to the top on every chunk. When a thinking card exists, the update path now sets `pre.textContent` directly; full-rebuild path only fires when no existing card is present.

### Stage-348 maintainer fixes

- **`api/helpers.py:_SENSITIVE_LOWER_MARKERS` — add `"://"` URL marker** — Opus SHOULD-FIX-pre-merge on PR #2171's credential prefilter. The prefilter listed only specific DB scheme prefixes (`postgres://`, `mysql://`, `mongodb://`, `redis://`, `amqp://`) and a closed set of form keys (`token=`, `secret=`, `password=`, `authorization=`, `key=`), so OAuth callback URLs (`https://example.com/callback?code=AUTH_OPAQUE`), URL userinfo (`https://admin:supersecret@api.example.com/v1`), and signed-URL query params (`?signature=...`, `?session=...`) bypassed the hard agent redactor entirely — defeating the "WebUI API responses are a hard safety boundary" comment at `helpers.py:189`. Adding the generic `"://"` marker routes every http(s)/ws(s)/ftp URL to the hard redactor (which then selectively redacts only the sensitive substrings — plain `https://example.com/guide.html` URLs still pass through unchanged). Regression-pinned with 5 new parametric cases in `tests/test_security_redaction.py` (`test_redact_text_prefilter_covers_url_userinfo_and_sensitive_query_params`) covering OAuth code, URL userinfo, signed-URL signature, session query param, and WebSocket token — plus a negative-case `test_redact_text_prefilter_admits_plain_urls_without_sensitive_params` confirming the redactor doesn't over-redact plain URLs. Verified by reverting the fix locally: all 5 sensitive-URL cases fail; restoring the fix: all 5 pass. ~6 LOC code + ~50 LOC test.

## [v0.51.54] — 2026-05-13 — Release AD (stage-347 — singleton self-built — NVIDIA NIM prefix preservation fix #2179)

### Fixed

- **PR #2179** (self-built, closes #2177) — NVIDIA NIM no longer 404s when WebUI is configured with `provider: nvidia` and a `nvidia/<model>` id. `resolve_model_provider()` in `api/config.py` had the `_PORTAL_PROVIDERS` guard (Nous, OpenCode-Zen, OpenCode-Go, NVIDIA NIM — providers whose APIs require the full namespaced `provider/model` wire format) sitting **after** the `prefix == config_provider` strip branch. For `model_id="nvidia/nemotron-3-super-120b-a12b"` + `config_provider="nvidia"`, the strip branch fired first and returned the bare `nemotron-...` to NIM, which then 404'd because NIM requires the full path. Same bug class as #854 / #894 (Nous portal). The guard was originally added with NVIDIA in mind but the structural ordering was wrong. Fix is a pure reorder of two `if` blocks — hoist `_PORTAL_PROVIDERS` ahead of the strip — so all portal providers always preserve the full `provider/model` id regardless of whether the prefix happens to equal the provider name. Also closes a latent symmetric bug for the Nous case if a `nous/<model>` id ever entered the catalog. Cross-tool trace against hermes-agent's `hermes_cli/models.py` (line 59, 177, 237-239) and `agent/model_metadata.py:68` confirms the agent CLI sends `nvidia/nemotron-...` verbatim — both tools now agree on the wire format. 118-line regression suite covering the reported case, cross-namespace `qwen/` and `meta/` ids, every static nvidia model in `_PROVIDER_MODELS`, the latent `nous/<model>` ordering pin, and a non-portal-provider regression pin for the anthropic strip behavior. nesquena APPROVED with 200-line end-to-end trace + 12-shape behavioural harness + cross-tool wire-format verification. Reported on Discord by @vishnu in #report-bugs.
Expand Down
63 changes: 35 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ The bootstrap will:

If provider setup is still incomplete after install, the onboarding wizard will point you to finish it with `hermes model` instead of trying to replicate the full CLI setup in-browser.
For a step-by-step walkthrough of the wizard, provider choices, local model server Base URLs, and safe re-runs, see [`docs/onboarding.md`](docs/onboarding.md).
If an AI assistant is helping with install, reinstall, bootstrap, provider setup, or first-run support, have it read [`docs/onboarding-agent-checklist.md`](docs/onboarding-agent-checklist.md) before running commands or inspecting logs.

---

Expand Down Expand Up @@ -267,7 +268,7 @@ Full list of environment variables:
| `HERMES_WEBUI_PORT` | `8787` | Port |
| `HERMES_WEBUI_STATE_DIR` | `~/.hermes/webui` | Where sessions and state are stored |
| `HERMES_WEBUI_DEFAULT_WORKSPACE` | `~/workspace` | Default workspace |
| `HERMES_WEBUI_DEFAULT_MODEL` | `openai/gpt-5.4-mini` | Default model |
| `HERMES_WEBUI_DEFAULT_MODEL` | *(provider default)* | Optional model override; leave unset to use the active Hermes provider default |
| `HERMES_WEBUI_PASSWORD` | *(unset)* | Set to enable password authentication |
| `HERMES_WEBUI_EXTENSION_DIR` | *(unset)* | Optional local directory served at `/extensions/`; must point to an existing directory before extension injection is enabled |
| `HERMES_WEBUI_EXTENSION_SCRIPT_URLS` | *(unset)* | Optional comma-separated same-origin script URLs to inject; see [WebUI Extensions](docs/EXTENSIONS.md) |
Expand Down Expand Up @@ -367,9 +368,9 @@ Or using the agent venv explicitly:
/path/to/hermes-agent/venv/bin/python -m pytest tests/ -v
```

Tests run against an isolated server on port 8788 with a separate state directory.
Production data and real cron jobs are never touched. Current count: **3309 tests**
across 100+ test files.
Tests run against an isolated server with a separate state directory.
Production data and real cron jobs are never touched. Current snapshot:
**5303 tests collected** across **488 test files**.

---

Expand Down Expand Up @@ -491,33 +492,33 @@ across 100+ test files.
## Architecture

```
server.py HTTP routing shell + auth middleware (~154 lines)
server.py HTTP routing shell + auth middleware (~446 lines)
api/
auth.py Optional password authentication, signed cookies (~201 lines)
config.py Discovery, globals, model detection, reloadable config (~1110 lines)
helpers.py HTTP helpers, security headers (~175 lines)
models.py Session model + CRUD + CLI bridge (~377 lines)
onboarding.py First-run onboarding wizard, OAuth provider support (~507 lines)
profiles.py Profile state management, hermes_cli wrapper (~411 lines)
routes.py All GET + POST route handlers (~2250 lines)
state_sync.py /insights sync — message_count to state.db (~113 lines)
streaming.py SSE engine, run_agent, cancel support (~660 lines)
updates.py Self-update check and release notes (~257 lines)
upload.py Multipart parser, file upload handler (~82 lines)
workspace.py File ops, workspace helpers, git detection (~288 lines)
auth.py Optional password authentication, signed cookies (~366 lines)
config.py Discovery, globals, model detection, reloadable config (~4139 lines)
helpers.py HTTP helpers, security headers (~302 lines)
models.py Session model + CRUD + CLI bridge (~1927 lines)
onboarding.py First-run onboarding wizard, OAuth provider support (~1002 lines)
profiles.py Profile state management, hermes_cli wrapper (~1056 lines)
routes.py All GET + POST route handlers (~9772 lines)
state_sync.py /insights sync — message_count to state.db (~118 lines)
streaming.py SSE engine, run_agent, cancel support (~4420 lines)
updates.py Self-update check and release notes (~545 lines)
upload.py Multipart parser, file upload handler (~284 lines)
workspace.py File ops, workspace helpers, git detection (~810 lines)
static/
index.html HTML template (~600 lines)
style.css All CSS incl. mobile responsive, themes (~1050 lines)
ui.js DOM helpers, renderMd, tool cards, context indicator (~1740 lines)
workspace.js File preview, file ops, git badge (~286 lines)
sessions.js Session CRUD, collapsible groups, search, reload recovery (~800 lines)
messages.js send(), SSE handlers, live streaming, session recovery (~655 lines)
panels.js Cron, skills, memory, profiles, settings (~1438 lines)
commands.js Slash command autocomplete (~267 lines)
boot.js Mobile nav, voice input, boot IIFE (~524 lines)
index.html HTML template (~1323 lines)
style.css All CSS incl. mobile responsive, themes (~3767 lines)
ui.js DOM helpers, renderMd, tool cards, context indicator (~7216 lines)
workspace.js File preview, file ops, git badge (~369 lines)
sessions.js Session CRUD, collapsible groups, search, reload recovery (~3517 lines)
messages.js send(), SSE handlers, live streaming, session recovery (~2301 lines)
panels.js Cron, skills, memory, profiles, settings (~6480 lines)
commands.js Slash command autocomplete (~1302 lines)
boot.js Mobile nav, voice input, boot IIFE (~1607 lines)
tests/
conftest.py Isolated test server (port 8788)
61 test files 961 test functions
conftest.py Isolated test server/state fixtures
488 test files 5303 tests collected
Dockerfile python:3.12-slim container image
docker-compose.yml Compose with named volume and optional auth
.github/workflows/ CI: multi-arch Docker build + GitHub Release on tag
Expand All @@ -537,8 +538,14 @@ State lives outside the repo at `~/.hermes/webui/` by default
- `CHANGELOG.md` -- release notes per sprint
- `SPRINTS.md` -- forward sprint plan with CLI + Claude parity targets
- `THEMES.md` -- theme system documentation, custom theme guide
- `docs/docker.md` -- Docker compose setup, common failures, and bind-mount migration
- `docs/supervisor.md` -- launchd, systemd, supervisord, runit, and s6 process-supervisor setup
- `docs/onboarding.md` -- first-run wizard, provider setup, local model server Base URLs, and safe re-runs
- `docs/onboarding-agent-checklist.md` -- safety rules, evidence commands, and pass/fail checks for assistant-led install or reinstall support
- `docs/troubleshooting.md` -- diagnostic flows for common failures (e.g. "AIAgent not available")
- `docs/wsl-autostart.md` -- WSL2 auto-start at Windows login
- `docs/EXTENSIONS.md` -- administrator-controlled WebUI extension injection
- `docs/rfcs/README.md` -- RFC index for larger architecture and durability proposals

## Contributors

Expand Down
Loading
Loading