Commit Graph

236 Commits

Author SHA1 Message Date
nesquena-hermes a201401236 Stage 388: PR #2524 2026-05-20 00:17:48 +00:00
Eleanor Berger 4598adfd04 feat: add Geist Contrast skin 2026-05-20 00:09:06 +00:00
AJV20 54b6c38578 feat(health): expose WebUI stream runtime diagnostics 2026-05-19 22:48:10 +00:00
nesquena-hermes 54875f2110 Stage 385: PR #2550 2026-05-19 03:13:47 +00:00
Michael Lam 1827ea3efd fix: add Grok OAuth provider catalog support 2026-05-18 19:51:01 -07:00
Michael Lam 6917b9a0e7 fix: sanitize custom provider env hints 2026-05-18 15:18:20 -07:00
keyos b2e1bac149 fix(config): keep anonymous custom endpoints in picker when /v1/models probe fails
When an anonymous custom endpoint (bare base_url, not a named custom_providers[] entry) fails its /v1/models probe, the provider group was silently dropped from the model picker entirely. This made the endpoint unusable even when /v1/chat/completions would work fine.

The fix adds an elif branch: if pid == 'custom', a cfg_base_url is configured, but no models were returned by the probe, the group is still added with an empty model list. Users can then select the Custom group and type a model ID manually in the picker's free-form input.

Closes #2542.
2026-05-18 19:28:46 +00:00
nesquena-hermes c7badae039 Stage 383: PR #2515
# Conflicts:
#	CHANGELOG.md
2026-05-18 16:44:35 +00:00
Michael Lam 037652308d fix: load remote models for named custom providers 2026-05-18 01:08:09 -07:00
junjunjunbong 3a53592107 Add previous messaging session controls 2026-05-17 21:27:32 -07:00
nesquena-hermes b861422045 Stage 379: PR #2473 2026-05-17 23:35:18 +00:00
nesquena-hermes 935d9e6402 Stage 379: PR #2461
# Conflicts:
#	CHANGELOG.md
2026-05-17 23:35:18 +00:00
ts2111 64db8bd794 fix: support /model alias switch for cross-provider custom models
Backend (api/config.py):
- resolve_model_provider(): check custom_providers for prefix match
  BEFORE the config_base_url branch. Previously, providers with a
  base_url set (e.g. deepseek) would catch all slash-delimited model
  ids and return the config provider, preventing custom provider
  routing.
- get_available_models(): include model aliases in response so the
  frontend can resolve them on /model commands.

Frontend (static/commands.js):
- cmdModel(): resolve aliases by fetching /api/models before fuzzy
  matching the dropdown.
- Add bare-model fallback when the alias resolves to a slash-delimited
  provider/model id (e.g. "deepseek/deepseek-v4-flash").
- Add cross-provider fallback: when the model is from a custom provider
  not in the active provider dropdown, call /api/session/update directly
  with the provider/model id and provider override.
2026-05-17 21:22:06 +02:00
nesquena-hermes a2920c99bc Stage 376: PR #2466
# Conflicts:
#	CHANGELOG.md
2026-05-17 16:42:11 +00:00
Frank Song 7a53fd4542 Clarify compact activity timeline semantics 2026-05-17 23:03:56 +08:00
starship-s aecad0f427 [verified] Fix WebUI memory session lifecycle commits 2026-05-17 03:30:06 -06:00
Michael Lam cdbb785037 fix: invalidate model cache on catalog changes 2026-05-16 22:24:12 -07:00
nesquena-hermes e9c6b7f06c Stage 375: PR #2432 — feat(theme): add Catppuccin appearance skin (Latte + Mocha palettes) by @Michaelyklam (closes #2426)
Co-authored-by: Michael Lam <michael@example.local>
2026-05-17 03:35:19 +00:00
nesquena-hermes 54f1a2acae Stage 373: PR #2415 — fix: ignore provider config flags in model picker by @Michaelyklam (fixes #2399) 2026-05-17 00:21:50 +00:00
nesquena-hermes 3480e75e13 Stage 372: PR #2413 — feat(quota-chip): add Settings toggle, flip default to off 2026-05-16 23:05:09 +00:00
nesquena-hermes a4ab7d4d27 Stage 371: PR #2409 — Stuck-PR sweep: salvage RTL chat from #1721 + override quota chip from #2082 by @malulian and @ai-ag2026
Co-authored-by: malulian <malulian@users.noreply.github.com>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-05-16 22:04:56 +00:00
BonyFish f82a763dfb fix: support list format for custom_providers.models in model dropdown
The get_available_models() function only handled dict-format models
(`{model_id: {}}`) for custom_providers entries, silently dropping
models specified as YAML lists (`[model1, model2]`) or list of dicts
(`[{id: ..., label: ...}]`).

