Commit graph

4058 commits

Author SHA1 Message Date
Teknium
58708c7066 fix(git): never block internal git calls on credential prompts
Port from openai/codex#34540 / #34612 ("detach non-interactive
subprocesses from stdin"): internal git invocations that run with nobody
attached — MCP catalog installs, plugin install/update, profile
distribution staging, worktree base fetches, and the desktop review
pane's git/gh backend — could hang on a credential prompt when a remote
is private, misconfigured, or requires auth. git prompts on the
inherited terminal (or via Git Credential Manager on Windows), so the
operation silently waits until its timeout, or forever at sites without
one (mcp_catalog clones have no timeout at all and inherit the parent
terminal).

- Add noninteractive_git_env() to hermes_cli/_subprocess_compat.py:
  GIT_TERMINAL_PROMPT=0 + GCM_INTERACTIVE=Never on a copy of the
  environment; GIT_ASKPASS/SSH_ASKPASS deliberately preserved so
  working non-interactive auth still succeeds.
- Wire it + stdin=DEVNULL into: mcp_catalog._do_git_install (clone/
  checkout), plugins_cmd (clone + pull), profile_distribution._git_clone,
  web_git._git/_gh (gh also gets GH_PROMPT_DISABLED=1), and cli.py's
  worktree base fetch helper.
- Tests: env contract, a real-git E2E against a local 401 Basic-auth
  HTTP server proving fail-fast ("terminal prompts disabled") instead
  of a hang, and per-call-site plumbing assertions. Sabotage-verified:
  removing the env from web_git._git fails the site test.
2026-07-28 17:34:21 -07:00
Brooklyn Nicholson
612b23f6c0 fix(web): raise uvicorn WS frame cap for Desktop file.attach
Uvicorn's 16 MiB default drops one-shot base64 remote attachments before
Hermes sees them. Raise ws_max_size to fit the 256 MiB attach reader after
base64 expansion, and bump DESKTOP_BACKEND_CONTRACT to v5 so older remotes
surface skew instead of silent disconnects.

Co-authored-by: Börje <borje@dqsverige.se>
2026-07-28 19:20:14 -05:00
Brooklyn Nicholson
812b75fdfc fix(desktop): refuse hardened-runtime sign when entitlement plists are missing
Hardened-runtime restrictions are enforced even for ad-hoc signatures,
so signing with --options runtime without the allow-jit entitlements
would leave Electron/V8 crashing on launch — strictly worse than the
legacy plain ad-hoc sign. Raise instead, so the fixup falls back to the
legacy path and the bundle always stays launchable.
2026-07-28 17:34:43 -05:00
Brooklyn Nicholson
5d171ffbbe fix(desktop): stable macOS signing identity so TCC grants survive rebuilds
Local/self-updated macOS builds were finished with a plain
'codesign --force --deep --sign -', leaving a cdhash-only Designated
Requirement and stripping electron-builder's entitlements. Every rebuild
changes the cdhash, so TCC treats the new bundle as different code and
forgets Full Disk Access, Desktop/Downloads/Documents, Accessibility,
Automation, and microphone grants — users re-approve everything after
every update.

Rework the relaunch fixup to sign inside-out (standalone Mach-O
binaries, nested frameworks/helpers, then the main bundle), preserving
the repo's entitlement plists, and pin an identifier-based Designated
Requirement when signing ad-hoc so TCC has a stable identity to persist.
Opt-in desktop.macos_signing_identity names a persistent keychain cert
(self-signed Code Signing cert works — no Apple Developer account) for a
certificate-anchored DR, the strongest form. An intact Developer ID
signature is detected and never clobbered, callers can pass the
publisher-signing decision explicitly so a later dotenv load can't flip
it, and the legacy deep ad-hoc sign remains the last-resort fallback.

