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.
This commit is contained in:
Nathan Esquenazi
2026-04-12 14:45:39 -07:00
committed by GitHub
parent 2fc19a8326
commit 39d42be396
4 changed files with 33 additions and 25 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -526,7 +526,7 @@
<div class="settings-section-title">System</div>
<div class="settings-section-meta">Instance version and access controls.</div>
</div>
<span class="settings-version-badge">v0.50.7</span>
<span class="settings-version-badge">v0.50.8</span>
</div>
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
<label for="settingsPassword" data-i18n="settings_label_password">Access Password</label>