Echo Studio (integrated from #457 in cycle 2b) was always-visible in the
chat sidebar and mobile hamburger nav. It's a scaffold/prototype, so surface
it only when opted in.
- add experimentalEchoStudio: false to StudioSettings + defaults
- new Settings > Labs (experimental) section with a Switch
- chat-sidebar + mobile-hamburger filter the Echo Studio nav item by the flag
/api/models now reads provider base_url + api_key entries from config.yaml and
fetches their /v1/models (60s cache, 3s timeout, server-side keys only), merging
them into the picker so configured upstream proxies restore dynamic discovery.
fetchSessions now sends accept: application/json, verifies the content-type is
JSON before parsing, and validates the response shape. When an auth/proxy layer
intercepts /api/sessions and returns HTML, the user gets a clear error instead
of a React crash from JSON.parse on '<!doctype html>'.
When React throws 'Failed to execute insertBefore/removeChild ... not a child
of this node' (stale DOM/runtime mismatch on navigation), the ErrorBoundary now
clears service-worker + cache-storage and reloads once (30s TTL guard to avoid
reload loops) instead of leaving the user on a dead error screen.
The Workspace model picker only saw entries from ~/.hermes/models.json,
the gateway /v1/models endpoint, and local provider discovery (Ollama,
Atomic Chat). Hermes Agent's actual catalog lives in ~/.hermes/config.yaml
under providers.<id>.models, providers.<id>.model, and model_aliases.
In setups where /v1/models intentionally returns only 'hermes-agent',
this meant the picker showed maybe 4 models out of the ~60 the user
had configured.
Add readClaudeConfigCatalog() to /api/models that walks providers.*.models
(strings or {id,name,provider} objects), each provider's default model,
and model_aliases (mapped to {id: alias, target: '<provider>/<model>'}).
Merge those entries via mergeModelEntries() so existing dedup and ordering
behavior is preserved, and append '+config.yaml' to the source label so
the UI/debug can tell where models came from.
The chat viewport previously only released stick-to-bottom when the
user scrolled up AND was already more than 200px from the bottom.
While reading near the end of a streaming response, any upward scroll
inside the bottom 200px did nothing — the ResizeObserver then yanked
the viewport back to the bottom on the next streaming chunk, producing
the 'can't scroll up' tug-of-war reported in #552.
Fix: any user-initiated upward scroll releases stick-to-bottom
immediately. Re-stick only when the user has stopped scrolling up
AND is at the bottom (<=NEAR_BOTTOM_THRESHOLD). Applied symmetrically
in ChatContainerRoot (handleScroll) and in chat-message-list's
handleUserScroll mirror.
The model-config provider dropdown in the Providers settings screen
(also shown in the in-chat Providers dialog) only offered Custom,
OpenRouter, Anthropic, and OpenAI. Despite docker-compose exposing
GOOGLE_API_KEY, users could not select Google/Gemini as a provider
without hand-editing config. Add 'google' to ModelProviderOption and
MODEL_PROVIDER_OPTIONS (label 'Google (Gemini)'), and register 'google'
as a known provider prefix so google/* model ids strip correctly.