Commit graph

1887 commits

Author SHA1 Message Date
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
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
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
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 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!
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
fa8fb82d0e test(gateway): change-watcher behavior contracts; align status-snapshot test with 60s cadence
Real temp-HERMES_HOME tests for _broadcast_watched_changes: silent seed,
cron/sessions signature moves, the 2s sessions floor's trailing edge, the
pet signature staying 'off' without a renderable pet, meta payload on
pet.changed, and a broken probe never killing the pass. Part of #73618.
2026-07-28 17:21:58 -05:00
Brooklyn Nicholson
c14ae3796e feat(desktop): fs-watch the plugin dir + demote the status snapshot
- watchDirectory IPC (same registry/channel as the preview file watchers)
  replaces the disk-plugin door's 5s readdir poll; older shells without
  the capability keep the poll, which self-upgrades to the watch once the
  plugins dir exists.
- Status snapshot: 15s → 60s, skips round-trips while hidden, and
  refreshes immediately on visibilitychange so re-focus never shows stale
  health.

Part of #73618.
2026-07-28 17:21:58 -05:00
Brooklyn Nicholson
fe64cafa33 feat(desktop): event-driven live sync — change broadcasts replace the always-on polls
One controller (store/live-sync.ts, the workspace-events twin for gateway
state): gateway-event.ts routes pet.changed / cron.changed /
sessions.changed into tick atoms, gated on the active profile like
skin.changed; gateway.ready's change_events flag arms them; a gateway wipe
resets the capability.

Consumers subscribe to ticks and demote their polls to slow backstops
(legacy cadence is kept verbatim against older backends):

- session.active_list 1.5s → sessions.changed + 30s backstop
- cron list 30s → cron.changed + 5min backstop
- messaging lists 10s → trailing sessions.changed refresh + legacy poll
  only when events are unavailable
- open messaging transcript 5s → sessions.changed + 30s backstop
- pet.info 3s/15s → pet.changed, NO timer on event-capable backends
  (users with no pet used to poll hardest); enabled=false broadcasts
  clear the mascot with zero round-trips
- cron runs page/peek 8s → cron.changed + 60s backstop