This caused users who define their custom providers with list-format
model declarations to see zero or incomplete model entries in both
Settings → Preferences → Default Model dropdown and the chat
interface model picker.

The fix adds an `elif isinstance(_cp_models_dict, list)` branch with
support for three list sub-formats:
  - Plain string list: `models: [m1, m2]`
  - Dict list: `models: [{id: m1, label: ...}]`
  - Mixed: `models: [m1, {id: m2}]`

Refs: hermes-agent issue where YAML list models were invisible
2026-05-16 05:43:09 +00:00
Hermes Agent b293bf8bc5 stage-364: Opus-caught live SSE event_id fix (side-channel approach)
Replace the earlier frontend-reset approach with a backend side-channel
approach that preserves the queue (event, data) tuple shape.

Problem (Opus catch):
- Live SSE frames emitted by _sse() in api/streaming.py:2296 carried no
  'id:' field. Only journal-replay frames (via _sse_with_id) emitted IDs.
- Frontend's _lastRunJournalSeq cursor stayed at 0 during live streaming.
- Mid-stream error → reconnect-to-replay arrived with after_seq=0.
- Server replayed every journaled event from seq 1.
- assistantText (closure-scoped) had accumulated all live tokens already
  → double-rendered output.

Fix:
- api/config.py: STREAM_LAST_EVENT_ID: dict = {} module-level dict.
- api/streaming.py put(): capture journal event_id, write to
  STREAM_LAST_EVENT_ID[stream_id]. Keep queue tuple as (event, data).
- api/routes.py _handle_sse_stream: read STREAM_LAST_EVENT_ID[stream_id]
  at emit time, use _sse_with_id when set.
- api/streaming.py finally block: pop STREAM_LAST_EVENT_ID for cleanup.

Why side-channel instead of 3-tuple:
- Earlier attempt (queue tuple → (event, data, event_id)) broke 4 existing
  tests: test_cancel_interrupt, test_sprint42, test_sprint51,
  test_issue1857_usage_overwrite. These all unpack 'event, data = q.get()'.
- Frontend-reset approach (reset assistantText before replay) broke 3
  other tests: test_smooth_text_fade, test_streaming_markdown,
  test_streaming_race_fix. _wireSSE must NOT reset accumulators because
  legacy reconnect doesn't replay events; only journal-replay does.

Side-channel preserves both invariants:
- Queue contract stays (event, data) — legacy consumers unbroken.
- Frontend accumulators stay alive on _wireSSE — legacy reconnect unbroken.
- Live SSE emits 'id:' so the journal cursor advances correctly.

6 regression tests added in test_stage364_opus_live_sse_event_id.py.
1 existing test (test_run_journal_streaming_static.test_streaming_journals_sse_events_before_queue_delivery) updated to be tuple-shape-agnostic.

