Talk to Hermes — streaming, conversational voice with barge-in — Voice mode used to mean: speak, wait for the whole reply to generate, then listen to one long audio file. Now Hermes speaks clause-by-clause as the response streams, you can i
Wake words and hands-free control — Say your own open-vocabulary wake phrase ("hey Hermes", or anything you pick) and Hermes starts listening — detection runs on-device, so no audio leaves your machine while it waits. Multi-profile voice ro
Voice on every platform — Send a voice note to Hermes on WhatsApp, Feishu, DingTalk, LINE, QQ, Photon, or Weixin and it's transcribed and answered; auto-TTS replies are delivered platform-aware (opus where platforms want opus, captions atta
Research you can trust — grounded citations with fact-checking — The new grounded-citations skill makes Hermes produce research where every claim is backed by a verifiable source: quotes are matched against the actual page text (not halluci
## What does this PR do? Makes messaging continuity durable across gateway restarts so a reply to an already-visible message resumes the exact session and transcript instead of opening an empty context, replaying a blank synthetic turn, losing an accepted inbound ask, or producing a duplicate response. The implementation adds a durable continuity journal/bin
## What does this PR do? remark-math parses prose currency amounts like `US$ 5M` / `R$ 15.4M` as inline math, corrupting message rendering. Escape spaced currency amounts so the parser leaves them as prose. ## Related Issue Fixes #85306 ## Type of Change - [x] 🐛 Bug fix (non-breaking change that fixes an issue) - [ ] ✨ New feature (non-breaking change that a
## Summary Adds a **OneBot 11 platform adapter** as a plugin (`plugins/platforms/onebot/`), connecting Hermes to QQ via local bridges — NapCat, Lagrange, LLOneBot, go-cqhttp — over reverse or forward WebSocket. Unlike the official QQ Bot platform (Tencent-approved app + openid), OneBot drives a regular QQ account, which is what many personal-bot and group us
## Problem When SSE streams encounter errors, the HTTP response is not properly closed. This causes connections to leak and exhaust the connection pool, causing subsequent requests to hang or fail. ## Fix Add `write_eof()` calls on all 4 SSE error paths in `gateway/platforms/api_server.py`: - `_handle_session_sse`: write_eof on session stream error - `_handl
## Problem When a session closes with errors, the nemo_relay scope stack gets out of sync, causing scope.pop() to raise RuntimeError: 'scope handle is not at the top of the stack'. This is a bug in nemo_relay's native Rust extension. Stale handles block future sessions from starting. ## Fix Adds `_attempt_stack_recovery()` which: 1. Peeks at the scope stack
## What does this PR do? Stops the tui_gateway **server queue** from re-firing the live turn's original user prompt **P** after a mid-turn correction **Q** (Desktop `session.redirect` / busy-input interrupt redirect). That re-fire produced a second agent turn and a second active `role=user` row with the same content (#84417). **Root cause (contract layer):**
## Summary - resolve auxiliary vision transport from the selected provider profile instead of silently falling back to OpenAI chat completions - preserve explicit auxiliary overrides while inheriting provider-level api_mode for custom and built-in providers - add focused regression coverage for Codex Responses, explicit overrides, unknown transports, and mai
## Summary Long-form TTS chunking now respects **CJK punctuation** as sentence boundaries. Chinese/Japanese/Korean text has no spaces, so the old logic (`re.split(r"(?<=[.!?;:,])\s+", ...)`) treated an entire paragraph as one "word" and hard-split it at arbitrary characters — e.g. `发现三个隐|患点` — producing unnatural mid-word breaks in spoken output. Latin punct
## What does this PR do? Sessions created by other surfaces (CLI, TUI, gateway, cron) that are actively running on the backend paint as idle in the desktop sidebar, because the live dot only reacts to renderer-side turn activity. This PR adds backend liveness stamps and exposes foreign (agent-created) live rows via `active_list`, then lets the renderer dot/a
## What does this PR do? The sidebar gets "mark as unread/read" backed by the existing `last_read_at` watermark: the backend persists read-state (recursive lineage), the renderer derives a persisted unread dot, and opening a session marks it read. ## Related Issue Fixes #85305 Related (neighboring unread/watermark PRs): #74293, #76504, #82824, #74162 ## Type
## What does this PR do? One-shot surfaces (cli, acp, cron, subagents) create session rows that never close, accumulating as open rows with no liveness. This adds a heal pass: non-chat rows idle for 24h+ get closed (`end_session('orphan_heal')`). ## Related Issue Fixes #85304 Related (same bug class, upstream PRs): #44088, #47292, #56603, #65422, #76995, #50
## What does this PR do? Two stale-end fixes, stacked on #84821: 1. Resuming (mounting) a finalized session is now read-only: it no longer reopens the row (`ended_at` cleared) or re-lights the live dot. Only the first real turn reopens it (`_reopen_if_finalized`). 2. DB-derived liveness now requires real recent activity: `active_list` foreign rows need `last
## Coordinated successor to #69156 This PR is the current-main, behavior-tested successor to the still-open #69156 — **not an independent competing implementation**. Its first commit is #69156 rebased onto current `main`, preserving **Shunkleburger** as that commit's author. The second commit is the minimal follow-up required by current ingress behavior: a r
Fixes #84920. ## What The `[Replying to: "..."]` pointer injected into inbound messages caps the quoted text at 500 chars (`gateway/run.py`). The cut was silent — the agent receiving the context could not tell a truncated quote from a short original, and (see issue) has misdiagnosed it as a truncated *outbound* delivery, then redundantly re-sent the "missing
## Summary A gateway using `gateway.multiplex_profiles` runs all profiles in one process. `setup_logging()` adds every profile's file handlers to one root-logger `QueueListener`, so once a second profile initializes, records are written to **all** profile log files. In a live multiplexed Telegram deployment, this placed 132 lines from the owner's session — i
## What does this PR do? The All-profiles aggregate view (sidebar grouped by "profile") rendered groups in a fixed order — `default` pinned first, the rest alphabetical — with no way to reorder. This adds drag-to-reorder for profile group headers, reusing the same `ReorderableList` + grab-handle machinery already used for flat session rows and project overvi
## Problem Continuous gateway sessions (the normal state for a Telegram/Slack agent) keep the Relay session scope open for days or weeks. Relay export is close-driven, so: - the session root span and its attributes stay unexported until `/new` or idle-end, - any marks attached to the session scope are held with it, - a crash or redeploy loses the entire open
Cron failure alerts around the fallback chain were lying in three ways, all field-reported from an enterprise fleet running ~40 jobs: 1. Every provider failure said "Fallback chain was exhausted or unavailable" even when no chain was configured, sending operators to debug a fallback that never ran. 2. The scheduler's own inactivity watchdog (a stuck tool cal
## Problem A kanban worker's memory-provider `on_session_end` fires only when the process reaches `_run_cleanup` -> `shutdown_memory_provider`. - The non-goal `-q` worker reaches it from `_finalize_single_query`'s `finally`. - The fully-quiet `-Q` goal-mode worker relied **solely on `atexit`**. `atexit` is bypassed by the kanban `os._exit(0)` signal handler,
## What does this PR do? `hermes skills install <url>` fails with a misleading `Could not fetch '<url>' from any source` for large SKILL.md files that mention glob patterns or directory paths under allowed support dirs. Root cause: `_referenced_support_paths()` in `tools/skills_hub.py` treats any `references|templates|scripts|assets|examples/...` token (matc
## Problem Hermes persisted visible transcripts, but request construction and durable session ownership were split across the normal agent loop, Codex app-server, clients, and provider continuation state. That made history correctness depend on the selected transport and left model/provider switching, restart recovery, idempotency, and large tool envelopes w
## What does this PR do? Adds opt-in, per-call/per-task `model` and `reasoning_effort` selection to `delegate_task`. Hermes already supports a global `delegation.model`, which is a good default for inexpensive workers, but one quality-sensitive task in a batch may need stronger compute. Today that requires mutating global config (racy for parallel work) or l
## What does this PR do? Completes and polishes the German (`de`) locale for Hermes Desktop. This draft builds on #51762 and contains its original locale commit plus two focused follow-up commits. The original locale covered the translation catalog, but current desktop components still contained renderer-level English strings and newer catalog fields could f
## Summary `RelayRuntime.ensure_session()`'s own `except RuntimeError:` branch — taken when the shared `_scope_op_executor()` refuses new futures during interpreter shutdown — still ran the native `scope.push` synchronously and unbounded on the calling thread: ```python except RuntimeError: # Interpreter shutdown: executor refuses new futures; # push synchro
## Summary Fix Hermes Desktop's integrated terminal routing when the window's primary profile is local but the selected profile uses profile-scoped SSH. Before this change, chat, files, and sessions followed the selected profile's backend, but the integrated terminal resolved only the window's primary connection. A Windows Desktop configured as: ```text All
## What does this PR do? Prevents Hermes Desktop sessions for remote-gateway profiles from disappearing after a profile switch when the sidebar requests a logical window larger than the remote `/api/sessions` endpoint allows. Desktop's aggregate profile endpoint accepts up to 500 rows, and active sidebar filters raise the requested window to 300. The Electro
## Summary This repo has an established, well-tested convention (`hermes_cli/_subprocess_compat.py::windows_hide_flags()`, `tests/test_windows_subprocess_no_window_flags.py`) for hiding the console window a short-lived console subprocess would otherwise flash on Windows. Three spawn sites don't follow it: 1. **`hermes_cli/session_lost_and_found.py`** (brand-
## Summary `PlatformActions._resolve_adapter()` (`hermes_cli/platform_actions.py`, the `ctx.platform_actions` facade landed by #84989) unconditionally reads `runner.adapters` — the **default profile's** adapter registry: ```python adapter = getattr(runner, "adapters", {}).get(platform_enum) ``` Every other adapter-resolution path in this codebase is careful
## What does this PR do? Model pickers in Desktop and Dashboard currently offer the full reasoning-effort scale for every reasoning model, including levels the selected model cannot represent. This change preserves exact models.dev reasoning controls in the shared model-options payload and filters both pickers, preventing users from saving unsupported choice
## Problem In `_cmd_show()` display mode, `task_graph_context(conn, task.id)` was called after the `connect_closing()` with-block had already closed the connection, raising: ``` sqlite3.ProgrammingError: Cannot operate on a closed database. ``` Fixes https://github.com/mjbfg1995/agent-control/issues/11 ## Fix Pre-compute the graph dict inside the `with` bloc
## Summary `register_context_reference_provider()` (`agent/context_references.py`) writes into one bare, process-global dict (`_context_reference_providers`) with no profile key at all. It's the one registration surface the "widen ownership ledger to all registration surfaces" effort (commits `2219747990`, `85020f2238`) missed: 20 of 21 `PluginContext.regist
## Summary Fixes a false-positive diagnostic in `check_systemd_timing_alignment()` (`gateway/shutdown_forensics.py`): system-level gateway installs (`hermes gateway install --system`) logged `"Stale systemd unit detected: ... TimeoutStopSec=90s but drain_timeout=180s"` on every boot even though the actual unit was configured correctly (e.g. `TimeoutStopUSec=
## Summary `apps/desktop/src/store/suggestion-providers/mcp.ts` and `skill.ts` each keep a 5-minute TTL cache (`configuredNames` / `index`) of profile-scoped REST reads (`listMcpServers()` / `getSkills()`). Both already export an `invalidate*SuggestionIndex()` function — `mcp.ts`'s own docstring literally says *"Drop the configured-servers cache (**profile s
## Summary Telegram only renders a true voice message (round bubble, working speed control, no filename) for Ogg/Opus. Free **Edge TTS** emits MP3/M4A, which `send_voice` was routing through `sendAudio` as a plain audio *file* (shows a filename, no speed control). Delivery also depended on the agent emitting an optional `[[audio_as_voice]]` marker — fragile,
## Summary `_reconnect_after_notadb()` self-heals the single shared **write** connection (`self._conn`) when it starts raising `sqlite3.DatabaseError("file is not a database")` — the runtime-corruption signature left when a sibling process (a forked curator agent, an external repair pass) replaces/truncates the backing file out from under a live connection.
## Problem When the local proxy (SpeedCat/Clash) runs in **TUN mode with fake-ip DNS**, `bots.qq.com` and `api.sgroup.qq.com` resolve into the `198.18.0.0/15` benchmark range. The SSRF guard in `tools/url_safety.py` then blocks the QQ Bot adapter's requests: ``` Reconnect failed: Failed to get QQ Bot access token: Blocked request to private/internal address
## Problem `hermes_cli/web_dist` is gitignored **and** is the live static root the dashboard serves. `web/vite.config.ts` sets `outDir: "../hermes_cli/web_dist"` explicitly, so any `cd web && npm run build` — including the exact recovery command `_do_build_web_ui` prints on failure — writes the current working tree into production. The deployed bundle carrie
## What does this PR do? <!-- Describe the change clearly. What problem does it solve? Why is this approach the right one? --> ## Related Issue <!-- Link the issue this PR addresses. If no issue exists, consider creating one first. --> Fixes # ## Type of Change <!-- Check the one that applies. --> - [ ] 🐛 Bug fix (non-breaking change that fixes an issue) - [
## Linked Issue Closes #84058 ## Description The desktop app intermittently drops the cursor from the composer input (reads as an app "refresh") when an agent tool call starts streaming. Root cause: a re-mounting edit-composer subscription calls `beginEdit(messageId)` a second time for a message whose composer is already open, and the vendor base class `Exte
## Summary When resuming a session with `hermes --resume` or `/resume`, the model would silently revert to the config default instead of using the model the session was actually using. This affected any session where the user had switched models mid-conversation with `/model`. **Root cause — two bugs:** 1. **CLI `/model` switch didn't persist to the session
## What does this PR do? On Windows, `hermes gateway stop` skips the graceful drain entirely whenever the gateway runtime lock does not currently name the live gateway — the in-flight agent turn is abandoned and `resume_pending` is never persisted, so nothing auto-resumes on the next boot. That is the exact regression #33798 was merged to fix (`10ee4a729ba`,
Four read-only additions to `hermes kanban`, so a consumer can tell *why* work stopped and *whether it is moving* without opening any task. Stacked on #83348 — it needs the `activity-v1` groundwork. Review that one first; this branch's diff against it is the four commits below. ## What each commit adds **`report why work stopped and whether it is moving`** —
`hermes sessions activity` aggregates interactive sessions per workspace and emits counts and timestamps only. Stacked on #85266, which is stacked on #83348. This branch's own change is one commit: **147 lines added, 0 removed.** ## Why A person driving work by hand in a terminal leaves no kanban row. Anything watching the kanban store therefore reports that
## Summary This PR closes the Hermes Kanban failure chain where recovery/escalation triage could be mistaken for fresh intake, existing graphs could be duplicated, and non-code children could inherit code worktrees. It also introduces a deliberately report-only workspace lifecycle registry and closeout manifest surface. ### Cut 1: safe orchestration boundari
## What does this PR do? Desktop users connected to a remote backend can now preview and download agent-generated files that live in a Docker sandbox bind mount. Previously, the backend treated paths such as `/workspace/report.txt` as host paths and returned 404 even though the artifact existed in the configured sandbox workspace. ### Symptom In an SSH-remot
## Summary - normalize only the parent directory of the first `ProgramArguments` executable when comparing launchd plists - treat aliases such as `venv -> .venv` as the same virtualenv without collapsing distinct virtualenvs that share a base interpreter - add regression coverage for both alias equality and distinct-venv inequality ## Why On macOS installs,
## Summary Adds an opt-in `queue_to` setting for verified webhook routes. It delivers the rendered webhook prompt as a normal, full user turn into an explicitly configured platform/chat/thread session, using the gateway runner’s existing FIFO machinery. - Preserves current independent webhook delivery when `queue_to` is absent. - Busy target sessions receive
## What does this PR do? `TurnRunner.run_sync()` forwarded `compression_exhausted` from its empty-response return path, but dropped the flag when the agent returned a non-empty final response. Compression exhaustion itself produces a non-empty error response, so the gateway never received the flag and its existing session auto-reset recovery could not run. S
## What does this PR do? Normalizes numeric YAML scalars in stdio MCP `env` mappings before constructing `StdioServerParameters`. OS process environments and the MCP SDK require string values. Hand-written YAML such as: ```yaml mcp_servers: my-server: command: /path/to/server env: MY_FLAG: 1 ``` currently loads `MY_FLAG` as an integer. `_build_safe_env()` pa
## What this adds Three features not on upstream main, built on current `nous/main` (fa83af3f9): 1. **`mode=queue/steer/interrupt` with queue as safe default** — busy sessions are never interrupted; messages queue at a safe boundary and inject after the active tool completes. Upstream main's `inject_message` INTERRUPTS busy sessions. 2. **`non_control` flag