Commit graph

19005 commits

Author SHA1 Message Date
Teknium
e807b7106c test: set returncode on fake Popen proc (main's player loop reads returncode, not wait()) 2026-07-28 18:12:26 -07:00
Teknium
050461ec83 fix: hoist STT credential read guard to the public transcribe_audio entry point
The rebase onto #73510's prepare/dispatch split left the guard inside
_transcribe_prepared_audio, where source validation ran first and a
blocked .env surfaced a format error instead of the read-block message.
Guard now fires before any validation/preprocessing.
2026-07-28 18:12:26 -07:00
Teknium
e251e78df9 feat(tools): env_passthrough allowlist for command-provider secret scrub
Command providers legitimately reference their own API keys in shell
templates (curl one-liners). The #70342 scrub removes ALL provider keys,
which would break such setups. Add a per-provider env_passthrough list
(TTS + STT) that copies named variables back from the parent env, plus
docs and tests. Scrub stays the default; passthrough is explicit opt-in.
2026-07-28 18:12:26 -07:00
Teknium
59fc68e93f test: align env-scrub fixtures with idle-timeout runner; assert env passed in no-shell kwargs 2026-07-28 18:12:26 -07:00
Teknium
fc26e965bb fix(tools): apply idle timeout to command STT runner (class fix for #50081)
Port the progress-based idle-timeout pattern from _run_command_tts
(PR #50087, @CleanDev-Fix) to _run_command_stt: the timeout resets on
any stdout/stderr output, so a slow-but-alive STT provider survives
while a silently stalled one is killed. Stuck detection stays
progress-based, never wall-clock.
2026-07-28 18:12:26 -07:00
Frowtek
38bb193f38 fix(stt): route transcription inputs through the shared read guard
`transcribe_audio` reads a local file and hands it to the configured STT
provider — for the hosted providers (Groq, OpenAI, Mistral, xAI, ElevenLabs)
that ships the file's bytes to a third-party API. The same local-input read
guard was added to image-gen (587be5b5b) and xAI video-gen (104232979) to keep
the agent from feeding credential/secret stores to a provider, but STT was
missed.

Call `get_read_block_error(file_path)` at the top of `transcribe_audio`, before
validation/dispatch, so a `.env`, `auth.json`, `.anthropic_oauth.json`,
`mcp-tokens/`, etc. is refused up front instead of being transcribed (and, for
hosted providers, exfiltrated). This is defense-in-depth, not a security
boundary — the guard's own message says so — but it restores parity with the
image/video-gen tools.

Regression test: a `.env` file is refused with the shared read-guard message
before any provider dispatch (mutation-verified).
2026-07-28 18:12:26 -07:00
dsad
37d0b6c81a fix(tts): block output to protected paths 2026-07-28 18:12:26 -07:00
Eugeniusz Gilewski
b76acacbb9 fix(tools): execute local STT templates without a shell
HERMES_LOCAL_STT_COMMAND rendered quoted placeholders into a
user-configured template and passed the result to shell=True. Shell
metacharacters in the template therefore remained executable syntax even
though the placeholder values themselves were quoted.

Tokenize the rendered template and invoke it as an argv list while
preserving the existing timeout, closed stdin, and Windows creation flags.
Lock the invocation contract with metacharacter regression coverage and
document explicit shell wrapping for trusted templates that need it.

Salvages #32694

Co-authored-by: Ernest Hysa <takis312@hotmail.com>
2026-07-28 18:12:26 -07:00
Sherman
273b986fd9 feat(tools): expand command TTS output_format allowlist (m4a/aac/amr/opus)
Command-type TTS providers validated output_format against a hardcoded
{mp3,wav,ogg,flac} set; any other value was silently coerced back to mp3,
which then mismatched the output path the post-run check expects. This
blocked common ffmpeg-producible containers/codecs — notably m4a (AAC),
the portable choice for WeChat/iOS/mobile voice files — with no
config-only path (only a local source patch, lost on every update).

Widen COMMAND_TTS_OUTPUT_FORMATS to add m4a, aac, amr, opus. This only
permits a command provider to declare these; the user's command still
produces the file (e.g. via ffmpeg). No built-in provider behavior
changes and no new required config.

Update the two tests that pinned the old set, and add a positive case
covering the new formats. Document the supported output_format values.
2026-07-28 18:12:26 -07:00
峯岸 亮
3ae25e0fbd fix(security): scrub credentials from voice playback subprocesses
## Summary
- Spawn system audio players (`ffplay` / `afplay` / `aplay`) with `hermes_subprocess_env(inherit_credentials=False)`.
- Prevent gateway tokens and provider API keys from leaking into OS media helpers.
- Add a regression test asserting scrubbed env on `Popen`.

## Salvage / credit
Sibling of #70342 / incomplete #56332 (TTS/STT command scrub) on the voice-mode playback path.
2026-07-28 18:12:26 -07:00
峯岸 亮
24a6fb6448 fix(security): scrub Hermes secrets from voice command subprocess env
Salvage incomplete #56332: route command TTS/STT through hermes_subprocess_env
while preserving delegated-child lineage, and close the sibling local-whisper
subprocess.run path that still inherited the full process environment.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 18:12:26 -07:00
CleanDev-Fix
4e8a66dace fix(tools): keep command TTS deadline through exit 2026-07-28 18:12:26 -07:00
CleanDev-Fix
1b97e3efc5 fix(tools): chunk command TTS stream reads 2026-07-28 18:12:26 -07:00
CleanDev-Fix
3884f078bd fix(tools): use idle timeout for command TTS 2026-07-28 18:12:26 -07:00
Teknium
43333acdda ci: pin uv version in setup-uv to eliminate per-job manifest fetch
Unpinned, astral-sh/setup-uv resolves 'latest' by fetching
https://raw.githubusercontent.com/astral-sh/versions/.../uv.ndjson on
EVERY job. A transient failure of that fetch fails the whole job before
any test runs (2026-07-28: tests slice 5/8 died 12s in with
'##[error]fetch failed' on PR #73514). Pinning version makes setup-uv
download the binary directly — one less external hop per job across all
7 call sites (tests, lint x2, docker, e2e-desktop, lockfile-check).
2026-07-28 17:59:23 -07:00
Teknium
7e7f7d3059
Merge pull request #70509 from NousResearch/hermes/hermes-29661bf6
feat(voice): on-device wake words with open-vocabulary phrases and multi-profile voice routing
2026-07-28 17:58:33 -07:00
brooklyn!
392f1b9477
Merge pull request #73705 from NousResearch/bb/terminal-clipboard
feat(desktop): copy and paste in the GUI terminal
2026-07-28 19:58:31 -05:00
brooklyn!
fca2aa7236
Merge pull request #73741 from NousResearch/bb/composer-strip-align
Align the composer status lane with the surface
2026-07-28 19:58:08 -05:00
Teknium
1b9377b1fd fix(buzz): scoped identity lock, negative name caching, sidebar registration
Follow-up on the salvaged #71610 commits:
- acquire/release a scoped lock on relay_url+pubkey in connect/disconnect
  (IRC pattern) so two profiles can't drive one Buzz identity — duplicate
  replies and split de-dupe state; +2 tests
- negative-cache _resolve_user_name failures so a profile-less pubkey
  doesn't re-hit 'users get' every poll sweep (flagged by @jethac on the PR)
- register user-guide/messaging/buzz in website/sidebars.ts (page was
  unreachable — the #63359 trap)
2026-07-28 17:57:48 -07:00
Rob Zehner
e01d6650fe docs(user-guide): add annotated recommended default settings for Buzz adapter
Expands the 'Recommended display settings' section into a comprehensive
'Recommended default settings' block covering display, access control,
polling, and mention behavior. Each setting includes an inline annotation
and rationale bullet. Matches Telegram/email default behavior (no
intermediate tool output, mention-gated channels, private-by-default
access).

Refs #68871
2026-07-28 17:57:48 -07:00
Rob Zehner
7e1e3b92f8 fix(plugin): classify Buzz DMs by p-tag so un-mentioned DMs dispatch
On hosted relays `buzz dms list` reliably returns [] even when DM
conversations exist, so DMs leaked in via `channels list` (as entries
named "DM" with an empty description) and were seeded chat_type="group".
That put them behind the channel mention gate: "@Chip /whoami" worked
but an un-mentioned DM was silently dropped.

Classify from the Nostr tags of real traffic instead: a message another
user sends in a DM carries a structural ["p", <own pubkey>] tag even
when the text never mentions the agent, while in a real channel a
p-tag-to-self only ever accompanies a visible @mention (typed mention
or reply). A group conversation therefore latches to chat_type="dm" on
the first kind-9 event that is p-tagged to self WITHOUT a visible
mention in the content — guarded by channels-list metadata so a real
community channel (real name / non-empty description) is never
reclassified by a reply or mention that p-tags the agent.

- latch during history seeding too, so a leaked DM bypasses the
  mention gate from the very first poll after connect
- keep `dms list` as a best-effort source, and scan `channels list`
  as a fallback so DM conversations opened mid-run still get watched
- strip a leading @mention in DMs as well, so "@Chip /whoami" keeps
  firing as a slash command after the conversation reclassifies

Refs #68871

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:48 -07:00
Rob Zehner
ffb38f0c03 feat(plugin): add require_mention setting to Buzz adapter
Channel mention-gating was hardcoded on. Add a configurable require_mention
(default True, preserving current behavior). When False, the agent responds to
every message in a watched channel, not only when @mentioned; DMs always
dispatch. Read from config.yaml gateway.platforms.buzz.extra.require_mention
with BUZZ_REQUIRE_MENTION env override, bridged via apply_yaml_config_fn like
the other settings. A leading mention is still stripped when present.

Refs #68871
2026-07-28 17:57:48 -07:00
Rob Zehner
65f52d4913 fix(plugin): strip leading @mention from Buzz channel messages
Channel messages address the agent with a leading @mention (e.g. '@Chip
/whoami'). The adapter passed the raw content through, so the gateway's
is_command() check (text.lstrip().startswith('/')) never matched and slash
commands were routed as plain chat. Strip a leading mention (name, npub, or
hex form) before dispatch in channels, mirroring the Discord adapter. Also
cleans normal prompts ('@Chip what's up?' -> 'what's up?'). DMs are untouched.

Verified live: '@Chip /whoami' -> '/whoami' after connect populates identity.

Refs #68871
2026-07-28 17:57:48 -07:00
Rob Zehner
01f8852ccc fix(plugin): bridge Buzz config.yaml -> env + fix reaction flags
The Buzz adapter's check_requirements() reads config from env only, so a
config.yaml-only setup (relay URL in gateway.platforms.buzz.extra) failed the
check_fn gate and was silently skipped at startup. Add an apply_yaml_config_fn
hook that bridges buzz.extra -> BUZZ_* env vars, mirroring the Slack/Telegram
pattern; BUZZ_PRIVATE_KEY stays a .env secret.

Also fix send_reaction() to use buzz-cli's real flags (--event <id> --emoji),
replacing the non-existent --channel/--message-id flags that would have failed
on every message. Verified live against the hosted relay (accepted:true).

Refs #68871
2026-07-28 17:57:48 -07:00
Rob Zehner
66fc2e2a92 feat(plugin): add Buzz (Block/Nostr) platform adapter
Plugin-path adapter (zero core changes) connecting Hermes to a Buzz
community relay via the buzz CLI binary (JSON in/out, arg-list exec,
key passed via env only). Inbound uses a poll loop with per-channel
high-water marks seeded from newest (no history replay), event-id
de-dupe, self-echo suppression by pubkey, and mention gating in
channels (DMs always dispatch). Registers env_enablement, cron
home-channel delivery, and an out-of-process standalone sender,
mirroring the IRC plugin.

Verified against a live relay: connect -> send -> poll -> MessageEvent
round-trip, self-echo suppressed, clean disconnect.

Known limitation: polled inbound (default 4s); a websocket transport
(buzz-ws-client) is a future optimization.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:57:48 -07:00
Teknium
82ed4dee36 docs(acp): verify Buzz relay-bridge docs against buzz source, cross-link modes, add zh-Hans
Follow-up on the salvaged #69915 commits:
- verified env vars against block/buzz crates/buzz-acp/src/config.rs
  (BUZZ_RELAY_URL, BUZZ_PRIVATE_KEY, BUZZ_API_TOKEN, BUZZ_ACP_AGENT_COMMAND/
  ARGS, BUZZ_ACP_RELAY_OBSERVER, BUZZ_ACP_AGENT_OWNER) and kind 24200
  observer frames against docs/nips/NIP-AO.md
- dropped unverifiable claims: docs/hermes-agent-acp.md link (404 upstream),
  relay-directory profile publication / External-agents card walkthrough,
  'bot role' membership phrasing
- replaced key provisioning with the actual buzz-admin generate-key /
  add-member flow from the buzz-acp README
- retitled the section 'Buzz channels (relay bridge)' and cross-linked it
  with the Buzz Desktop managed-runtime section both ways; permission
  paragraph now points at the owner-only warning instead of duplicating it
- zh-Hans translation of the new section + retitle to ACP 宿主集成
2026-07-28 17:57:37 -07:00
nytemodeonly
c83dadc1d3 docs(acp): add Buzz external-agent activity guidance
Co-authored-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
2026-07-28 17:57:37 -07:00
nytemodeonly
e755f1725f docs(acp): document Buzz host integration
Co-authored-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
2026-07-28 17:57:37 -07:00
Brooklyn Nicholson
a7e3536a70 fix(desktop): align the composer status lane with the surface
The lane is `inset-x-0`, which resolves against the composer root's
padding box, while the surface and the underside strip sit in its
content box — so the pill strip hung 5px further left than both.

Measured: pills 321.83, chip 326.83, surface 326.83.
2026-07-28 19:42:35 -05:00
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!
ded2314910
Merge pull request #73711 from NousResearch/bb/composer-micro-actions
Composer contribution seams: micro-action pills and an underside strip
2026-07-28 19:28:42 -05:00
brooklyn!
6cccef6c10
Merge pull request #73710 from NousResearch/bb/triage-large-remote-attach
fix(desktop): allow large remote attachments (supersedes #66555)
2026-07-28 19:28:01 -05:00
brooklyn!
9b6d91045c
Merge pull request #73704 from NousResearch/bb/slash-skills-by-usage
Rank the slash menu by the skills you actually use
2026-07-28 19:20:49 -05:00
Brooklyn Nicholson
254aeda122 fix(desktop): dedicated reader for large remote attachments
Keep Settings-configurable preview/image loads on readFileDataUrl, and
route remote non-image attach through a 256 MiB IPC so uploads are not
stuck on the 16 MiB default after #73221.

Co-authored-by: Börje <borje@dqsverige.se>
2026-07-28 19:20:14 -05: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
1e355116a1 fix(desktop): keep composer chrome out of the pop-out gesture
The pill strips live inside the composer root, so their box sits within
the drag region and their gaps read as grab area. Exclude anything
marked composer-no-drag, matching how buttons and menu items are already
excluded.
2026-07-28 19:20:07 -05:00
Brooklyn Nicholson
9b01c74f00 feat(desktop): render the micro-action and underside strips
Pills pin to the top of the composer's overlay lane, outside the status
card and outside its scroller, so nothing stacks above them and a long
todo list can't scroll them away. The underside slot sits below the
surface. Both share one grid constant and one parent, so their left
edges match by construction rather than by matching numbers in two
files.

The strips take pointer events while their empty space falls through to
the pop-out drag region, which keeps the composer draggable by the band
its badges live in.
2026-07-28 19:20:00 -05:00
Brooklyn Nicholson
1cefad5523 feat(desktop): composer.microActions + composer.underside contribution areas
Two new seams on the composer, both through the existing contribution
registry: a data area whose providers resolve badge descriptors per
session, and a render area for a chrome-free strip below the surface.
Core registers nothing in either — they stay empty until something
contributes.

Providers resolve from live session context rather than registering
static entries, so a contribution can be conditional without the
reactive when() the registry deliberately doesn't offer.
2026-07-28 19:19:52 -05:00
Brooklyn Nicholson
807dc0c45f feat(desktop): copy and paste in the GUI terminal
xterm paints to a canvas, so its selection is not a DOM selection: the Edit
menu's Copy and the right-click Copy both call webContents.copy(), find
nothing, and copy nothing. On macOS the menu also swallows the Cmd+C
accelerator before the renderer sees it. That left Ctrl+C as the only key that
did anything, and xterm correctly forwards it to the PTY as SIGINT.

Mirror the selection into xterm's hidden helper textarea (the mechanism xterm
already uses for Linux middle-click paste) so the OS sees a real selection and
every platform copy path works, and add explicit chords on top: Cmd+C/Cmd+V on
macOS, Ctrl+Shift+C/V elsewhere, matching VS Code. Plain Ctrl+C copies only
when text is selected -- the behavior Windows Terminal and Tabby ship -- and
stays SIGINT otherwise, so interrupting a process never breaks.

Reads go through a new hermes:readClipboard IPC handler for the same reason
writes already do: the renderer's clipboard API throws whenever the document
isn't focused.
2026-07-28 19:15:04 -05:00
Brooklyn Nicholson
959d2993ab feat(desktop): rank the slash menu by the skills you actually use
The `/` popover listed skills alphabetically, so on a 200-skill install
the ones invoked daily sat below a wall of skills that shipped with
Hermes and were never opened — /research-paper-writing (never used)
outranked /research (60 invocations).

Sort the Skills section most-used first, A-Z within a tie, and on a bare
`/` drop bundled skills with no recorded activity. Typing a query keeps
every match: a search that hides a result is broken, so a typed `/re`
only reorders.
2026-07-28 19:14:46 -05:00
Brooklyn Nicholson
75bf13e032 feat(gateway): report per-skill usage and origin in commands.catalog
The catalog advertises every skill command but nothing about which ones
the user actually reaches for, so consumers can only sort them
alphabetically. Add a `skills` map keyed by slash command carrying the
activity count and origin (hub / bundled / local) already tracked in the
skills sidecars, read once per catalog build.

Additive: older clients ignore the field, and an unreadable sidecar
degrades to zero usage rather than failing the catalog.
2026-07-28 19:14:46 -05:00
hermes-seaeye[bot]
9dc191d437
fmt(js): npm run fix on merge (#73699)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 00:09:37 +00:00
brooklyn!
3d0e543aa7
Merge pull request #73697 from NousResearch/bb/session-list-user-send-bump
fix(desktop): bump session list order on user send
2026-07-28 19:00:32 -05:00
Brooklyn Nicholson
7d5b92cdfb fix(desktop): bump session list order on user send
Recents were hard-sorted by started_at and only refreshed last_active after
turn complete — so reviving an old thread stayed buried until the assistant
finished. Stamp last_active on prompt seed, keep it monotonic across mid-turn
refreshes, and sort agent recents by activity.
2026-07-28 18:53:33 -05:00
brooklyn!
7bfbfa3e34
Merge pull request #73681 from NousResearch/bb/macos-tcc-identity
fix(desktop): stable macOS signing identity so TCC grants survive local rebuilds
2026-07-28 17:41:57 -05:00
brooklyn!
6c2a3b4bb2
Merge pull request #73673 from NousResearch/bb/desktop-eventbus
Desktop: event-driven live sync — gateway change broadcasts replace the always-on polls
2026-07-28 17:39:40 -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
bc1c168d49 docs(desktop): explain macOS TCC permission persistence and the keychain identity opt-in 2026-07-28 17:28:33 -05:00
Brooklyn Nicholson
456818875d fix(installer): route macOS re-sign through the config-aware signing fixup
install.sh duplicated the raw deep ad-hoc re-sign, so install/repair and
self-update could disagree about the app's signing identity — an update
signed with the stable identity would be clobbered back to a cdhash-only
DR by the next installer repair. Call the shared Python fixup (passing
the shell's publisher-signing decision explicitly), and branch into the
historical xattr + deep ad-hoc repair when the venv helper is missing or
fails so a broken venv never leaves the bundle unlaunchable.

Co-authored-by: cipry0200 <cipry0200@users.noreply.github.com>
Co-authored-by: natebransc <natebransc@users.noreply.github.com>
Co-authored-by: caseyanthony <caseyanthony@users.noreply.github.com>
2026-07-28 17:28:33 -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