Co-authored-by: lewis4x4 <lewis4x4@users.noreply.github.com>
Co-authored-by: natebransc <natebransc@users.noreply.github.com>
Co-authored-by: caseyanthony <caseyanthony@users.noreply.github.com>
Co-authored-by: gvago <gvago@users.noreply.github.com>
Co-authored-by: twe-cloud <twe-cloud@users.noreply.github.com>
2026-07-28 17:28:32 -05:00
atakan g
d210500b54 fix(desktop): surface external venv update blockers 2026-07-28 14:47:36 -07:00
Siddharth Balyan
3cedac00b7
fix(credits): reintroduce grant_spent, gated on an in-session crossing (#73634)
* Revert "fix(credits): remove the 'Grant spent · $X top-up left' notice"

This reverts commit 5dc6a14c14.

* fix(credits): reintroduce grant_spent behind an in-session crossing gate

The removed notice nagged because its condition is a steady state for
accounts living on top-up, the latch is per-session, and the cold-start seed
runs the policy at every session open — every session re-announced
'Grant spent · $X top-up left'. Reintroduce it gated so only a session that
WATCHES the grant run out announces:

- seen_grant_unspent crossing gate, mirroring seen_below_90: opens when the
  session observes the grant meaningfully unspent (>= GRANT_UNSPENT_MIN_MICROS,
  1 cent — portal-seeded states derive micros from float dollars and can carry
  sub-cent residue where headers report exactly spent). Seeds never prime it.
- The gate guards only the show branch and is consumed by the announcement —
  one announcement per crossing. Header flicker (used_fraction None and back)
  clears the sticky line but cannot re-announce; a renewal re-opens the gate.
- new_credits_latch() centralizes the latch shape (agent build, lazy re-init,
  and the policy test helper all build through it).
- Tests: steady-state-open stays silent (seed + policy seams), live crossing
  announces once, flicker/renewal/residual cases locked; the rendering test
  primes the gate and asserts its leg count so a gate regression cannot
  silently shrink coverage.
2026-07-28 21:36:23 +00:00
LunarNexus
8c12fa7cf0 fix(lmstudio): respect applied runtime context 2026-07-29 02:19:13 +05:30
Jeffrey Quesnelle
38574a7397
Merge pull request #73544 from afourniernv/fix/relay-desktop-outbox-export
fix(observability): export shared metrics after task completion
2026-07-28 15:56:11 -04:00
Alex Fournier
1c582c4c4a fix(observability): gate export on subscriber flush
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-07-28 12:08:10 -07:00
Teknium
5f1c400e72 fix(web-server): profile-scope the desktop audio endpoints
/api/audio/transcribe, /api/audio/speak, /api/audio/elevenlabs/voices, and
the /api/audio/speak-stream WebSocket resolved TTS/STT config from the
dashboard's own HERMES_HOME regardless of the active profile, so a
non-default profile's voice settings were silently ignored. Give all four
the same optional profile param as the rest of the dashboard surface,
entering _config_profile_scope (await-safe, config-only — the audio paths
touch no skills globals) inside their worker threads.

Backend half of the desktop fix; completes the renderer-side profileScoped()
threading. Fixes #53441 #45506 #66012 #64057.
2026-07-28 11:58:03 -07:00
Teknium
fe3dc29009 fix(voice): honor quoted beep_enabled strings + correct PortAudio OSError hint
Two in-house micro-fixes from issue triage:

- #49883: voice.beep_enabled gates in cli.py and hermes_cli/voice.py used
  bool() on the config value, so a quoted YAML string like "false" or
  "off" kept beeps on. Route through utils.is_truthy_value.
- #18432: AudioRecorder.start() collapsed OSError from _import_audio into
  the 'pip install sounddevice numpy' hint — but OSError means the
  PortAudio SHARED LIBRARY is missing, which pip cannot fix. Mirror
  detect_audio_environment's system-package hint (libportaudio2 /
  brew portaudio / Termux pkg install portaudio) on that path.

Fixes #49883
Fixes #18432
2026-07-28 11:57:37 -07:00
Kewe63
f98952b267 feat(voice): make beep notification volume configurable from config.yaml
Closes #55908. The CLI voice-mode beep amplitude is hardcoded at 0.3 inside
tools.voice_mode:play_beep(), which makes the record start/stop cues too
quiet on low-volume systems and headphones. Users couldn't adjust it
without editing source.

Move the literal into a configurable voice.beep_volume setting (clamped to
0.0-1.0, default 0.3 to preserve prior behaviour). The new
_get_beep_volume() helper reads via the same load_config() pattern used by
cli.py's _voice_beeps_enabled() and hermes_cli/voice.py's _beeps_enabled(),
keeps bools / out-of-range / non-numeric / NaN values safely on the default,
and falls back silently if config can't load so the audio cue never breaks
the voice loop on a degenerate config.yaml.

