* feat: add comic/doodle theme style with local font
Add a new "comic" theme style that applies hand-drawn aesthetics (Comic Neue
font, bold borders, heavy font weight) while keeping the original light/dark
background colors. Font files are bundled locally to avoid Google Fonts CDN
dependency.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: update DisplaySettings to use renamed theme API and update brand assets
Rename mode/setMode/ThemeMode to brightness/setBrightness/BrightnessMode
to match the refactored useTheme composable. Update favicon and logo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Remove run.text accumulator and insertResponseTextOnce that caused
text blocks before and after tool calls to be concatenated into a
single message. Now response.output_text.done only sets finish_reason
without overwriting delta-accumulated content.
- Remove run.text, textInserted from ResponseRunState
- Remove insertResponseTextOnce method
- output_text.done only marks finish_reason='stop' on last message
- response.completed no longer calls insertResponseTextOnce
- Add 7 tests covering flush, abort, and multi-block text separation
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Defer all non-user message DB writes until response completion or
abort, instead of writing tool calls immediately during streaming.
This ensures correct message ordering and prevents the abort handler
from overwriting displayed messages with incomplete DB data.
- Remove immediate addMessage() calls from response.output_item.done
- Remove immediate addMessage() from insertResponseTextOnce
- Add flushResponseRunToDb() to batch-write all run messages on
both normal completion (markCompleted) and abort (handleAbort)
- Skip user messages in flush (already written in handleRun)
- Remove refreshActiveSession() from abort.completed frontend handler
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* refactor: migrate from /v1/runs to /v1/responses streaming API
Replace EventSource-based polling with direct SSE streaming via the
/v1/responses endpoint across all server-side callers (chat-run-socket,
context-compressor, gateway-client, agent-clients). Messages are now
written to DB in real-time during streaming, eliminating post-run sync.
Frontend chat store adds tool_call_id tracking for deduplication.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: bump version to 0.5.16 and add changelog
- Persist real API usage to usage table on response.completed
- Remove unused codex_reasoning_items field from message schema
- Fix unused variable warnings in chat-run-socket
- Bump version to 0.5.16
- Add changelog entries for 0.5.16 (8 locales)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Add a website icon link (globe) alongside the GitHub icon in the
sidebar footer. Shorten version label from "Hermes Web UI" to "Web UI".
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Add a "QQ Group" tab in the site header that opens a centered modal
with the QR code image. Includes mobile menu support and i18n.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Fix kanban loading spinner flickering on auto-refresh (silent mode)
- Fix group chat room-list transparent background on mobile
- Fix group chat sidebar auto-opening on mobile entry
- Fix page-header title overlapped by hamburger button on mobile
- Move hamburger button position to top: 10px
- Add changelog note about upgrading hermes-agent for kanban support
- Add i18n translations for all 8 locales
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Auto-detect Docker container environment and use service name
'hermes-agent' as default host instead of 127.0.0.1
- Replace hardcoded column names with SELECT * in session DB queries
to compat with older Hermes agent state.db schemas
- Remove unused UPSTREAM env var from docker-compose.yml
- Include err.message in syncFromHermes failure logs
- Add group chat rule to prevent self-mentioning
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: support run approval prompts in chat
* fix(chat): render approval prompts
* fix(chat): dedupe approval pattern labels
* chore: sync approval flow with current main
- update Hermes Agent approval support guidance to PR #21899
- initialize Hermes table schemas in session-sync tests
* feat: add IP-based login brute-force protection
- Per-IP rate limiting: 3 failed login attempts locks the IP for 1 hour
- Separate counters for password login and token auth
- Global safety net: 20 req/min, hard lock after 50 total failures
- Persistent lock state to ~/.hermes-web-ui/.login-lock.json (survives restarts)
- Manual unlock: edit or delete the lock file
- Frontend handles 429/503 responses with localized error messages
- i18n support for 8 languages
* feat: add locked IP management endpoint and UI
- GET /api/auth/locked-ips: list all currently locked IPs (protected)
- DELETE /api/auth/locked-ips/:ip: unlock a specific IP (protected)
- DELETE /api/auth/locked-ips: unlock all IPs (protected)
- AccountSettings: shows locked IPs with remaining time, unlock buttons
- i18n support for 8 languages
- Clean up stale .js artifacts, add .gitignore rule
* fix: cross-type IP lock and IPv6-compatible unlock route
- Password and token login now share IP lock state: if an IP is locked
by either method, ALL auth methods are blocked for that IP
- Changed unlock endpoint from path param to query param (?ip=xxx) to
support IPv6 addresses containing colons
- Merged unlockIp and unlockAll into a single handler
* chore: increase global login rate limit from 20 to 100 requests per minute
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: ekko <fqsy1416@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
The connect() method defaulted to port 8648, causing websocket connection
refused errors when the server was started with a custom port via
`hermes-web-ui start <port>` or PORT env var. Now reads from process.env.PORT.
Closes#536
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat: add landing page and docs website package
Add packages/website — a Vue 3 + Naive UI static site with landing page
and documentation, sharing the Pure Ink monochrome design with the main
app. Features: particle network hero animation, screenshot carousel,
feature grid, install guide tabs, GitHub star history, scroll reveal
animations, and Chinese/English bilingual support.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: add favicon to website package
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: use dynamic theme param for star history chart
Switch from CSS media query to JS-based dark mode detection so the
star-history SVG matches the current theme toggle state.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: resolve TypeScript strict mode errors in website components
- Remove unused isDark import in HeroSection
- Add null check for canvas parent element
- Rename unused img loop variable in ScreenshotsSection
- Remove unused NIcon import in SiteHeader
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: resolve TS narrowing errors in canvas resize closure
Use canvasRef.value directly inside resize() with local null check
instead of relying on outer closure narrowing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
FUN-Codex: add GPT models (5.5, 5.4, 5.4-mini, 5.3-codex, 5.3-codex-spark)
FUN-Claude: replace with actual Claude models from API (opus-4-7 down to 3-5-haiku)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
TerminalPanel was connecting on mount even when the drawer was closed
and the terminal tab was inactive. Combined with reconnectAttempts
resetting on every ws.onopen, this caused infinite reconnection loops
that spawned PTY processes until system limits were hit.
- Pass `visible` prop to TerminalPanel, only connect when terminal tab
is actually shown
- Move reconnectAttempts reset from ws.onopen to "created" handler so
only successful PTY creation resets the counter
- Remove unused onMounted import
Fixes#509
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
PR #470 changed the default listen host to undefined, letting Node.js
bind to IPv6 :: on systems that support it. This broke WSL2 where IPv6
dual-stack is unreliable — the server binds to :: but IPv4 127.0.0.1
connections fail, causing the health check to time out.
Revert to 0.0.0.0 as the default. Users who need IPv6 can set
BIND_HOST=:: explicitly.
Fixes#518
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat: add session export with full and compressed modes
Add export functionality that allows users to download session data
as JSON or plain text, with optional LLM-based context compression
for long conversations. Includes UI controls in chat panel, session
list, and history view, plus i18n strings for all 8 locales.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: close SQLite DB on shutdown to prevent lock conflicts on restart
The shutdown handler did not close the SQLite connection, leaving the
database locked when nodemon restarted the process. This caused the new
process to fail DB init, trigger the recovery path (delete + recreate),
and re-sync all sessions from Hermes on every dev restart.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Add export functionality that allows users to download session data
as JSON or plain text, with optional LLM-based context compression
for long conversations. Includes UI controls in chat panel, session
list, and history view, plus i18n strings for all 8 locales.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Added scanSkillsDir() function that scans both three-level
(skills/<category>/<skill>/SKILL.md) and two-level
(skills/<skill>/SKILL.md) directory structures.
- Flat skills (at two-level) are grouped under a new 'misc'
(雜項) category, displayed with Chinese name '雜項'.
- Updated listFiles() and readFile_() to handle 'misc' category
path mapping correctly.
- All tests pass (347 passed, 3 pre-existing failures unrelated
to this change).
Allow sending multiple messages while a run is active. Messages are
queued on the server and processed sequentially after each run
completes. Each completed assistant message triggers speech playback
independently, and the UI shows queue status with a badge indicator.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: update tests for new batch delete and update mechanism changes
**sessions-routes.test.ts:**
- Add missing batchRemove mock to controller mock
- Fix "No batchRemove export defined" error
**update-controller.test.ts:**
- Update test to expect direct npm/npm.cmd calls instead of dirname(process.execPath)
- Update timeout from 120000 to 10 * 60 * 1000 (10 minutes)
- Update spawn path check to use dynamic global prefix (expect.any)
Tests now match the refactored update mechanism that uses npm prefix -g
for reliable path resolution.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: add speechSynthesis mock to message-item-highlight tests
* test: fix all failing tests
- Add approvals mock to session-settings test
- Fix NSwitch stub to properly emit events
- Update usage stats test expectations for new field structure
- Mock getDb in model-context tests to avoid database lock errors
- Add speechSynthesis API mock to message-item-highlight tests
Related to v0.5.12 feature changes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The @ symbol is a special character in vue-i18n (used for linked formats).
Removing it from all changelog entries fixes:
- Chinese: @mention → mention
- English: @mention → mention
- All other languages: @mention → mention
This resolves the SyntaxError in message compilation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug: In group chat input, using keyboard (ArrowDown/ArrowUp + Enter) to
select an agent from the @mention dropdown always inserts the wrong agent
name (the first one), regardless of which item is visually highlighted.
Mouse click works correctly.
Root cause: naive-ui's NDropdown has its own internal keyboard state machine
that is independent from the component's activeIndex ref. When Enter is
pressed, NDropdown fires @select using its own stale index before
handleKeydown runs, always selecting the wrong agent. NDropdown exposes no
public API to synchronize its internal state, making this unfixable in place.
Fix: Replace NDropdown with a custom <div class="mention-dropdown">
rendered via v-for, with fully manual keyboard/click/hover control. This
eliminates the dual-state conflict entirely — there's a single activeIndex
for all interactions.
Additional improvements over the previous NDropdown-based implementation:
- Scroll follows the active item automatically (scrollToActive)
- Dropdown flips upward when insufficient space below (smart placement)
- Click-outside-to-close via document-level listener
- Transition animation matching NDropdown's fade-in-scale-up exactly
(0.2s cubic-bezier, scale 0.9->1 with opacity fade)
Co-authored-by: Fix Contributor <fix-contributor@hermes-web-ui.dev>
* feat: add batch delete functionality for chat sessions
Backend:
- Add batchRemove controller to handle bulk session deletion
- Add POST /api/hermes/sessions/batch-delete endpoint
- Support both local session store and CLI deletion
- Return detailed results (deleted, failed, errors)
Frontend:
- Add batch selection mode with checkboxes in SessionListItem
- Add batch selection toggle and select all button
- Add batch delete button with confirmation
- Update ChatPanel to manage selected session IDs
- Add batchDeleteSessions API function
i18n:
- Add batch delete translations for all 8 languages
- Simplify "Web UI/API Server Sessions" to "Sessions"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: vertically align buttons in session list header
Add inline-flex and center alignment to all buttons in session-list-actions
to ensure proper vertical centering with the title text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: ensure proper vertical alignment in session list header
- Set fixed height of 22px for session-list-actions
- Add min-height and height to all buttons
- Add line-height to session-list-title for text baseline alignment
- Add min-height: 0 to session-list-header to prevent flex stretch
This ensures the title and all action buttons are perfectly vertically centered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: call loadSessions after batch delete instead of looping deleteSession
The previous implementation was calling chatStore.deleteSession(id) in a loop
after batch delete API succeeded, which triggered individual delete API calls
for each session - causing n API requests instead of 1.
Now we simply call loadSessions() to refresh the session list from the server
after successful batch deletion, ensuring:
- Only 1 API request for batch delete
- UI stays in sync with server state
- No duplicate API calls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: improve update mechanism reliability
Major improvements to the update system:
**Path Resolution:**
- Remove unreliable dirname(process.execPath) assumption
- Use npm from PATH environment variable
- Dynamically get global prefix via `npm prefix -g`
- Calculate CLI path based on actual global install location
**Windows Support:**
- Remove complex cmd.exe wrapper logic
- Directly call npm.cmd (works on all Windows setups)
- Simplified quote handling
**Error Handling:**
- Add fallback error message (err.stderr || err.message || String(err))
- Add default success message when output is empty
- Wrap spawnRestart in try-finally to ensure cleanup
**Timing:**
- Increase timeout from 120s to 10min (slow network support)
- Increase restart delay from 2s to 3s (safer margin)
**Code Quality:**
- Remove unused functions (getNodeBinDir, getWindowsShell, quoteForWindowsCommand)
- Use constants instead of magic numbers (10 * 60 * 1000)
- More maintainable and cross-platform compatible
This fixes issues where updates would fail due to incorrect npm/CLI paths
on systems with non-standard Node.js installations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add database table for model context length configuration
- Add model_context table with provider/model/context_limit fields
- Implement UPSERT endpoint for model context configuration
- Add priority lookup: database > config.yaml > custom_providers > cache
- Add frontend click-to-edit UI in ChatInput with tooltip
- Add i18n support for context editing dialog (all 8 locales)
- Use context_limit field consistently across frontend and backend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use useMessage() composable instead of window.$message in ChatInput
- Remove incorrect NMessage import (not a component)
- Use useMessage() composable from naive-ui
- Replace window.$message?.xxx() with message.xxx()
- Fixes TypeScript build errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend:
- Filter out assistant messages with empty content in mapHermesMessages()
- Prevents displaying 194/585 empty messages in long sessions
- Reduces visual clutter and improves UX
Backend:
- Add message count limit (<= 200) to compression threshold checks
- Prevents compressing sessions with too many messages
- Improves compression performance and reliability
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add init system detection (systemd/launchd/windows-service/other)
- Automatically use "gateway run" mode for environments without service managers (WSL/Docker/Termux/proot)
- Add safeNetworkInterfaces() wrapper to handle uv_interface_addresses permission errors in proot
- Prevents ERR_SYSTEM_ERROR (errno 13) when os.networkInterfaces() fails in restricted environments
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove streaming-indicator and tool calls panel from HistoryMessageList component.
History sessions should not show "typing" indicators as they are read-only, completed sessions.
Changes:
- Remove streaming-indicator video animation from history
- Remove tool calls panel from history
- Clean up unused imports and functions
- Remove unnecessary watchers
- Clean up related styles (~200 lines removed)
- Update version to 0.5.11 in package.json
- Update changelog version to 0.5.11
This fixes the issue where history sessions incorrectly showed streaming indicators
when there was an active run in another session.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: call upstream stop API when aborting a run
- Modified handleAbort to call POST /v1/runs/{run_id}/stop endpoint
- Use profile-specific upstream URL and API key from gatewayManager
- Add 5-second timeout with error handling and logging
- Keep local abortController.abort() for EventSource cleanup
- Change handleAbort to async method and update call site
This ensures the upstream Hermes gateway is properly notified
when a user aborts a run, allowing graceful termination.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: close ChatRunSocket connections on shutdown to prevent hanging
- Add close() method to ChatRunSocket to abort all active runs
and clear session state
- Pass chatRunServer to bindShutdown and close it before
groupChatServer during shutdown
- This prevents EventSource connections and abort controllers
from keeping the process alive during nodemon restart
Fixes the "still waiting for sub-process to finish" issue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Handle chat run abort lifecycle
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>