Test results:
- Full pytest: 5713 passed, 10 skipped, 1 xfailed, 2 xpassed, 0 failed
- Previously-failing 5 tests: ALL PASS
- 6 new regression tests: ALL PASS
2026-05-16 03:58:54 +00:00
Hermes Agent 5ab2ebed2e Merge pull request #2322 into stage-362
fix: route endpoint-discovered Ollama models correctly (Michaelyklam)
2026-05-15 22:55:30 +00:00
Michael Lam 2fdc1d99e2 fix: expand legacy Hermes CLI toolset alias 2026-05-15 13:08:22 -07:00
Michael Lam 512c401e8a fix: route endpoint-discovered Ollama models correctly 2026-05-15 12:16:23 -07:00
Hermes Agent ad76db8651 Merge pull request #2291 into stage-359
feat: add Nous Research skin (linuxid10t)
2026-05-15 14:55:10 +00:00
linuxid10t b2d4f13c5b feat: add Nous Research skin
Adds a cold steel-blue/monospace skin inspired by nousresearch.com:
- Steel-blue accent (#4682B4) replacing warm gold
- Monospace typography (SF Mono, Roboto Mono, Courier New)
- Sharp corners, technical dashed borders
- Dark navy palette (#0A0E14) for dark mode

Files changed:
- static/style.css — Nous skin CSS variables and component overrides
- static/boot.js — Nous skin entry in _SKINS array
- static/index.html — nous in inline skin validation list
- api/config.py — nous + sienna in server-side _SETTINGS_SKIN_VALUES
2026-05-15 00:28:34 -05:00
Yao Ning b1bf800fa4 feat: make upload size limit runtime-configurable
Signed-off-by: Yao Ning <zay11022@gmail.com>
2026-05-15 11:39:23 +08:00
Hermes Agent ec689e32be Merge pull request #2099 into stage-358
feat: add opt-in streaming text fade (dobby-d-elf, off-by-default)
2026-05-14 21:27:52 +00:00
Hermes Agent 612480ce56 Merge pull request #2165 into stage-358
feat(providers): show pooled Codex quota status (starship-s, post-review follow-up)
2026-05-14 21:27:51 +00:00
Michael Lam d246bf2654 fix: canonicalize configured provider model lookup 2026-05-14 09:05:13 -07:00
Frank Song e2f319d730 Add extra large font size option 2026-05-14 11:09:21 +08:00
Jordan SkyLF bec21eafa0 Add What's New summary toggle 2026-05-13 15:53:01 -07:00
Hermes Agent ca82f60144 Merge pull request #2191 into stage-350
fix(auth) 1/3: thread-safe login rate limiter + PBKDF2 key separation + transparent migration (lucasrc)
2026-05-13 20:41:36 +00:00
Michael Lam 1e17760a04 Fix opencode-go provider overlap routing
Closes #1894
2026-05-13 12:13:37 -07:00
Lucas Coutinho 2bcf411519 fix(auth): invalidate password hash cache in save_settings() on password change 2026-05-13 14:08:37 -03:00
Hermes Agent 8060b2ba3a Merge pull request #2179 into stage-347
fix(config): preserve nvidia/ prefix on NVIDIA NIM (closes #2177)

Self-built. nesquena APPROVED with extensive end-to-end trace including
cross-tool agent CLI verification and 12-shape behavioural harness.
2026-05-13 07:33:45 +00:00
nesquena-hermes 9b1d786459 fix(config): preserve nvidia/ prefix on NVIDIA NIM (closes #2177)
Move the `_PORTAL_PROVIDERS` guard in `resolve_model_provider()` to run
BEFORE the `prefix == config_provider` strip branch. The guard was added
for NVIDIA (along with the Nous portal cases in #854 / #894) but was
placed after the strip, so it never fired when `config_provider == "nvidia"`
and the model id started with `nvidia/`.

For `model_id="nvidia/nemotron-3-super-120b-a12b"`,
`config_provider="nvidia"`:
  - prefix = "nvidia", bare = "nemotron-3-super-120b-a12b"
  - prefix == config_provider → True → strip branch returned bare name
  - `_PORTAL_PROVIDERS` guard never reached
  - bare "nemotron-3-super-120b-a12b" sent to NVIDIA NIM → HTTP 404

NIM requires the full namespaced path. The fix moves the portal guard
to run first, so all portal providers (Nous, OpenCode-Zen, OpenCode-Go,
NVIDIA NIM) always preserve the full `provider/model` id regardless of
whether the prefix happens to equal the provider name.

This also closes a latent symmetric bug for the Nous case if a
`nous/<model>` id ever existed in the catalog.

Test plan:
- New `tests/test_issue2177_nvidia_prefix_preservation.py` covers:
  - nvidia/nemotron-... under nvidia (the reported case)
  - cross-namespace qwen/ and meta/ under nvidia (regression pin)
  - every static nvidia model in `_PROVIDER_MODELS` resolves to itself
  - latent nous/<model> under nous (structural ordering pin)
  - non-portal providers (anthropic) still strip — fix doesn't over-correct
- Existing portal-routing suites (test_nous_portal_routing.py,
  test_issue895_894_nous_prefix.py) continue to pass.
- Full test suite: 5320 passed, 4 skipped, 3 xpassed.

Reported on Discord by @vishnu (Nathan forwarded as #2177).
2026-05-13 07:05:57 +00:00
MrFant a4417d11f9 fix: handle dict model entries in provider models list
When a provider's 'models' config contains dicts (e.g. {"id": "x", "label": "y"})
instead of plain strings, _apply_provider_prefix() crashes with:
  AttributeError: 'dict' object has no attribute 'startswith'

This happens because the list comprehension at line 3505 passes the raw dict
as the model ID. The fix extracts 'id' and 'label' from dict entries while
keeping string entries as-is.

Fixes the /api/models and /api/onboarding/status 500 errors.
2026-05-13 13:49:40 +08:00
starship-s c562ce2e8c fix(providers): preserve quota cache on refresh failure 2026-05-12 21:16:34 -06:00
starship-s a166625e02 fix(providers): refresh pooled Codex quota state 2026-05-12 21:00:24 -06:00
dobby-d-elf 8727d145fa Merge branch 'master' into smooth-text-fade 2026-05-11 20:35:40 -06:00
dobby-d-elf 67e29fa991 feat: add opt-in streaming text fade 2026-05-11 13:13:26 -06:00
Frank Song f6115b78c6 Fix custom provider name slugs with ports 2026-05-11 17:24:53 +08:00
nesquena-hermes 23cfc99738 fix(config): split hermes_cli and urlopen fallback in lmstudio branch (CI fix)
CI on Python 3.13 (clean editable install, no hermes_cli package) was still
failing the 3 lmstudio tests after the first fix attempt. Root cause: the
outer try/except in the lmstudio branch was catching ImportError from
`from hermes_cli.models import provider_model_ids`, hijacking the whole
branch and silently skipping the urlopen fallback.

Restructured into two independent tiers:
  1. hermes_cli lookup in its own try/except — ImportError logs at DEBUG
     and continues with lm_ids=[].
  2. urlopen fallback runs unconditionally when lm_ids is empty, including
     after hermes_cli import failure.

New regression test `test_lmstudio_fallback_works_when_hermes_cli_unavailable`
explicitly blocks hermes_cli via sys.meta_path and verifies the lmstudio
group still populates from the urlopen fallback. Without this test, the
CI-vs-local divergence (local env had hermes_cli installed, CI didn't)
would keep slipping through.

All 12 lmstudio-related tests pass, including the 3 #1527 tests that
broke on stage-337.
2026-05-11 06:06:58 +00:00
nesquena-hermes 2ca220eec0 fix(config): PR #1970 lmstudio branch must honor cfg.model.base_url fallback
PR #1970 added a dedicated `elif pid == "lmstudio":` branch in
`get_available_models()` that fetches the live /v1/models list when the
hermes_cli helper doesn't have ids cached. The fallback path inside that
branch only looked at `cfg["providers"]["lmstudio"]["base_url"]`, missing
the historical config shape where the URL lives under `cfg["model"]`:

  model:
    provider: lmstudio
    base_url: http://192.168.1.22:1234/v1   ← here, not under providers.lmstudio
  providers:
    lmstudio:
      api_key: local-key

3 pre-existing tests in tests/test_issue1527_lmstudio_base_url_classification
broke on stage-337 because of this — they passed on master, failed after
the PR #1970 merge.

The simpler fix is to enhance the already-introduced `_get_provider_base_url()`
helper so it falls back to `cfg["model"]["base_url"]` when
`cfg["model"]["provider"] == provider_id`, then use the helper inside the
lmstudio branch instead of a direct lookup. This keeps the previous
behaviour (where the generic configured-provider branch handled lmstudio
via the model block) while preserving PR #1970's live-discovery additions.

Belt-and-suspenders: `_get_provider_base_url()` explicitly does NOT inherit
model.base_url for providers other than the active one — if a user's config
says `model.provider: anthropic` and they have `providers.openai` configured
without a base_url, openai must still resolve to None (use SDK default),
not to the anthropic proxy URL.

6 new regression tests in tests/test_pr1970_lmstudio_base_url_fallback.py
lock the two-location lookup, the precedence rule (explicit providers entry
wins over model fallback), trailing-slash stripping, and the negative case
(model.base_url MUST NOT leak to non-active providers).

All 51 tests in the existing model-resolver + custom-provider banks still
pass.

Caught by maintainer review on stage-337 (full pytest with the new network
isolation in place surfaced the regression that the fork-CI mock-server path
would have hidden).
2026-05-11 05:59:59 +00:00
nesquena-hermes e0ecf2a035 Merge PR #1970: feat: LM Studio provider with live model discovery 2026-05-11 05:12:04 +00:00
nesquena-hermes 97b283c5a4 Merge PR #2039 into stage-335 2026-05-11 00:25:07 +00:00
ai-ag2026 2ead7daa2f fix: expose active run lifecycle in health 2026-05-11 02:15:00 +02:00