Commit Graph
454 Commits
Author SHA1 Message Date
Frank Song 33859b2704 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 09:43:54 +08:00
sbe27 68d406f7ff fix(models): refresh OpenCode Go static catalog 2026-07-09 01:23:08 +00:00
Frank Song ff0e6347cb Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 08:22:26 +08:00
t 2cf43527a3 feat(updates): stable/experimental release channels
Add an update_channel setting (stable|experimental) that selects which git-tag
stream the self-updater tracks on the single linear master line:

  stable       -> 'v*'      promoted, soaked releases (default; unchanged glob)
  experimental -> 'exp-v*'  every release batch (opt-in testers)

A channel is only a tag glob — no branches, no divergence — so every ff-only
guarantee (#2653/#2846/#3140) is preserved. Channel governs the WebUI repo only;
the Agent repo keeps its historical branch fall-through.

Correctness (advisor-reviewed, Codex + Fable):
- describe uses --match <glob> so a commit tagged both v* and exp-v* resolves
  to the channel-correct tag
- stable never falls through to origin/master when HEAD contains the latest
  stable tag (the firehose-suppression that makes channels work)
- apply_force_update refuses to reset --hard onto an ancestor ref (rewind guard)
- update cache + in-progress guard keyed by (channel, include_agent)
- channel display badge is a SEPARATE field; WEBUI_VERSION stays channel-neutral
  (asset cache-busting / SW cache / stale-client skew all do exact-string equality)

Settings UX: channel dropdown with risk-forward copy + the ff-only asymmetry
helper ('switching back to Stable keeps your current version until Stable catches
up'), channel chip on the version badge, i18n keys, re-check on switch.

Tests: new tests/test_update_channels.py (12 real-git-fixture tests) + existing
update suite updated for the channel-aware signatures.
2026-07-08 19:25:27 +00:00
Frank Song 9b352e3acf Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 00:28:30 +08:00
Rod Boev f4172f73b0 fix(mcp): preserve config overrides for profile reads (#5619) 2026-07-08 16:18:52 +00:00
Frank Song 5764df66f3 fix(wakeup): share process pause lane resolution 2026-07-07 17:00:36 +08:00
Frank Song 1ab9c47e9b fix(wakeup): canonicalize paused route model lanes 2026-07-07 15:09:30 +08:00
t 2454719f2e gate 5696 + fold-in: non-throwing HERMES_DEBUG_SLOW parse (Codex CORE fix) 2026-07-06 20:15:42 +00:00
t 06590556f5 gate 5696 on current master 2026-07-06 20:05:17 +00:00
Konstantin M 60e1b5277d perf(webui): bound _last_message_timestamp to a tail window
Session.compact() called _last_message_timestamp(self.messages) which
reversed-walked all 2,730 messages on every /api/session response.
The messages array is chronologically ordered, so the last non-tool
message sits at the very end. Scan only the last 8 messages; fall
back to a full scan only if no timestamp is found in the window
(unusual sessions with >8 trailing tool rows).

Reverts to identical behavior when the window hits, since the most
recent user/assistant message's timestamp is what compact() emits.

Bench (single-shot, live Chromebook eMMC):
  before: get_session=1107ms compact=1725ms total=2880ms
  after:  get_session= 648ms compact= 288ms total=1313ms
  (-80% on compact, -54% on total)

Revert: git checkout master && systemctl --user restart hermes-webui
2026-07-06 16:53:01 +00:00
Konstantin M 1018732a26 perf(webui): Phase 0 instrumentation for session-load hot path
The slow-request journal showed /api/profiles, /api/models, and /api/session
fire on every session click but had no per-stage timing. Add:

- RequestDiagnostics coverage for GET /api/profiles, /api/models, /api/session
- Stage markers inside /api/profiles (list / active lookup / isolated check)
- Stage markers inside /api/models (freshness routing, serialize)
- Per-stage stage-log inside get_available_models_for_session_visit that
  emits a [SLOW] line when total wall time crosses HERMES_DEBUG_SLOW ms
  (default 500ms) so we can pinpoint which sub-step is blocking

No behavior change. Existing _t0.._t6 timing on /api/session and the
2.5s/10s TTL on /api/sessions are unchanged. Reversible: revert commit.
2026-07-06 14:23:34 +00:00
Rod Boev 29e9a27299 fix(#5619): preserve same-path cfg overrides on reload 2026-07-05 17:08:20 -04:00
Rod Boev 1124fd882a fix(#5619): reload model payloads on profile switch 2026-07-05 16:44:33 -04:00
Rod Boev 2f815f040c fix(#5619): clear stale cfg overrides on profile switch 2026-07-05 16:33:01 -04:00
Rod Boev 4c3a4e011e fix(#5619): reload config cache on profile switch 2026-07-05 16:22:35 -04:00
nesquena-hermes 14ad45bd1f fix(#5311): skip live /v1/models probe for opencode-go — use curated static list
OpenCode Go's /v1/models endpoint returns models from the full public catalog that
are NOT enabled on the Go tier; selecting any such probe-only model 404s
('model not found') when the chat request is sent. get_available_models now skips
the live probe for opencode-go (bare branch) so the next `if not raw_models` falls
through to the curated static _PROVIDER_MODELS['opencode-go'] list — the actual
Go-tier models. Narrow, provider-scoped; no other provider affected.

Contributor stage; gate-pass. Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-05 05:05:30 +00:00
nesquena-hermesandakay64 d27ef5d196 harden #5511: active provider owns its OWN providers: allowlist (complete ownership model)
Codex 5th CORE: the ownership set only covered static _PROVIDER_MODELS, not the
active provider's own config.yaml providers.<active>.models allowlist. So an
active provider defined purely via providers: (no static catalog) could be
hijacked by another slug's overlapping entry earlier in config order. Fix folds
the active provider's own providers: allowlist (list + dict shapes, copilot
excluded) into _provider_models_set — completing the ownership model so the
active provider owns its declared models from EITHER the static catalog OR its
own providers: entry. Regression test (fail-without-fix verified). 40 model_resolver
tests green.

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:28:44 +00:00
nesquena-hermesandakay64 f2604a22bd harden #5511: _canonicalise_provider_id accepts alias targets in _PROVIDER_MODELS (Gemini class)
Codex 4th CORE (root latent bug in the shared canonicalizer): _canonicalise_provider_id
only accepted an alias resolution if the target was in _PROVIDER_DISPLAY. But some
canonical targets (e.g. 'gemini') are indexed in _PROVIDER_MODELS and NOT
_PROVIDER_DISPLAY, so 'google-gemini'->'gemini' was rejected and returned raw
'google-gemini' — leaving the ownership guard empty and letting providers.openai.models
hijack an active Gemini-owned model. Fix mirrors the direct-hit check (line 1445):
accept alias targets in EITHER _PROVIDER_DISPLAY OR _PROVIDER_MODELS. This is the
root fix for the whole alias-canonicalization class (gemini/google-gemini/
google-ai-studio etc.), not just the reported site. Blast-radius verified: 81
canonicalise/provider-alias tests across 5 files still green; x-ai still stays x-ai,
opencode_go still folds. Gemini regression test added (fail-without-fix verified).

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:23:45 +00:00
nesquena-hermesandakay64 4399ff02e0 harden #5511: canonicalise active provider for ownership set (latent alias bug)
Codex 3rd CORE (pre-existing latent bug the providers: scan exposes): the
_provider_models_set ownership lookup used the RAW config_provider (e.g. 'z-ai')
but _PROVIDER_MODELS is keyed by canonical slug ('zai'), so an aliased active
provider got an empty ownership set -> _skip_custom_providers guard-2 silently
failed -> another providers.<slug>.models entry could hijack an active-owned
model (verified: active z-ai + glm-5 + providers.openai.models.glm-5 -> openai).
Fix: canonicalise config_provider before the _PROVIDER_MODELS lookup (and reuse
the canonical slug for the scan's ownership guard). Regression test with an
aliased active provider (verified fail-without-fix).

This is the root-cause fix for the alias class — the ownership guard now holds
for every aliased provider (zai/kilo/etc.), not just the reported site.

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:17:05 +00:00
nesquena-hermesandakay64 f8c9f9e08c harden #5511: providers: scan honors active-provider ownership guard
Codex 2nd CORE: the new providers: scan didn't honor the _skip_custom_providers
active/default ownership guard (config.py:2535) that the custom_providers scan
uses. So with an active provider (e.g. ai-gateway) + model.default=gpt-5 AND a
providers.openai.models.gpt-5 entry, the scan hijacked gpt-5 to openai instead of
the active ai-gateway path. Fix: when _skip_custom_providers is true, the
providers: scan only considers the active provider's own canonical slug (skip all
other slugs). Regression tests: active-provider-owns-default not hijacked (fails
without fix); active provider's OWN providers: entry still matches.

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:10:47 +00:00
nesquena-hermesandakay64 cca3b6c279 release #5511: exclude Copilot from providers: routing scan + regression tests + CHANGELOG
Gate-cert (nesquena-hermes) + Codex found 1 CORE: akay64's new providers: scan in
resolve_model_provider() iterated ALL providers.<slug>.models INCLUDING
providers.copilot.models — but that key is a documented per-model SETTINGS map
(reasoning_effort/limits), NOT a routing allowlist (config.py exception). So a
Copilot per-model settings entry hijacked that model's routing to Copilot
(Codex reproduced gpt-5 -> copilot). Fix (Codex-exact): skip copilot in the scan.

Added the regression tests the PR shipped 0 of: user-defined allowlist routes;
active-openai + providers.copilot.models (dict + list shapes) stays openai
(both verified to fail without the exclusion).

Co-authored-by: akay64 <akay64@users.noreply.github.com>
2026-07-04 07:06:05 +00:00
nesquena-hermes ec9f77c319 Merge commit '87404d76f036' into release/stage-5511 2026-07-04 06:57:54 +00:00
akay64 87404d76f0 fix: scan providers: in resolve_model_provider() for user-defined provider matching
Mirrors the existing custom_providers: scan immediately above. User-defined
providers declared under config.yaml's providers: section with explicit
models: lists were invisible to model-to-provider resolution — bare model
IDs fell through to the stale config_provider, routing through the wrong
endpoint.

The insertion site sits after custom_providers: (unchanged) and before
all heuristics (@provider:model, slash, fallback — all unchanged).
2026-07-04 08:59:21 +04:00
Rod Boev fdacb4b922 fix(#5435): stop startup workspace repair from persisting speech defaults 2026-07-02 21:28:46 -04:00
Rod Boev 8f7b9c1d0d fix(#5435): drop dead persisted-key state from config load 2026-07-02 19:38:35 -04:00
Rod Boev 3b344b1d9a fix(#5435): preserve local speech prefs until settings.json owns them 2026-07-02 19:33:13 -04:00
Rod Boev 91ae1dede9 fix(#5435): persist TTS and voice preferences server-side 2026-07-02 15:43:24 -04:00
nesquena-hermesandRod Boev b931ae2f9b fix(settings): reconcile #5145 rename+steer-flip onto master's #5170 mirror
Rebase PR #5162 (rename busy_input_mode -> default_message_mode; flip the
default from 'queue' to 'steer') onto current origin/master WITHOUT dropping
the shipped #5170 localStorage persistence mirror.

Rename the mirror machinery to the new setting name for consistency:
  _BUSY_INPUT_MODES        -> _DEFAULT_MESSAGE_MODES        (values unchanged)
  _normalizeBusyInputMode  -> _normalizeDefaultMessageMode  (fallback now 'steer')
  _persistBusyInputMode    -> _persistDefaultMessageMode
  _readPersistedBusyInputMode -> _readPersistedDefaultMessageMode
  window._busyInputMode    -> window._defaultMessageMode (+ renamed exports)

localStorage: write the new 'hermes-default-message-mode' key; read it with a
fallback to the legacy 'hermes-busy-input-mode' key so an existing user's
persisted preference survives the rename.

Preserve #5170 behavior at every mirror site under the new names:
  - boot success  -> window._defaultMessageMode=_persistDefaultMessageMode(...)
  - boot FAILURE  -> window._defaultMessageMode=_readPersistedDefaultMessageMode()
    (NOT a hardcoded 'steer' — a saved 'interrupt'/'queue' must still apply when
    the server is unreachable; do not regress #5167/#5132)
  - preferences autosave, settings-panel load, and _applySavedSettingsUi all
    persist through _persistDefaultMessageMode(...)

Tests updated for the rename while keeping the persistence-behavior assertions
(test_1062, test_5145, test_5167); test_5167 gains explicit guards that the
load-failure path reads the persisted pref and never hardcodes a literal mode,
plus autosave/panel-load mirror-write coverage.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-02 05:44:48 +00:00
nesquena-hermes 4bbb64bcc1 #5213: Claude Code sidebar visibility toggle (#4714), rebased on master 2026-07-02 01:40:19 +00:00
nesquena-hermes d43618beca stage #5301 2026-07-01 22:42:35 +00:00
nesquena-hermes 17b0998c0b stage #5313 2026-07-01 19:35:42 +00:00
nesquena-hermes 6caaced8eb fix(webui): bound in-memory SESSIONS cache with lazy reload (#4765)
Root cause of the silent-crash-after-hours cluster (#4765/#2233/#4633): the
global in-memory SESSIONS LRU evicted with a blind popitem(last=False), which
could drop an actively streaming or not-yet-persisted session (data loss) and
was capped only via an env var. On long-running installs the effective result
was unbounded RAM growth until segfault.

- Add _session_is_evictable(): a session is evictable ONLY when it is not
  streaming (no active_stream_id), has no in-flight turn (no pending_user_message
  / pending_started_at), and its full state is proven on disk (sidecar
  message_count >= in-memory count; metadata-only stubs and zero-message shells
  are trivially safe).
- Add _evict_sessions_over_cap(): replaces all 8 blind popitem loops across
  models.py, routes.py, streaming.py. Walks the LRU oldest-first and removes only
  provably-safe entries; never acquires LOCK/stream locks itself (caller holds
  LOCK) so no lock-ordering deadlock. May briefly exceed the cap rather than ever
  evict an active/unsaved session.
- Make the cap configurable via config.yaml webui.sessions_cache_max
  (get_sessions_cache_max()); precedence config.yaml -> HERMES_WEBUI_SESSIONS_MAX
  (legacy) -> DEFAULT_SESSIONS_CACHE_MAX=300. No new HERMES_* env var. Invalid or
  <1 values fall back so a typo can never disable the bound.
- Evicted sessions lazily reload from their JSON sidecar via the existing
  get_session() accessor; no call sites changed. _index.json sidebar behavior
  unchanged (the sidebar reads the index, not SESSIONS).
- Add tests/test_issue4765_sessions_lru_eviction.py (8 tests): eviction past
  cap, active/streaming never evicted, unsaved/stale-tail never evicted, lazy
  reload with identical content, and no-data-loss under heavy churn.
- README: document the config.yaml key + safety semantics.

Fixes #4765.
2026-07-01 18:14:39 +00:00
Charles Inglis f9de93f42e refactor: make nested-route reasoning deny boundary-based, not prefix-based
Structural hardening per user request, following the 3rd round of the
same bypass class on PR #5313's reasoning_efforts feature:

Round 1: plain-ordering regression (provider-config short-circuit ran
before the nested-route deny).
Round 2: a provider-qualified hint (@custom:<slug>:vertex/gemini-...)
left a slug fragment that the deny's prefix-match missed.

Both were legitimate, narrowly-targeted fixes, but the pattern —
_nested_route_reasoning_denied() requiring the model string to START
WITH the route prefix — meant every future wrapper/nesting scheme
would need the strip logic to be updated in lockstep, and a missed
case fails OPEN (reasoning re-enabled on a route that must never show
it), which is the wrong failure mode for a security-adjacent guard.

This commit removes that class of bug instead of patching its latest
instance: _nested_route_reasoning_denied() now searches for the
vertex/gemini- or gemini_cli/gemini- pattern ANYWHERE in the string at
a non-alphanumeric boundary, rather than requiring it at position 0.
Correctness no longer depends on _strip_provider_hint_for_reasoning()
having stripped exactly the right prefix first — any number of opaque
wrapper layers (@provider:, a named custom-provider slug, or any
nesting scheme not yet invented) can precede the route and the deny
still fires.

Verified: all historical bypass strings (round 1 and round 2, plus a
hypothetical deeper double-wrapped case) now correctly deny; embedded
substrings that must NOT match (e.g. 'notvertex/gemini-x') correctly
don't, thanks to the boundary lookbehind.

Added test_nested_route_deny_is_boundary_based_not_prefix_based
locking in the structural invariant directly, independent of any
particular wrapper scheme.

Verification:
./scripts/test.sh tests/test_reasoning_effort_model_capabilities.py tests/test_custom_provider_bare_model_reasoning.py
# 54 passed in 1.31s

Full suite: 11257 passed, 64 pre-existing failures (identical file/test
set as the prior commit's baseline — network/credential/profile-isolation/fd-leak
tests, unrelated to reasoning_efforts).
2026-07-01 10:02:45 -05:00
Charles Inglis 0109baa142 fix: strip named-provider slug before nested-route reasoning deny
Resolve gate certifier feedback on PR #5313 (deeper bypass, round 3):

A provider-qualified hint like `@custom:<slug>:vertex/gemini-image-1.0`
still bypassed `_nested_route_reasoning_denied()`. The old
`_strip_provider_hint_for_reasoning()` did a naive first-colon split,
which only removed the leading `@custom:` wrapper and left the named
provider's slug (e.g. `agg:vertex/gemini-image-1.0`) attached to the
model id. That leftover slug fragment no longer starts with
`vertex/gemini-`, so the nested-route deny missed it and a configured
`providers.<name>.reasoning_efforts` / `custom_providers[].reasoning_efforts`
allowlist re-enabled reasoning controls on Gemini image/embedding routes
that must never expose them.

Fix: `_strip_provider_hint_for_reasoning()` now accepts the resolved
provider id and strips the exact `@{provider}:` prefix first (e.g.
`@custom:agg:`), so both the wrapper and the slug are removed in one
pass before the nested-route deny check runs. Falls back to the
original first-colon split when no provider is supplied, preserving
existing behavior for plain `@provider:model` hints.

Verified in-process: both
`@custom:agg:vertex/gemini-image-1.0` and
`@custom:agg:vertex/gemini-embedding-001` now correctly resolve to []
instead of leaking the configured ["low", "high"].

Added regression test extending the existing nested-route-deny test to
cover provider-qualified hinted models.

Verification:
`./scripts/test.sh tests/test_reasoning_effort_model_capabilities.py tests/test_custom_provider_bare_model_reasoning.py`
-> 53 passed in 1.23s
Full suite: 11258 passed (63 pre-existing failures unrelated to this
change — network/credential/profile-isolation/fd-leak tests; confirmed
identical failure set on unmodified branch).
2026-07-01 09:35:14 -05:00
Charles Inglis 4f4c8fd183 fix: respect custom provider config and nested route denies
Resolve review feedback on PR #5313:
- Read reasoning_efforts from named custom_providers entries for custom:<name>
  providers instead of looking only in providers:<name>.
- Preserve the nested-route deny ordering before the provider-config shortcut
  so Gemini image/embedding routes cannot be re-enabled by config.
- Deduplicate configured effort values before returning them.

Add focused regression tests for bare provider config, named custom provider
config, all-invalid fallthrough, ACP hard guards, and nested route denies.
2026-07-01 07:35:08 -05:00
Gordie fb8bf2f5bd fix(webui): keep #1855 fast-path window and Copilot gpt-4o regression tests green
- Extract the MoA @moa:/moa/ prefix-stripping branch of
  _resolve_compatible_session_model_state into a new
  _moa_fast_path_model_state() helper. Inlining it had pushed
  `catalog = get_available_models()` just past the 6000-char
  source window that
  tests/test_issue1855_resolve_model_provider_fast_path.py::
  TestFastPathSourceShape scans to guard the #1855 fast-path/
  catalog-call ordering, breaking that regression test after
  rebasing onto current master.
- Re-add gpt-4o to the refreshed Copilot static fallback list.
  It's a real Copilot-served model and
  tests/test_issues_373_374_375.py::TestStaleModelListCleanup::
  test_copilot_list_unchanged asserts the Copilot list keeps it
  even as #374 removes it from the generic OpenAI list. The live
  Copilot catalog probe remains authoritative; this only affects
  the cold-start/probe-miss fallback.

Verification:
  ./scripts/test.sh tests/test_issue1855_resolve_model_provider_fast_path.py tests/test_issues_373_374_375.py tests/test_issue5057_moa_webui_route.py tests/test_copilot_provider_model_settings_not_allowlist.py tests/test_moa_model_picker_provider.py tests/test_pr1970_lmstudio_base_url_fallback.py -q
  -> 73 passed in 3.60s

  ./scripts/test.sh tests/ -k "config or provider or model or copilot or moa or lmstudio" -q
  -> 1745 passed, 2 skipped, 9775 deselected, 1 xpassed in 68.57s
2026-07-01 07:01:51 -05:00
Gordie 0b8e67c20d fix(#5301): admit active models-only custom provider without reopening dedup regression
Maintainer review found regression 2 on commit 575996a7: the new
_has_provider_route gate (api/config.py) required api/base_url/api_key/key_env
before admitting a configured provider into the picker, which dropped a
models-only custom provider config (the lmstudio-style shape from #1970,
tests/test_pr1970_lmstudio_base_url_fallback.py::test_provider_catalog_preserves_dict_shaped_raw_key_lookup).

A naive fix (admit any config with models) would re-break
test_unknown_duplicate_copilot_provider_config_is_not_rendered, since a
spurious alias like copilot-2: {name: copilot, models: {...}} must stay
rejected.

Fix: admit a models-only provider config as evidence only when its canonical
id matches the active/configured provider (threaded via active_provider,
already in scope). Non-active models-only configs (including duplicate
aliases of known providers) are still rejected.

Added a regression test:
tests/test_pr1970_lmstudio_base_url_fallback.py::test_provider_catalog_rejects_non_active_models_only_custom_provider
covering both the active-admits and non-active-rejects cases side by side.

Verification:
./scripts/test.sh tests/test_issue5057_moa_webui_route.py tests/test_copilot_provider_model_settings_not_allowlist.py tests/test_moa_model_picker_provider.py tests/test_pr1970_lmstudio_base_url_fallback.py -q
26 passed, 1 skipped in 5.58s

Broader sanity pass: ./scripts/test.sh tests/ -k "config or provider or model or copilot or moa or lmstudio" -q
1728 passed, 5 skipped, 9654 deselected (4 pre-existing failures confirmed unrelated: 2 reproduce identically on the pre-fix commit via git stash, 2 are order-dependent and pass in isolation).
2026-07-01 07:01:51 -05:00
nesquena-hermes cf1ab5bbbc fix(#5301): scope models-as-settings-map guard to Copilot only (was breaking providers.<id>.models allowlist for all built-ins, #644 regression) 2026-07-01 07:01:51 -05:00
Gordie 07a1509feb fix(webui): harden MoA picker preset fallbacks
- Avoid an unguarded resolve_moa_preset fallback call when preset resolution fails.
- Populate the MoA picker directly from configured presets so older Hermes Agent installs that lack provider_model_ids('moa') still render the virtual provider.
- Add regression coverage for both review findings.

Verification:
./scripts/test.sh tests/test_issue5057_moa_webui_route.py tests/test_copilot_provider_model_settings_not_allowlist.py tests/test_moa_model_picker_provider.py -q
12 passed, 1 skipped
2026-07-01 07:01:51 -05:00
Gordie f29441bf09 fix(webui): split MoA picker and Copilot catalog fixes
- Keep resolve_moa_preset optional so older hermes-agent installs still degrade gracefully.
- Surface MoA presets in the WebUI model picker as a virtual provider.
- Keep configured Copilot per-model settings from collapsing the built-in catalog.
- Refresh the Copilot static fallback used when the live catalog probe misses.

Verification:
./scripts/test.sh tests/test_issue5057_moa_webui_route.py tests/test_copilot_provider_model_settings_not_allowlist.py tests/test_moa_model_picker_provider.py -q
11 passed, 1 skipped
2026-07-01 07:01:51 -05:00
Charles Inglis b80ea016b1 fix: guard cursor-acp/copilot-acp before config lookup, fall through on all-invalid list
Addresses Greptile review feedback on PR #5313:
1. Move cursor-acp/copilot-acp guard before step 0 so a stray config
   entry can't surface unsupported effort options for those providers.
2. Only short-circuit when the filtered list is non-empty; an all-invalid
   list (e.g. typos) falls through to heuristics instead of hiding
   reasoning support from the UI.
2026-06-30 23:28:03 -05:00
Charlesandgreptile-apps[bot] 26fb3745f9 Update api/config.py
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-06-30 23:25:43 -05:00
Charles Inglis b84832e999 feat: support per-provider reasoning_efforts in config.yaml
Add a config-driven path to resolve_model_reasoning_efforts() that reads
providers.<name>.reasoning_efforts from config.yaml. This lets users
explicitly list valid reasoning effort levels per provider, so the
WebUI dropdown only shows options the model actually supports.

Handles both custom:<name> and bare registered provider names.
Falls through to existing heuristics (Copilot per-model lists, LM Studio
live API, models.dev) when no config entry is present.
2026-06-30 23:06:09 -05:00
Paladin173 d8f0f8f97f Fix composer control reorder rebase collision
Reapply footer control ordering on current upstream/master while preserving the required situational chip renderer. Persist composer_control_order with backend validation, keep the settings descriptions reorder-aware, and make primary/situational chip renderers participate in same-group drag ordering.

Verified with: node --check static/boot.js; node --check static/panels.js; git diff --check; ./scripts/test.sh tests/test_issue4598_composer_control_visibility.py
2026-06-30 21:11:54 -05:00
nesquena-hermes 3e84d7bf11 Merge #4738 (savagebread neon skins + skin-persistence fix) into stage 2026-06-30 22:21:28 +00:00
nesquena-hermes 0b2507ac9f fix(#4738): register neon-soft/neon-paint in _SETTINGS_SKIN_VALUES (server-side skin persistence) 2026-06-30 22:13:36 +00:00
nesquena-hermes 78ca4d43a1 Merge #5005 (futureworld678 Shift+Enter) into stage 2026-06-30 21:51:42 +00:00
nesquena-hermes 0b74397fa6 Merge #5120 (rodboev budget limit) into stage 2026-06-30 21:28:51 +00:00
nesquena-hermes b098e5c658 feat(#4676): gate per-project quick-create buttons behind opt-in setting
Add a default-OFF 'project_quick_create_buttons' setting that gates the
per-project '+' quick-create buttons on sidebar project chips. The chip
render site only calls _attachProjectQuickCreateButton when the new
window._projectQuickCreate runtime flag is enabled, so the buttons do
not appear unless the user opts in via Settings.

Mirrors the large_text_paste_as_attachment opt-in plumbing (defaults,
allowlist, settings collect/restore/save, window hydration at both sites,
checkbox row, i18n across all locales).
2026-06-30 19:49:07 +00:00