Commit Graph

558 Commits

Author SHA1 Message Date
Nathan Esquenazi
3c771c4d2c Merge pull request #344 from nesquena/fix/testing-md-port-8786
docs: fix stale port 8786 in TESTING.md prerequisites
2026-04-12 23:49:22 -07:00
Nathan Esquenazi
2398ec51fe docs: fix stale port 8786 in TESTING.md prerequisites — correct port is 8787 2026-04-13 06:38:14 +00:00
nesquena-hermes
4eaf4e0743 docs: fix stale test count in README architecture block (791 → 802) (#340)
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 22:07:36 -07:00
nesquena-hermes
1c0d13c6d9 fix: title auto-generation + mobile close button (PR #333) + v0.50.10
* fix(merge): preserve auth errors + fix title auto-generation

* fix(css): hide mobile close button on desktop for workspace panel

* fix: hide duplicate collapse button in mobile workspace panel view

* docs: v0.50.10 — title auto-generation fix + mobile close button (PR #333)

---------

Co-authored-by: MILO <milo@MILOdeMacMINI-2.local>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 21:45:25 -07:00
nesquena-hermes
4c78d8a56b fix: correct Simplified Chinese (zh) locale — remove Traditional Chinese strings (#338)
fix: correct Simplified Chinese (zh) locale — remove Traditional Chinese strings
2026-04-12 19:20:20 -07:00
Hermes Agent
229680ae1e fix: zh-Hant approval_btn_always — use Traditional Chinese chars (始終允許 not 始终允许) 2026-04-13 02:19:57 +00:00
Nathan Esquenazi
e0e642a239 fix: apply reviewer correction for zh-Hant approval_btn_always
Per @shiqingshan review: use Simplified Chinese for approval_btn_always.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:53:37 -07:00
Nathan Esquenazi
e684fdd731 fix: replace Traditional Chinese with Simplified in zh locale (#337)
The zh (Simplified Chinese) locale had ~40 strings from a "missing keys"
section that were actually Traditional Chinese or garbled text. This
replaces them with correct Simplified Chinese, removes duplicate keys,
and fixes a garbled zh-Hant string (姊妹允許 → 始終允許).

Fixes #337

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:10:37 -07:00
Nathan Esquenazi
2a3324c201 fix: allow onboarding from Docker bridge networks (closes #334) (#335)
Expands the onboarding setup IP check from 127.0.0.1-only to any loopback or RFC-1918 private address. Docker containers connect via 172.17.x.x — previously blocked with a 403. Public IPs still blocked unless auth enabled. 791 tests pass.
2026-04-12 16:35:47 -07:00
Nathan Esquenazi
39d42be396 fix: deduplicate model dropdown (hyphen vs dot) + README accuracy (#332)
Normalizes hyphens to dots in backend model-ID comparison so claude-sonnet-4-6 (hermes-agent format) matches claude-sonnet-4.6 (WebUI list) and no duplicate entry is injected. README line counts and test count corrected. 791 tests, all pass.
2026-04-12 14:45:39 -07:00
nesquena-hermes
2fc19a8326 feat: OAuth provider onboarding path — Codex/Copilot no longer blocks setup (#331)
Fixes bug 2 from issue #329. current_is_oauth flag; confirmation card for OAuth providers; KeyError fix in _build_setup_catalog. 15 new tests, 791 total.
2026-04-12 14:28:16 -07:00
nesquena-hermes
7d9d7e7b66 feat: HERMES_WEBUI_SKIP_ONBOARDING env var + synchronous key reload (#330)
Fixes bugs 1+3 from issue #329. Skip-onboarding env var (with chat_ready guard); os.environ set synchronously after key write. 8 new tests, 776 total.
2026-04-12 14:26:00 -07:00
nesquena-hermes
9c44d0cf3e fix: strip think tags when model emits leading whitespace before <think> (#327)
Remove ^ anchor from think/Gemma regexes in ui.js; trimStart() before startsWith checks in messages.js streaming path. Fixes MiniMax M2.7 and any model emitting leading newlines before <think>. 10 new tests, 768 total.
2026-04-12 14:07:00 -07:00
Nathan Esquenazi
7552cd3e9b Merge pull request #328 from nesquena/fix/docker-compose-workspace-volume
fix: add missing workspace volume to two-container compose (#326)
2026-04-12 13:47:17 -07:00
Nathan Esquenazi
f316fb7502 fix: add missing workspace volume to two-container compose (#326)
docker-compose.two-container.yml was missing the ~/workspace:/workspace
volume mount that the single-container compose already has. Without it
the workspace directory inside the container is ephemeral and the user
can't browse their actual files.

Fixes #326

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:46:59 -07:00
Nathan Esquenazi
50583f0667 Merge pull request #325 from nesquena/fix/docker-restart-venv
fix: Docker container restart without recreating (#324)
2026-04-12 13:27:41 -07:00
Nathan Esquenazi
26c24867e6 fix: Docker container restart without recreating (#324)
uv venv fails with 'A virtual environment already exists' when the
container is stopped and started (not removed). The venv persists in
the container filesystem between stop/start cycles.

Fix: skip venv creation and dependency installation if they already
exist from a previous run. Uses two checks:
- /app/venv/bin/python3 exists → skip venv creation
- /app/venv/.deps_installed marker → skip pip install

This also makes restarts much faster since deps don't need to be
reinstalled every time the container starts.

Fixes #324

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:27:21 -07:00
Nathan Esquenazi
2562567730 fix: onboarding completes gracefully for pre-configured providers (closes #322) (#323)
OAuth/CLI-configured providers (openai-codex, copilot, nous) no longer blocked by onboarding wizard. 5 new tests, 758 total.
2026-04-12 13:22:48 -07:00
nesquena-hermes
2b21bb68b8 feat: workspace panel state persists across refreshes (#321)
localStorage key hermes-webui-workspace-panel saves open/closed on every state change; restored on boot before syncWorkspacePanelState(). 7 new tests, 753 total.
2026-04-12 12:50:32 -07:00
nesquena-hermes
84ca4d617b fix: mobile Enter inserts newline (closes #269) (#320)
Cherry-pick of mobile Enter newline fix from #315. On touch-primary devices (pointer:coarse), Enter inserts a newline. Desktop unchanged. 4 new tests, 746 total.
2026-04-12 12:41:12 -07:00
nesquena-hermes
9021e76708 docs: fix sprint34 test count 19→21, cascade to correct 742 total (#319)
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 12:19:35 -07:00
nesquena-hermes
eddf3249c1 docs: add contributor recognition section to README, tag contributors in CHANGELOG (#317)
- Add ## Contributors section to README with named thanks for all external
  contributors: @aronprins (v0.50.0 UI overhaul), @iRonin (6-PR security
  sprint), @DavidSchuchert (German i18n), @kevin-ho (OLED theme),
  @Bobby9228 (mobile profiles button), @franksong2702 (title guard +
  breadcrumb), @tgaalman (thinking card fix), @smurmann (custom provider
  routing), @jeffscottward (Haiku model ID)
- Add contributor attribution links to CHANGELOG entries for v0.50.0,
  v0.49.3, v0.49.1, v0.41.0, and the German/routing/OLED entries

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 12:04:42 -07:00
nesquena-hermes
ede1a5fc50 feat: composer-centric UI refresh + Hermes Control Center (v0.50.0, closes #242)
* Polish workspace panel behavior and app dialogs

* Replace remaining emoji UI glyphs with Lucide icons

* Redesign composer footer around model and context controls

Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.

Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/

* Remove obsolete activity bar

Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.

This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.

* Move workspace and model switching into composer footer

* Move profile switching into composer footer

* Refactor Hermes control center UI

* Redesign control center settings modal layout

Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.

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

* Put session item actions in a dropdown

* Use Hermes mark in sidebar control button

* Reset control center section on close

* Drop session-item left border indicator

Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.

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

* Increase session search input vertical padding

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

* Normalise odd pixel values across UI

Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.

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

* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)

The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints

Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.

* Improve composer footer mobile responsiveness and UX

- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior

* Fix duplicate files button and broken workspace close on mobile

Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().

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

* Fix model chip icon vertical alignment in composer footer

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

* Fix workspace toggle button hidden on desktop by conflicting CSS class

Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.

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

* Fix session actions dots button inaccessible on mobile sidebar

Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.

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

* Fix composer footer manage links not opening sidebar panel

The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.

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

* Widen icon-only composer chips breakpoint from 400px to 768px

Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.

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

* Fix composer-left vertical scrollbar by setting overflow-y:hidden

When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.

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

* fix: resolve rebase conflicts and fix control center test assertions

- Resolved 4 conflicts during rebase onto master (workspace.js,
  boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
  cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion

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

* fix: update version badge in System tab to v0.49.4

* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge

---------

Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 11:55:40 -07:00
nesquena-hermes
ed2d55f020 docs: fix test count totals in CHANGELOG for v0.49.2 and v0.49.3 (#314)
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 11:18:55 -07:00
nesquena-hermes
28354a9702 docs: v0.49.4 release — cancel cleanup fix (#313)
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 11:10:25 -07:00
nesquena-hermes
bd3ec45aa9 fix: cancel cleanup no longer depends on SSE event (closes #299)
cancelStream() now clears S.activeStreamId, calls setBusy(false),
setStatus(''), and hides the cancel button directly after the cancel
API request completes. Previously cleanup depended on the SSE 'cancel'
event, which never arrived if the connection was already closed —
leaving 'Cancelling...' status and busy spinner stuck indefinitely.

The SSE cancel handler in messages.js still fires when the connection
is alive and performs additional cleanup (adds 'Task cancelled.' message,
clears tool cards). All operations are idempotent.

9 new tests in tests/test_sprint36.py.

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 11:08:59 -07:00
nesquena-hermes
74a4263056 docs: v0.49.3 release — session title guard + breadcrumb nav (#311)
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 10:53:17 -07:00
nesquena-hermes
28a0f0bef9 fix+feat: session title guard + breadcrumb nav + wider panel + responsive msgs (closes #300, #292)
PR #301 changes:
- api/streaming.py: guard title_from() with s.title == 'Untitled' check
- api/routes.py: same guard in sync/non-streaming path

PR #302 changes (cleaned — restores accidentally-removed features):
- static/boot.js: PANEL_MAX 500 -> 1200
- static/boot.js: clearPreview() calls renderBreadcrumb() to restore dir view
- static/style.css: responsive .messages-inner breakpoints (1400px/1800px)
- static/workspace.js: renderFileBreadcrumb() function with clickable segments
- static/workspace.js: openFile() calls renderFileBreadcrumb(path)

12 new tests in tests/test_sprint35.py

Note: PR #302 branch contained several accidental regressions (removed app-dialog
system, onboarding CSS, _checkProviderMismatch, closeMobileFiles, etc.) that were
not part of its stated scope. This clean branch applies only the three intended
features on top of current master.

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 10:51:48 -07:00
Nathan Esquenazi
b12a682121 ci: add GitHub Actions workflow to run tests on PRs (#307)
Runs pytest suite across Python 3.11, 3.12, and 3.13 on ubuntu-latest.
Agent-dependent tests auto-skip via existing conftest logic.
Triggers on PRs targeting master and pushes to master.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:38:27 -07:00
nesquena-hermes
bc16545794 docs: v0.49.2 release — OAuth provider onboarding fix (#308)
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 10:38:14 -07:00
nesquena-hermes
a13a1e0b9e fix: recognize OAuth providers as ready in onboarding (closes #303, #304)
* fix: recognize OAuth providers as ready in onboarding (closes #303, #304)

OAuth-authenticated providers (GitHub Copilot, OpenAI Codex, Nous Portal,
Qwen OAuth) were incorrectly blocked by the first-run onboarding wizard
because _status_from_runtime() only treated providers in
_SUPPORTED_PROVIDER_SETUPS as valid, and _provider_api_key_present() only
checked for plain API keys.

Changes in api/onboarding.py:
- Add _provider_oauth_authenticated(provider, hermes_home): checks
  hermes_cli.auth.get_auth_status() first (authoritative), then falls back
  to parsing ~/.hermes/auth.json directly for the known OAuth provider IDs
  (openai-codex, copilot, copilot-acp, qwen-oauth, nous).
- _status_from_runtime(): add else branch for providers not in
  _SUPPORTED_PROVIDER_SETUPS; calls _provider_oauth_authenticated() so
  copilot/openai-codex users with valid credentials get provider_ready=True.
- Fix misleading 'API key' wording in provider_incomplete note for OAuth
  providers; now says 'Run hermes auth or hermes model to complete setup.'

19 new tests in tests/test_sprint34.py covering all branches.

* fix: mock _HERMES_FOUND in _status_from_runtime tests

5 tests in TestStatusFromRuntimeOAuth failed because _status_from_runtime()
short-circuits to 'agent_unavailable' when _HERMES_FOUND is False.
The tests passed imports_ok=True but _HERMES_FOUND is a separate module-level
flag. Fixed: _call() helper now mocks _HERMES_FOUND=True with restore in finally.

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

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:37:38 -07:00
nesquena-hermes
fc43b897c5 docs: v0.49.1 release notes — Docker docs + mobile Profiles button
- CHANGELOG: entries for #291 (Docker docs) and #265 (mobile Profiles button)
- ROADMAP: sprint table row + header date/count updated to v0.49.1/700
- TESTING.md: test count 700
- SPRINTS.md: version v0.49.1 + test count 700
- static/index.html: version bumped to v0.49.1

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 00:43:45 -07:00
nesquena-hermes
d6a925cf11 feat(mobile): add Profiles button to mobile bottom navigation (#265)
Adds a Profiles button as the last item in the mobile bottom nav bar,
making the Profiles panel reachable on mobile without opening the sidebar.

Fixes from original PR:
- Uses mobileSwitchPanel('profiles') not the broken two-call approach
- data-panel='profiles' attribute present for active-highlight state
- SVG 20x20 stroke-width 1.5 matching all other mobile nav icons
- Placed last (Chat → Tasks → Skills → Memory → Spaces → Profiles)
- 3 new tests in test_mobile_layout.py covering presence, handler, and order

Tests: 700 passed (up from 697)

Co-authored-by: @gabogabucho

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 00:41:52 -07:00
Nathan Esquenazi
5468b04550 docs: two-container Docker setup for Agent + WebUI (#288)
Adds docker-compose.two-container.yml and README section for running
hermes-agent and hermes-webui in separate Docker containers connected
via shared volumes.

The key insight: the WebUI imports hermes-agent's Python modules
directly (not via HTTP), so the agent source must be mounted into
the WebUI container. The existing docker_init.bash handles installing
the agent's dependencies at startup via uv pip install.

Shared volumes:
- hermes-home: config, sessions, skills, memory (~/.hermes)
- hermes-agent-src: agent source code for Python import

Fixes #288

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:37:35 -07:00
nesquena-hermes
7556ea0e04 docs: v0.49.0 final — test count 697, add #287 and #289 entries
All three PRs now merged:
- #285: first-run onboarding wizard
- #287: self-update git pull diagnostics
- #289: skip flaky redaction test in agent-less envs

Final test count: 697 (up from 679)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 00:23:06 -07:00
nesquena-hermes
92fbf2a793 test: skip flaky redaction test in agent-less environments (#289)
This test depends on session state that varies with test ordering.
It passes when run in isolation or with the full hermes agent, but
fails intermittently in the standard test suite. Add to the
auto-skip list alongside other agent-dependent tests.

Fixes #289

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-12 00:19:36 -07:00
nesquena-hermes
0d98116b37 fix: improve self-update git pull diagnostics (#287)
Rebased and enhanced version of PR #287 by @ccqqlo:

- _run_git() now returns stderr on failure instead of empty string,
  so the UI can surface actionable git error messages
- Added _split_remote_ref() to split tracking refs like origin/master
  into separate remote + branch args for git pull
- Ignore untracked files in stash decision (--untracked-files=no) to
  prevent misleading stash-pop failures
- Fail early with clear message on unresolved merge conflicts
- 4 unit tests covering stderr, stdout fallback, exit code, and ref splitting

Based on work by @ccqqlo in PR #287.

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:19:33 -07:00
nesquena-hermes
31a721417e feat(onboarding): add one-shot bootstrap and first-run setup wizard (#285)
Adds a bootstrap launcher and a blocking first-run onboarding wizard that guides
new users through minimum Hermes setup from the browser UI.

Supported provider flows: OpenRouter, Anthropic, OpenAI, custom OpenAI-compatible.
OAuth/terminal-first flows remain via 'hermes model'.

Security hardening applied during review:
- /api/onboarding/setup restricted to loopback when auth disabled
- Newline injection guard in _write_env_file
- esc() on setup.unsupported_note in onboarding.js
- Test isolation fix (send_key instead of bot_name in contamination test)
- Skip markers for PyYAML-dependent tests in agent-less environments

Tests: 693 passed (up from 679)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: gabogabucho <gabogabucho@gmail.com>
2026-04-12 00:11:41 -07:00
nesquena-hermes
f9663d2f1d docs: bump to v0.49.0 — onboarding wizard release
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 23:41:10 -07:00
nesquena-hermes
cbc3c01604 docs: v0.48.2 release notes — provider mismatch warning
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 21:26:34 -07:00
nesquena-hermes
42dd2b562d fix: warn on provider/model mismatch, surface auth errors (#266)
* fix: warn on provider/model mismatch, surface auth errors (#266)

Fixes #266 — WebUI silently ignores provider/model selection mismatch.

The problem: selecting an OpenRouter (or Anthropic/OpenAI) model while
Hermes is configured for a different provider (e.g. local Ollama) sends
the request to the wrong endpoint, which returns a 401 Unauthorized error
with no UI indication of why.

Three-layer fix:

1. api/streaming.py — detect 401/auth errors explicitly
   Added is_auth_error detection covering '401', 'AuthenticationError',
   'authentication', 'unauthorized', 'invalid api key', and the specific
   Ollama error string 'no cookie auth credentials'. Auth errors emit
   apperror with type='auth_mismatch' and a hint pointing to 'hermes model'.

2. static/ui.js — expose active_provider and warn on selection
   - populateModelDropdown() stores data.active_provider from /api/models
     as window._activeProvider (the field was already in the response but
     the frontend never used it)
   - New _checkProviderMismatch(modelId) helper: compares the selected
     model's slash-prefix (e.g. 'openai/' from 'openai/gpt-4o') against
     the active provider. Skips the check for 'openrouter' and 'custom'
     to avoid false positives on configs that legitimately route any model.

3. static/boot.js — warn on model dropdown change
   modelSelect.onchange calls _checkProviderMismatch() and shows a toast
   when the selected model looks incompatible with the configured provider.

4. static/messages.js — distinct UI label for auth errors
   apperror handler now distinguishes type='auth_mismatch' and shows
   'Provider mismatch' as the error label instead of 'Error'.

5. static/i18n.js — provider_mismatch_warning and provider_mismatch_label
   keys added to all 5 locales (en, es, de, zh-Hans, zh-Hant).

Tests: 21 new tests in tests/test_provider_mismatch.py covering all
five change areas. 679/679 total pass (658 baseline + 21 new).

* fix: t() call args spread + use i18n label for auth mismatch

1. ui.js: _checkProviderMismatch passed [modelId, ap] as a single
   array arg to t(). Since t(key, ...args) spreads, the function
   received the array as m and undefined as p. Fixed to pass as
   separate args: t('provider_mismatch_warning', modelId, ap).

2. messages.js: 'Provider mismatch' label was hardcoded instead of
   using t('provider_mismatch_label'). Now uses the i18n key with
   fallback for when t() isn't available.

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

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:25:18 -07:00
nesquena-hermes
6b4ff53315 docs: v0.48.1 release notes — table inline formatting
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 21:03:51 -07:00
nesquena-hermes
ce84d1bafa fix: render inline formatting in markdown table cells (#273)
Table cells used esc() which escaped all HTML including <strong>,
<em>, <code> tags. Changed to inlineMd() which processes markdown
bold/italic/code/links and allows safe HTML tags through.

This runs after the pre-pass that converts <strong> to ** and
<em> to *, so both HTML tags and markdown syntax in table cells
are rendered correctly.

Fixes #273

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:03:01 -07:00
nesquena-hermes
afa540a222 docs: v0.48.0 release notes — gateway session sync
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 20:54:21 -07:00
nesquena-hermes
711bb5a6c9 feat: real-time gateway session sync (Phase 1) (#274)
* feat: add real-time gateway session sync (Phase 1)

- Add gateway_watcher.py: background daemon polling state.db every 5s
  for gateway session changes (telegram, discord, slack, etc.)
- Extend get_cli_sessions() to include all non-webui sources
- Add SSE endpoint /api/sessions/gateway/stream for real-time push
- Add dynamic source badges (telegram=blue, discord=purple, slack=dark purple)
- Rename 'Show CLI sessions' to 'Show agent sessions'
- Wire watcher lifecycle into server start/stop
- 10 tests covering metadata, filtering, SSE, and watcher lifecycle
- Activated via the same checkbox as CLI session import

Addresses GitHub issue #272

* fix: SSE event name mismatch, TLS attribute, remove PLAN.md

- Fix critical SSE bug: frontend listened for 'gateway_session_update'
  but backend sends 'sessions_changed' -- events were silently dropped
- Fix frontend field check: data.changed -> data.sessions (matches
  the actual payload structure from gateway_watcher)
- Fix TLS: ssl.TLSv1_2 -> ssl.TLSVersion.TLSv1_2 (the bare attribute
  does not exist, would crash TLS setup and silently fall back to HTTP)
- Remove PLAN.md: implementation plan should not be committed to repo

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

* fix: test isolation and slow-consumer sentinel in gateway sync

tests/test_gateway_sync.py:
- Fix _get_test_state_dir() path mismatch: the function was computing
  HERMES_HOME/webui-mvp-test but conftest.py sets HERMES_HOME=TEST_STATE_DIR,
  so state.db was written to a double-nested path the server never read.
  Now uses HERMES_WEBUI_STATE_DIR first (which conftest sets directly to
  TEST_STATE_DIR), fixing the 7/10 test failures in full-suite ordering.
- Fix conn cleanup: removed conn.close() from inside try blocks so the
  connection stays valid for _remove_test_sessions() in the finally block.
  Previously the closed conn caused ProgrammingError in finally (swallowed
  by bare except), leaving ghost sessions in state.db on test failure.

api/gateway_watcher.py:
- Fix slow-consumer queue eviction: when a subscriber queue fills (>10 events)
  and is removed from _subscribers, now puts a None sentinel into it so the
  SSE handler unblocks and closes the connection, letting EventSource
  auto-reconnect. Without this the connection stayed open but received no
  further events.

* fix: test isolation — set HERMES_WEBUI_TEST_STATE_DIR in conftest

The gateway sync tests write directly to state.db and must use the same
path the test server reads from.  Previously they computed the path
independently, which broke when test_auth_sessions.py set a different
HERMES_WEBUI_STATE_DIR in the test-process environment at import time.

tests/conftest.py:
- Set HERMES_WEBUI_TEST_STATE_DIR=TEST_STATE_DIR in the test process's
  os.environ (via setdefault) so gateway tests can read it reliably.
  Using setdefault preserves any explicit override the caller may pass.

tests/test_gateway_sync.py:
- Simplify _get_test_state_dir(): check HERMES_WEBUI_TEST_STATE_DIR first
  (now reliably set by conftest), fall back to HERMES_HOME/webui-mvp-test.
  Remove the workaround that tried to snapshot HERMES_HOME at import time.

Result: 658/658 tests pass in full-suite ordering (was 651 pass / 7 fail).

---------

Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 20:53:12 -07:00
nesquena-hermes
c677893105 docs: v0.47.1 release notes — Spanish locale
- CHANGELOG: v0.47.1 entry for Spanish locale (PR #275)
- ROADMAP: header updated v0.47.0 → v0.47.1, 645 → 648 tests; sprint row added
- TESTING.md: test count 645 → 648
- static/index.html: version v0.47.0 → v0.47.1

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 20:08:34 -07:00
nesquena-hermes
eca6f5efbd feat(i18n): add Spanish locale for WebUI (#275)
* feat(i18n): add Spanish locale for WebUI

* fix(i18n): translate tab_skills to Habilidades in Spanish locale

tab_skills was left as 'Skills' (English) in the es block — the only
sidebar tab that wasn't translated. Changed to 'Habilidades', the correct
Spanish term for Skills.

Also added tab_skills and tab_memory to the representative translation
assertions in test_spanish_locale.py to lock this in for future changes.

---------

Co-authored-by: gabogabucho <gabogabucho@gmail.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 20:06:37 -07:00
nesquena-hermes
068836cf6b fix: add aria-label to mobile workspace panel close button
The × button added for the mobile workspace panel close in v0.47.0
had a title= attribute but no aria-label. Screen readers may announce
the raw × character ('times' or 'multiplication sign') instead of
reading the title. Added aria-label='Close workspace panel' to match
the accessibility pattern used by other icon buttons in the panel header.

All 645 tests pass.

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 16:18:46 -07:00
nesquena-hermes
09325f1bdf docs: rewrite HERMES.md with accurate 2026 market comparisons
* docs: rewrite HERMES.md with accurate 2026 market comparisons

* fix: correct /loop and scheduling claims for Claude Code

Three factual errors corrected:

1. /loop is a native bundled skill available without any plugin. The doc
   incorrectly described it as behavior from the ralph-wiggum plugin.
   ralph-wiggum provides /ralph-loop, which is distinct: it iterates toward
   a completion goal. /loop polls on a fixed schedule. Both exist and serve
   different purposes.

2. claude.ai/code/scheduled is not a real usable URL or scheduling interface.
   Removed the reference. Cloud scheduling is described as cloud-managed cron
   with a 1-hour minimum interval.

3. 'your data leaves your hardware' was only half-true. Desktop scheduled tasks
   run locally with full file access. Cloud tasks do leave your hardware. Rewrote
   to be precise: the real distinction vs Hermes cron is that neither option runs
   as a headless server daemon.

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 15:45:38 -07:00
nesquena-hermes
1003fa410c docs: add CSS icon hotfix note to v0.47.0 CHANGELOG
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
2026-04-11 12:39:12 -07:00