Idle renderer traffic drops from ~89 req/min to the backstops (~5/min).
Part of #73618.
2026-07-28 17:21:58 -05:00
brooklyn!
43ddefd08f
Merge pull request #73675 from NousResearch/bb/edit-models-hover
fix(desktop): match Edit Models row hover to the model picker
2026-07-28 17:19:44 -05:00
brooklyn!
f9d7bca252
fix(desktop): overlay scrollbars on conversation code blocks (#73670)
The app-wide `.scrollbar-dt *` webkit rules force classic always-on
gutters on every descendant scroller. Opt code-card scroll surfaces out
via `.scrollbar-overlay` so Electron keeps platform overlay bars (fade
in on scroll, no reserved track).
2026-07-28 22:13:54 +00:00
Brooklyn Nicholson
6d83a79f93 fix(desktop): match Edit Models row hover to the model picker 2026-07-28 17:13:20 -05:00
brooklyn!
ed5fd3503d
fix(desktop): clear surface height vars from the attached surface, not a detached node (#73638)
When the status stack collapses (last subagent/background/queue item
finishes), React removes the stack div before the layout-effect cleanup
runs. Resolving the surface via closest('[data-chat-surface]') from that
now-detached node falls back to the document root, so the stale measured
height was cleared from the wrong element and stayed on the surface —
inflating --thread-last-message-clearance (the thread's bottom padding)
until the next publish. Same latent hazard in the composer's unmount
cleanup.

Capture the surface root while the node is still attached and clear from
it directly.
2026-07-28 17:07:22 -05:00
hermes-seaeye[bot]
66e20786d0
fmt(js): npm run fix on merge (#73666)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 21:55:38 +00:00
Teknium
03af12c5c9 style(desktop): blank line between node: and vitest import groups
perfectionist/sort-imports requires spacing between builtin and
external import groups.
2026-07-28 14:47:36 -07:00
Teknium
20e0b26c4e test(desktop): run venv-blocker-scan tests under vitest
CI's electron vitest project collects electron/**/*.test.ts; the
node:test-based suite reported 'No test suite found'. Import
describe/it from vitest like sibling electron tests.
2026-07-28 14:47:36 -07:00
Teknium
1dd1c449bb fix(desktop): run venv-blocker scan async off the main-process event loop
The preflight's execFileSync froze the Electron UI event loop for up to
15s while psutil scanned the full process table. Convert
scanVenvBlockers to async execFile and await it in applyUpdates; tests
updated to async DI stubs. Also adds trailing newlines.
2026-07-28 14:47:36 -07: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
atakan g
fa9217542d fix(desktop): preserve local model provider endpoint 2026-07-28 14:17:27 -07:00
Brooklyn Nicholson
aa0465a309 fix(desktop): clear surface height vars from the attached surface, not a detached node
When the status stack collapses (last subagent/background/queue item
finishes), React removes the stack div before the layout-effect cleanup
runs. Resolving the surface via closest('[data-chat-surface]') from that
now-detached node falls back to the document root, so the stale measured
height was cleared from the wrong element and stayed on the surface —
inflating --thread-last-message-clearance (the thread's bottom padding)
until the next publish. Same latent hazard in the composer's unmount
cleanup.

Capture the surface root while the node is still attached and clear from
it directly.
2026-07-28 15:34:15 -05:00
brooklyn!
c8cdeb435f
fix(desktop): allow renderer camera capture (#73558)
* fix(desktop): allow camera capture through the permission handlers

The session permission hooks were written for the voice composer and denied
video outright, so any renderer getUserMedia({video}) failed with
NotAllowedError before the OS was ever consulted.

Rename isAudioCapturePermission to isMediaCapturePermission and accept video
alongside audio in both the request and check handlers. The OS capture
permission still applies, so the user keeps a real allow/deny.

* build(desktop): declare camera usage for signed macOS builds

A hardened-runtime build needs the camera entitlement and an
NSCameraUsageDescription string, or the packaged app is killed on first
camera access instead of prompting.
2026-07-28 14:50:52 -05:00
atakan g
89490ae373 fix(desktop): honor gitignore across Windows path casing 2026-07-28 12:36:35 -07:00
Atakan
15317e1fd7 fix(desktop): upload Windows attachments to Linux backends 2026-07-28 12:36:01 -07:00
atakan g
d4221b2736 fix(desktop): validate WebSocket token before backend ready 2026-07-28 12:35:57 -07:00
Teknium
43e1b7f474 style: eslint padding fix in profile-scope test (salvage follow-up) 2026-07-28 11:58:03 -07:00
Teknium
e27997d63f fix(desktop): route streaming TTS through the active profile backend
The /api/audio/speak-stream WebSocket URL was always minted for the primary
backend with no profile param, so streaming read-aloud used the default
profile's TTS provider even when another profile was active. Mint the
ticket for the active profile's connection and carry ?profile= (same seam
as /api/pty), via a read-only getApiRequestProfile() accessor.
2026-07-28 11:58:03 -07:00
Tom Brautlacht
c95f04501d fix(desktop): skip markdown tables in speech output 2026-07-28 11:58:03 -07:00
HarpStar
f02d41cb21 fix(desktop): summarize code blocks for read aloud 2026-07-28 11:58:03 -07:00
webtecnica
7c398a5c8e fix(desktop): prevent voice playback from auto-starting next sentence after stop (#70955) 2026-07-28 11:58:03 -07:00
Max Hsu
181511473e fix(desktop): render video/audio image-markdown as media, not broken <img>
Generated media often arrives as image markdown (![clip](clip.mp4)). The chat
markdown renderer maps the img element to MarkdownImage, which renders a raw
<img>; for a video/audio source the browser cannot paint it and shows a
broken-image icon even though the file is valid and plays fine. Images worked
only because <img src=...png> is valid markup.

Route video/audio sources in MarkdownImage to the existing MediaAttachment,
which already picks the correct <video>/<audio> element (streaming protocol +
open-externally fallback) by media kind. The intended MEDIA:-tag -> #media:
link path is unaffected; this only fixes the bare image-markdown case.

Since #57944 the image path is built on hooks (async src resolution, failure
and loading states), so the routing check cannot be a plain early return
inside it -- it would have to sit after every hook call and would still fire
an image resolve for media never rendered as an image. MarkdownImage is
therefore a thin hookless router in front of MarkdownImageContent, which is
the previous body unchanged.

The regression tests join the existing markdown-text.media.test.tsx added by
#57944 rather than replacing it.

Fixes #40896

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vU45SEvcxLkVeyyEE9gJ2
2026-07-28 11:58:03 -07:00
Ares4Tech
3e47efeb25 fix(desktop): stream remote media through gateway 2026-07-28 11:58:03 -07:00
Bryan Roller
3a1efa5e30 [verified] fix(desktop): scope TTS requests to active profile 2026-07-28 11:58:03 -07:00
Bartok9
a2b0c314ae fix(desktop): scope audio endpoints to the active profile
transcribeAudio/speakText/getElevenLabsVoices called the backend without
profileScoped(), so for a non-default profile they hit the default backend
config instead of the active profile - playback used the wrong TTS/voice
even though settings were saved to the active profile config.

Fixes #53441
2026-07-28 11:58:03 -07:00
Brooklyn Nicholson
eaf30a4de7 build(desktop): declare camera usage for signed macOS builds
A hardened-runtime build needs the camera entitlement and an
NSCameraUsageDescription string, or the packaged app is killed on first
camera access instead of prompting.
2026-07-28 13:40:44 -05:00
Brooklyn Nicholson
47cb4ea1fe fix(desktop): allow camera capture through the permission handlers
The session permission hooks were written for the voice composer and denied
video outright, so any renderer getUserMedia({video}) failed with
NotAllowedError before the OS was ever consulted.

Rename isAudioCapturePermission to isMediaCapturePermission and accept video
alongside audio in both the request and check handlers. The OS capture
permission still applies, so the user keeps a real allow/deny.
2026-07-28 13:40:44 -05:00
hermes-seaeye[bot]
9cf1227d4e
fmt(js): npm run fix on merge (#73555)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 18:38:33 +00:00
hermes-seaeye[bot]
2faac36866
fmt(js): npm run fix on merge (#73552)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 18:30:19 +00: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
Teknium
071e2adede test+i18n: follow-up for salvaged PR #68969
- Extract the cmdk filter into exported rankSearchOption and cover it,
  SearchableSelect selection/clear/placeholder, and ConfigField
  searchable-schema routing with 12 vitest cases (sabotage-verified:
  the backend schema test fails on unfixed main).
- Add searchPlaceholder/noResults/systemDefault strings to ja/ar/zh-hant
  (zh + en came with the salvaged commits; defineLocale would have
  fallen back to English otherwise).
- Add a backend invariant test: timezone ships as a searchable,
  clearable select of sorted IANA ids with a UTC fallback.
2026-07-28 10:49:14 -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
b5b3ed6563 fix: address cross-vendor review feedback
- Fix focus: use autoFocus on CommandInput instead of e.preventDefault()
- Fix filter: prioritize city segment match (return 2 for slash match)
- Fix handleSelect: always select, don't toggle-deselect
- Add aria-haspopup to trigger button
- Use defensive placeholder logic
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
Adolanium
b5dc471152
feat(desktop): make attachment data-URL size limit configurable (#73221)
Hard 16 MB cap on readFileDataUrl blocked larger local attaches with no way to raise it. Settings -> Chat now has a free-form MB field. Main process owns the persisted value and clamps only absurd inputs.
2026-07-28 09:10:21 -04:00
brooklyn!
30526baab5
Merge pull request #73262 from NousResearch/bb/dedupe-main-tile
fix(desktop): drop a session's tile when it loads into the main tab
2026-07-28 04:23:02 -05:00