Covered by tests/tools/test_voice_mode.py:
- TestGetBeepVolume (12 cases: missing key, custom value, boundary 0.0/1.0,
  out-of-range clamp, type coercion, bool guard, NaN guard, exception
  guard, dict-typed voice section)
- TestPlayBeepVolumeWiring (guards against re-introducing a hardcoded 0.3
  literal in play_beep)

Docs: website/docs/user-guide/configuration.md mentions the new key.
Other locale translations (zh-Hans etc.) intentionally untouched —
handled by the regular i18n sync pipeline as a separate change.

No change in default behaviour: existing users hear exactly the same beep.
2026-07-28 11:57:37 -07:00
YAMAGUCHI Seiji
89d5785692 fix(voice): prefer requested MP3 for playback 2026-07-28 11:57:37 -07:00
YAMAGUCHI Seiji
1182efa0a8 fix: play returned TTS audio path in CLI voice mode 2026-07-28 11:57:37 -07:00
Solitud1nem
af7205bea5 fix(voice): thread max_recording_seconds through the TUI path, add behavior coverage
Review follow-up: the sweeper is right that the first commit only cured
half the dead config — the TUI gateway builds its recorder params
explicitly, so the cap never reached recordings started from the TUI.

- start_continuous() grows a max_recording_seconds param (default 0.0 =
  disabled, so existing callers keep today's behaviour) and applies it to
  the shared recorder next to the silence params
- tui_gateway voice.record start forwards the validated cap; corruption
  semantics now mirror the silence params everywhere: non-numeric/bool
  falls back to the documented 120 default (a hand-edited
  `max_recording_seconds: true` must not become a 1-second cap), while
  an explicit numeric <= 0 disables the cap
- cli.py wiring updated to the same corruption semantics

New coverage, per review:
- TestMaxRecordingCap drives the mocked InputStream callback past the
  cap during continuous loud speech (the silence branch physically can't
  fire there) and asserts the one-shot callback fires exactly once; plus
  the disabled-cap negative
- TestMaxRecordingSecondsConfigReal pins the CLI config assignment for
  the valid / zero / bool / garbage cases via _voice_start_recording
- test_voice_record_start_forwards_max_recording_seconds pins the TUI
  forwarding for the same matrix
2026-07-28 11:57:37 -07:00
Jeffrey Cox
ae8d3e2027 fix(discord): make voice timeouts configurable 2026-07-28 11:56:37 -07:00
Teknium
4aac89b429 fix(tts): unify TTS text preprocessing behind one shared cleaner
Consolidates all TTS text-preparation paths onto
tools/tts_text_normalize.prepare_spoken_text:

- strip_nonspoken_blocks: removes <think> reasoning blocks (#34213,
  incl. unterminated streaming blocks) and the end-of-turn
  file-mutation verifier footer emitted by run_agent.py (#40772).
- flatten_newlines_for_payload: collapses newlines into sentence
  breaks so newline-sensitive OpenAI-compatible providers (Kokoro)
  speak the whole script instead of truncating at the first newline
  (#9004).
- tools/tts_tool._strip_markdown_for_tts (voice-mode streaming + web
  dashboard path) now delegates to the shared cleaner, with the legacy
  regex pipeline kept as a best-effort fallback.
- hermes_cli/voice.py speak_text and cli.py _voice_speak_response now
  use the shared cleaner instead of their own duplicated regex
  pipelines.
- gateway auto-TTS fallback also strips think blocks.

Tests: tests/tools/test_tts_prepare_spoken.py covers think blocks,
verifier footer, emoji, newline flattening, and the shared-cleaner
wiring on the tool/streaming/gateway paths. Updated the header
expectation in test_voice_cli_integration.py for the heading-fold
behavior of the shared cleaner.

Closes #34213, #9004, #40772
2026-07-28 11:55:01 -07:00
Teknium
ee1e789877 fix(vertex): merge duplicate vertex curated-list keys from #68767 salvage
Main gained its own vertex curated list (df051c17cc) two days after
PR #68767 was opened, so the cherry-pick produced a duplicate 'vertex'
dict key (later key silently wins in Python dict literals). Merge the
two into one list: union of both, existing entries preserved, contributor's
live-validated additions (gemini-3.6-flash, 3.5-flash-lite, 3.1-flash-lite)
folded in.
2026-07-28 11:54:46 -07:00
ceverson70
d7f6dadbea feat(vertex): add validated Gemini model catalog for /model picker
Add a "vertex" key to _PROVIDER_MODELS with 6 Gemini 3.x models that
were validated live against the Vertex AI OpenAI-compatible endpoint
(aiplatform.googleapis.com, global region, project antse-tooling) on
2026-07-21. All entries returned HTTP 200; 8 other candidates (e.g.
gemini-3.1-flash, gemini-3-pro-preview) returned 404 and were excluded.

Validated models (google/ prefix required by Vertex endpoint):
- google/gemini-3.5-flash       (frontier Flash, agentic)
- google/gemini-3.6-flash       (newer incremental over 3.5)
- google/gemini-3.5-flash-lite  (lighter/cheaper 3.5 variant)
- google/gemini-3.1-pro-preview (3.1 Pro preview)
- google/gemini-3-flash-preview (3.0 Flash preview)
- google/gemini-3.1-flash-lite  (most cost-efficient 3.x model)

Context lengths are covered by the existing "gemini" prefix entry
(1_048_576) in agent/model_metadata.py DEFAULT_CONTEXT_LENGTHS — no
additional entries needed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-07-28 11:54:46 -07:00
kosta
e25d516c8c fix(gemini): bump native provider aux default to gemini-3.6-flash
The native Gemini provider profile's default_aux_model and the curated
model picker catalog were still pinned to gemini-3.5-flash, a stale
generation now superseded by gemini-3.6-flash (documented GA). Bump
both so the auxiliary-task default and the picker stay in sync with
the current model.

Contract test asserts the durable lockstep invariant only
(default_aux_model is a member of _PROVIDER_MODELS["gemini"]) rather
than pinning either side to a frozen model-name string, so it doesn't
need updating on the next model-generation bump.
2026-07-28 11:54:46 -07:00
Fangliquan
0d0ad3f9d9 fix(hermes_cli): lock dashboard xAI active_provider contracts and setup unsuppress
Cover preserve-vs-mark-if-unset for dashboard OAuth, assert TTS setup clears
device_code suppression, and clarify set_active docstring callers.
2026-07-28 11:54:01 -07:00
Fangliquan
f42be94049 fix(hermes_cli): preserve unset-active dashboard xAI OAuth and cover token save modes
Use mark_provider_active_if_unset after dashboard token save, unsuppress
device_code after TTS setup login, and lock default-active plus refresh
active_provider contracts in tests.
2026-07-28 11:54:01 -07:00
Fangliquan
fce06e909d fix(hermes_cli): keep TTS/setup xAI OAuth from switching active chat provider
Save side-tool OAuth tokens without promoting xai-oauth via active_provider
or model.provider so hermes setup tts login no longer hijacks inference routing.
2026-07-28 11:54:01 -07:00
Teknium
38cd253abd feat(update): self-heal the hermes-acp launcher + document Buzz Desktop as an ACP host
Existing installs predate the install.sh hermes-acp launcher, and hermes
update never re-runs setup_path, so ACP hosts (Zed, JetBrains, Buzz)
still resolve Hermes as unavailable until a reinstall. _ensure_acp_launcher()
writes the launcher next to an existing hermes command in ~/.local/bin or
/usr/local/bin during hermes update — delegating to the sibling launcher so
it is correct for every install layout. Never follows symlinks (#21454),
skips unwritable dirs, no-op on Windows (venv Scripts is already on PATH).

Docs: add a Buzz Desktop section to the ACP page (en + zh-Hans).
2026-07-28 11:53:12 -07:00
SHL0MS
a7d5147cf1 fix(install): install a hermes-acp launcher onto PATH
setup_path() wrote a `hermes` launcher to ~/.local/bin but nothing for
`hermes-acp`. That console script exists only inside the venv, which is
not on the login-shell PATH.

ACP hosts resolve the agent by command name against that PATH, so an
otherwise healthy install looks absent to them. Buzz Desktop ships a
Hermes preset that spawns `hermes-acp` and reports the runtime as
unavailable; Zed and JetBrains configs that name the bare command have
the same problem.

Write a hermes-acp launcher next to the hermes one, dispatching to the
acp subcommand. Same PYTHONPATH/PYTHONHOME clearing, and the same rm -f
before cat > so an older symlink into the venv cannot be followed and
stomp the console script (#21454). Uninstall removes both launchers.

tests/test_install_sh_acp_launcher.py drives the block out of install.sh
rather than asserting on a copy, covering the venv and non-venv branches
plus the symlink-stomp case. Reverting the install.sh change turns all
three red.

Signed-off-by: SHL0MS <SHL0MS@users.noreply.github.com>
2026-07-28 11:53:12 -07:00
Teknium
d464ae3652 feat(cron): user-owned model pins + cron.model fleet default
Per-job cron inference pins are now user-owned: the agent-facing cronjob
tool schema no longer exposes model/provider/base_url, and the registered
handler ignores them even if a model hallucinates the old parameters.
Users set pins via the dashboard, hermes cron create/edit --model/--provider,
or jobs.json directly — and once set, a pin sticks until the user changes it.
Existing agent-era pins are grandfathered untouched.

New cron.model / cron.model_provider config keys give the cron fleet its
own default model, independent of the chat model. Fire-time resolution:
per-job pin > cron.model > HERMES_MODEL > model.default. An axis covered
by the explicit cron-fleet default is deliberate routing, not drift, so
the #44585 fail-closed guard skips it — switching your chat model with
/model or hermes model no longer breaks unpinned cron fleets.

- tools/cronjob_tools.py: drop model param from agent schema + handler;
  remove now-dead _resolve_model_override
- cron/scheduler.py: cron.model/model_provider resolution + per-axis
  drift-guard skip
- cron/jobs.py: snapshot resolution mirrors the new precedence
- hermes_cli/subcommands/cron.py + hermes_cli/cron.py: --model/--provider
  on hermes cron create/edit
- hermes_cli/config.py: cron.model / cron.model_provider defaults
- docs: cron.md model-resolution tip rewritten
2026-07-28 11:52:47 -07:00
Teknium
5dc6a14c14 fix(credits): remove the 'Grant spent · $X top-up left' notice
The grant_spent notice fired for every subscription user with top-up
funds the moment their cap was reached and camped in the CLI/TUI status
bar and desktop toasts with no action to take — the account keeps
working off top-up. Remove it everywhere:

- agent/credits_tracker.py: drop grant_cond + the emit/clear block;
  the dev fixture state now (correctly) produces no notice
- TUI: keep the turn-start clear of credits.grant_spent as back-compat
  for older backends that still emit the key
- Desktop: drop the demo step and stale comment references
- Docs/config comments: remove grant-spent from credits_notices text
- Tests updated: policy/cold-start now assert the key never fires

Usage bands, depleted, and restored notices are unchanged; /usage still
reports the full balance breakdown.
2026-07-28 11:21:44 -07:00
Alex Fournier
2aa34c5484 fix(observability): export shared metrics after tasks
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-07-28 11:06:14 -07:00
Bartok9
bff3aa3c1e fix(cli): resolve deferred platform plugin for its top-level CLI command
Closes #54678

`hermes photon ...` could fail with argparse `invalid choice: 'photon'`
even when the bundled Photon platform plugin is present. Photon registers
its top-level CLI command from the platform adapter module via
`ctx.register_cli_command(name="photon", ...)`, but bundled platform
plugins are cheap-registered as *deferred* entries to avoid importing every
gateway SDK during normal startup.

On the unknown-top-level-command slow path, `discover_plugins()` records the
deferred loader but never imports the matching platform module, so the CLI
registration side effect doesn't run and `photon` stays absent from
`_cli_commands` — argparse then rejects it.

Fix: after `discover_plugins()` on that slow path, resolve only the deferred
platform whose name matches the first positional token (via
`platform_registry.get(name)`) before reading `_cli_commands`. This imports
exactly the targeted platform, leaving normal startup cheap (a bare `hermes`
or flags-only invocation has no positional token and touches nothing). The
resolution is best-effort: registry/import failures are logged at debug and
never crash startup.

Added 3 tests in tests/hermes_cli/test_startup_plugin_gating.py: resolves the
matching platform, ignores empty/None command, and swallows registry errors.
Fails without the fix (symbol absent).
2026-07-28 10:49:55 -07:00
David Metcalfe
c8a4b18d34 fix: address community review — system default, clearable schema flag, UTC fallback
- Add 'System default' clear option to SearchableSelect via clearLabel prop
- Add clearable flag to ConfigFieldSchema (schema-driven, not hardcoded)
- Add clearable: true to timezone schema override in web_server.py
- Fix CommandItem value for clear item: use clearLabel instead of '' so
  cmdk can match it during search
- Fix backend: or ['UTC'] fallback for hosts without tzdata where
  available_timezones() returns an empty set (not an exception)
- Add systemDefault i18n key (en, types, zh)
2026-07-28 10:49:14 -07:00
David Metcalfe
5cb0a6aec1 feat(desktop): searchable timezone dropdown in Settings → Chat
The timezone field was a free-text input with no guidance on format.
Users had to know the exact IANA identifier (e.g. America/New_York)
to configure it. Replace it with a searchable combobox built on
Popover + cmdk Command — the same stack as Shadcn's Combobox.

Backend:
- Add `_timezone_options()` to web_server.py (cached at import time,
  returns sorted zoneinfo.available_timezones() — ~598 identifiers)
- Add `"timezone"` to `_SCHEMA_OVERRIDES` with `type: "select"`,
  `options`, and `searchable: true`

Frontend:
- New `SearchableSelect` component (Popover + cmdk Command)
  — closed-world filterable dropdown for large option lists
- `ConfigField` routes to `SearchableSelect` when
  `schema.searchable === true` (explicit opt-in, no threshold)
- Add `searchable?: boolean` to `ConfigFieldSchema` type
- Add i18n keys: `searchPlaceholder`, `noResults`

The `searchable` flag is deterministic — no existing field is
affected unless explicitly opted in. Future large-list fields
can adopt the same pattern by adding `searchable: true` to their
schema override.
2026-07-28 10:49:14 -07:00
Brooklyn Nicholson
78aeeab8d1 feat(sessions): say which surfaces hold the active-session slots
The cap is shared across CLI, desktop/TUI and the messaging gateway, so the
surface that gets rejected is rarely the one holding the slots. The rejection
read "Hermes is at the active session limit (5/5). Try again when another
session finishes." while every slot was an idle desktop tab, which took
filesystem access to work out.

Name the holders in the message, and show slot usage plus each holder in
`hermes status`. Both are inert when max_concurrent_sessions is unset, which
is the default. The gateway's duplicate copy of the message now reuses the
shared helper.
2026-07-28 12:11:52 -05:00
Brooklyn Nicholson
e35c2f6049 fix(sessions): claim the cap slot on first turn, not on open
An open chat window took a session-cap slot at session.create/resume time.
Every desktop tile paint and every background reconnect-resume opens one, so
on a websocket-flappy host they accumulated: five parked desktop tabs filled a
5-slot cap and locked the messaging gateway (which shares the cap) out for
fourteen minutes while running no agents at all.

A slot held that way is invisible everywhere. An unprompted draft has no DB row
and the sidebar filters it out with min_messages=1, so the only way to diagnose
it was reading runtime/active_sessions.json by hand.

Claim on the first turn instead, mirroring the lazy contract
_ensure_session_db_row already uses for the row itself. Capacity now means an
agent can run rather than that a window exists, and anything holding a slot is
something the user can see.

Also reclaim leases whose session skipped teardown. _prune_dead only fires when
the owning pid dies, and a dashboard/serve backend runs for days, so a leaked
lease was held until restart. The owning process reconciles against the leases
it still holds, which is exact and needs no heartbeat write on the turn path.
2026-07-28 12:11:52 -05:00
Teknium
fbc878ee2e feat(models): swap Gemini catalog entries to 3.1 Pro + 3.6 Flash; drop retired Qwen models
- openrouter + nous curated lists: replace google/gemini-3-pro-preview with
  google/gemini-3.1-pro-preview as the sole Pro entry, and
  google/gemini-3.5-flash with google/gemini-3.6-flash
- remove qwen/qwen3.7-plus and qwen/qwen3.6-35b-a3b from both lists
- regenerate website/static/api/model-catalog.json
- test fixture: swap qwen3.7-plus catalog-label fixture to qwen3.7-max
  (must be a model present in the nous curated list)

Both new Gemini ids verified live on OpenRouter /api/v1/models and the
Nous portal /v1/models (1,048,576 ctx — covered by the existing 'gemini'
prefix in DEFAULT_CONTEXT_LENGTHS).
2026-07-28 10:07:18 -07:00
Alex Fournier
b1a5d67e71 Merge upstream main into fix/hermes-relay-anthropic-context
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-07-28 08:10:58 -07:00
liuhao1024
9e2f07e704 perf(dashboard): use GROUP BY for session stats instead of fetching 10k rows
Replaces the O(N) list_sessions_rich histogram in /api/sessions/stats
with a single GROUP BY query, reducing response time from ~575ms to
<1ms on large databases.

Original PR #48921 by @liuhao1024. Salvage fixes based on review
feedback from teknium1 and @wernerhp:

1. Preserve try/except guard — a DB error still degrades to empty
   by_source instead of failing the whole stats response.
2. GROUP BY COALESCE(source, 'cli') — the original GROUP BY source
   could emit duplicate 'cli' keys (NULL group + literal 'cli' group)
   that the dict comprehension silently dropped.
3. Add exclude_children=True — list_sessions_rich excludes subagent
   runs, delegates, and compression continuations by default; the
   bare GROUP BY counted all rows, inflating source counts.

Aggregate shape (exclude_children/include_archived/limit params)
adapted from closed duplicate #61120 by @mijanx.

Closes #48914
Co-authored-by: mijanx <mijanx@users.noreply.github.com>
2026-07-28 19:07:28 +05:30
kshitijk4poor
560800f3cc refactor: salvage follow-ups for PR #59177
- Replace _load_cron_jobs_for_config_warning with lazy import of
  cron.jobs.load_jobs — picks up BOM handling, corruption repair,
  and context-local store resolution for free
- Re-add model.name to axis mapping (was dropped during merge);
  model.name is a legacy alias for model.default
- Fix grammar: '1 enabled unpinned cron job have' -> 'has'
- Pass user_config to cron_model_drift_guard_enabled in set_config_value
  to avoid a redundant load_config() re-read of the file just written
2026-07-28 17:52:21 +05:30
doncazper
3a358cb56b fix(cron): warn before model config changes trip cron drift guard
When an operator changes the global model/provider config, warn that
unpinned cron jobs with stored snapshots will fail-closed on their next
run. Adds a cron.model_drift_guard config opt-out (default true) for
fleets that should deliberately track changing global defaults.

Addresses #59031. Original PR #59177 by @doncazper.
2026-07-28 17:52:21 +05:30
brooklyn!
dcc543af9a
Merge pull request #73172 from NousResearch/bb/featured-models
feat(picker): curated model defaults + collapsible providers + select-all in Edit Models
2026-07-28 04:09:46 -05:00
Brooklyn Nicholson
ff7418315f fix(tui,cli): render a resumed interrupted turn as an event
Desktop already did; the TUI transcript and the CLI resume recap fell
through to the default and printed the raw system note.
2026-07-28 03:54:25 -05:00
Brooklyn Nicholson
d4449c47e2 feat(picker): derive a newest-per-lab featured shortlist from models.dev
Aggregator providers (nous, openrouter) serve dozens of models across many
labs. Add a featured=True enricher to build_models_payload that attaches a
featured_models shortlist to each row: within every lab keep the newest
_FEATURED_PER_LAB models by models.dev release_date, ranked among that row's
own models — never against the current date, so the choice is stable as
models age. Same-date ties fall back to curated list order. Single-lab
providers get an empty list. Derived live from the models.dev catalog already
loaded on this path; no hand-maintained allowlist.
2026-07-28 03:52:33 -05:00
Brooklyn Nicholson
1eb5ee1eaa fix(mcp): make Figma remote OAuth work via DCR allowlist defaults
Figma's mcp.figma.com register endpoint is a client_name allowlist
(Claude Code / Codex succeed; Hermes Agent 403s) and returns a client
secret while advertising auth_method=none, then requires the secret on
token exchange. Auto-set client_name + client_secret_post for Figma
hosts, pass oauth cfg through login/add paths, force interactive OAuth
for hermes mcp login from non-TTY desktop shells, and ship a catalog
entry. Proven: hermes mcp login figma → 26 tools.
2026-07-28 00:53:16 -05:00
Teknium
fab4c888ae feat(voice): say 'stop' to end a voice chat hands-free
Saying EXACTLY a configured stop phrase (default: 'stop') and nothing
else now ends the voice conversation instead of being sent to the agent
as a prompt. Match is deliberately strict — whole utterance,
case-insensitive, surrounding punctuation stripped — so 'stop doing
that and try X' still reaches the agent.

- tools/voice_mode.py: is_voice_stop_phrase() + voice.stop_phrases
  config loader (default ['stop'], [] disables, malformed config falls
  back safely).
- hermes_cli/voice.py: shared continuous loop (TUI + desktop) halts on
  a stop phrase exactly like the silent-cycle limit (fires
  on_silent_limit so every UI turns voice off); stop_continuous
  force-transcribe path swallows the phrase without counting a silent
  cycle.
- cli.py: classic CLI push-to-talk/continuous path and barge-in
  utterance path disable voice mode on a stop phrase.
- Config default voice.stop_phrases: ['stop']; docs updated.

Tests: tests/tools/test_voice_stop_phrase.py (27 tests,
sabotage-verified: loop test fails when detection is disabled);
voice suites green (110 + 44 passed).
2026-07-27 21:26:23 -07:00
Teknium
bc997a36a8 feat(stt): default global stt.language to 'en'
Whisper auto-detection frequently misidentifies short/accented clips,
which users experience as voice notes transcribed in the wrong language
(Teknium + CTO both hit this). The unified resolver from #73067 made a
global hint possible; this makes it the DEFAULT so stock installs stop
guessing. Non-English users set stt.language once; '' restores
auto-detect for multilingual use.

Deep-merge gives existing configs the new default automatically (no
_config_version bump needed); any explicit per-provider or global
language setting still wins.
2026-07-27 21:26:20 -07:00
Alex Fournier
14bed44c8c Reapply "feat(observability): integrate NeMo Relay runtime and shared metrics"
Signed-off-by: Alex Fournier <afournier@nvidia.com>
2026-07-27 21:10:51 -07:00
Teknium
a10bd49ddd feat(stt): unify language resolution across all STT providers
Class-level fix for the 'STT transcribes the wrong language' issue family
(#55551, #50181 and siblings). Previously language handling was per-provider
chaos: local honoured stt.local.language, Groq/OpenAI/Mistral/DeepInfra sent
no language hint at all, xAI silently forced 'en', ElevenLabs used its own
language_code key, and there was no global setting.

- New _resolve_stt_language() helper: stt.<provider>.language >
  stt.language (new global key) > HERMES_LOCAL_STT_LANGUAGE > auto-detect.
- Threaded through ALL providers: local, local_command, groq, openai,
  mistral, xai, elevenlabs, deepinfra (shared OpenAI handler), command
  providers, and plugin dispatch.
- xAI no longer forces English when nothing is configured (auto-detect).
- Mistral Voxtral now receives a language hint when configured.
- stt.groq.model is now honoured from config (previously env-only).
- DEFAULT_CONFIG gains stt.language, stt.groq, stt.xai, stt.mistral.language.
- Tests: tests/tools/test_stt_language_resolution.py (11 tests, sabotage-
  verified) + full transcription suite green (236 passed).

Builds on cherry-picked contributor work from #19786 (@zombopanda),
#23161 (@materemias), #50684 (@BlackishGreen33).
2026-07-27 20:28:31 -07:00
墨綠BG
131251a1ae 🐛 fix(stt): declare local initial prompt default 2026-07-27 20:28:31 -07:00
zombopanda
af2948343c feat(stt): add language parameter support for OpenAI provider
Add optional stt.openai.language config for OpenAI transcription. Forward non-empty hints to the API while preserving auto-detection when unset. Document the config-only setting, add its default, and cover configured and unset request arguments.
2026-07-27 20:28:31 -07:00
Jeffrey Quesnelle
841a5a744a
Revert "feat(observability): integrate NeMo Relay runtime and shared metrics" 2026-07-27 22:28:08 -04:00
Alex Fournier
ba4f5b893a Merge upstream main into feat/hermes-relay-shared-metrics 2026-07-27 17:28:56 -07:00