From 39d42be396e9b9e689f6d148ce639a2df6190516 Mon Sep 17 00:00:00 2001 From: Nathan Esquenazi Date: Sun, 12 Apr 2026 14:45:39 -0700 Subject: [PATCH] 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. --- CHANGELOG.md | 5 +++++ README.md | 44 +++++++++++++++++++++++--------------------- api/config.py | 7 ++++--- static/index.html | 2 +- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 443913c..cfc6768 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ --- +## [v0.50.8] Model dropdown deduplication — hyphen vs dot separator fix (PR #332) + +- **Model dropdown no longer shows duplicates for hyphen-format configs** (e.g. `claude-sonnet-4-6` from hermes-agent config): The server-side normalization in `api/config.py` now unifies hyphens and dots when checking whether the default model is already in the dropdown. Previously, `claude-sonnet-4-6` (hermes-agent format) and `claude-sonnet-4.6` (WebUI list format) were treated as different models, causing the same model to appear twice — once as a raw unlabelled entry and once with the correct display name. The raw entry is now suppressed and the labelled one is selected as default. +- **README updated**: test count corrected to 791 / 51 files; all module line counts updated to current values; `onboarding.py`, `state_sync.py`, `updates.py` added to the architecture listing. + ## [v0.50.7] OAuth provider onboarding path — Codex/Copilot no longer blocks setup (PR #331, fixes #329 bug 2) - **OAuth providers now have a proper onboarding path** (closes bug 2): Users with `openai-codex`, `copilot`, `qwen-oauth`, or any other OAuth-authenticated provider now see a clear confirmation card instead of an unusable API key input form. diff --git a/README.md b/README.md index 66d2ed4..c63738c 100644 --- a/README.md +++ b/README.md @@ -339,8 +339,8 @@ Or using the agent venv explicitly: ``` 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: **433 tests** -across 23 test files. +Production data and real cron jobs are never touched. Current count: **791 tests** +across 51 test files. --- @@ -462,31 +462,33 @@ across 23 test files. ## Architecture ``` -server.py HTTP routing shell + auth middleware (~83 lines) +server.py HTTP routing shell + auth middleware (~154 lines) api/ - auth.py Optional password authentication, signed cookies (~149 lines) - config.py Discovery, globals, model detection, reloadable config (~726 lines) - helpers.py HTTP helpers, security headers (~71 lines) - models.py Session model + CRUD + CLI bridge (~338 lines) - profiles.py Profile state management, hermes_cli wrapper (~366 lines) - routes.py All GET + POST route handlers (~1314 lines) - streaming.py SSE engine, run_agent, cancel support (~332 lines) - upload.py Multipart parser, file upload handler (~78 lines) + 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 (~1996 lines) + state_sync.py /insights sync — message_count to state.db (~113 lines) + streaming.py SSE engine, run_agent, cancel support (~545 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) static/ index.html HTML template (~600 lines) - style.css All CSS incl. mobile responsive, themes (~855 lines) - ui.js DOM helpers, renderMd, tool cards, context ring (~1090 lines) - workspace.js File preview, file ops, git badge (~247 lines) - sessions.js Session CRUD, ⋯ dropdown, collapsible groups, search (~600 lines) - messages.js send(), SSE handlers, rAF throttle (~352 lines) - panels.js Cron, skills, memory, profiles, control center (~1200 lines) - commands.js Slash command autocomplete (~170 lines) - boot.js Mobile nav, workspace state machine, composer chips, boot IIFE (~420 lines) + style.css All CSS incl. mobile responsive, themes (~1050 lines) + ui.js DOM helpers, renderMd, tool cards, context indicator (~1496 lines) + workspace.js File preview, file ops, git badge (~286 lines) + sessions.js Session CRUD, collapsible groups, search (~752 lines) + messages.js send(), SSE handlers, rAF throttle (~487 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) tests/ conftest.py Isolated test server (port 8788) - test_sprint{1-36}.py 36 test files, 742 test functions - test_regressions.py Permanent regression gate + 51 test files 791 test functions 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 diff --git a/api/config.py b/api/config.py index d0de2b2..c20b699 100644 --- a/api/config.py +++ b/api/config.py @@ -913,10 +913,11 @@ def get_available_models() -> dict: # Ensure the user's configured default_model always appears in the dropdown. # It may be missing if the model isn't in any hardcoded list (e.g. openrouter/free, # a custom local model, or any model.default not in _FALLBACK_MODELS). - # Normalize before comparing: strip provider prefix so 'anthropic/claude-opus-4.6' - # matches 'claude-opus-4.6' already in the list and avoids a duplicate entry. + # Normalize before comparing: strip provider prefix and unify separators so + # 'anthropic/claude-opus-4.6' matches 'claude-opus-4.6' and 'claude-sonnet-4-6' + # matches 'claude-sonnet-4.6' (hermes-agent uses hyphens, webui uses dots). if default_model: - _norm = lambda mid: mid.split("/", 1)[-1] if "/" in mid else mid + _norm = lambda mid: (mid.split("/", 1)[-1] if "/" in mid else mid).replace("-", ".") all_ids_norm = {_norm(m["id"]) for g in groups for m in g.get("models", [])} if _norm(default_model) not in all_ids_norm: # Determine which group to inject into. Compare against the diff --git a/static/index.html b/static/index.html index b399d5a..51fae8a 100644 --- a/static/index.html +++ b/static/index.html @@ -526,7 +526,7 @@
System
Instance version and access controls.
- v0.50.7 + v0.50.8