Commit graph

1929 commits

Author SHA1 Message Date
Teknium
d4ff566232 fix(model-set): persist base_url/api_key on auxiliary slot assignments
Sibling of #65254 (main-slot endpoint preservation): the auxiliary scope of
POST /api/model/set dropped the request's base_url/api_key on the floor, so
an aux slot pinned to a custom/local endpoint silently depended on
model.base_url — and broke the moment the main slot switched away and
cleared it. The aux resolver already reads auxiliary.<task>.base_url/api_key
(_resolve_task_provider_model); this persists them.

Desktop side: setAuxiliaryToMain / applyAuxiliaryDraft now carry the
user-defined provider's api_url as base_url, mirroring applyMainModel.
2026-07-28 21:45:44 -07:00
Teknium
0f64557c06 fix(wake): coerce dead onnx->tflite on macOS ARM64; clear stale voice turn-timeout
Two follow-ups from the voice PR (#70509).

1. macOS ARM64 onnx migration. Existing users who pinned
   openwakeword.inference_framework=onnx before the tflite fix landed kept a
   wake word that arms but never fires (ONNX's embedding model is broken on
   Apple Silicon, upstream #336). New resolve_inference_framework() honors an
   explicit framework everywhere ONNX actually works, but coerces the one
   provably-dead combination (explicit onnx + macOS ARM64) to tflite with a
   one-time warning. No config mutation; empty still falls back to the platform
   default. Both read sites (engine init + requirements check) route through
   the shared resolver.

2. Voice turn-timeout leak. Each listen cycle reassigned turnTimeoutRef
   without clearing the prior 60s timer, so a stale timer from an earlier
   cycle could fire handleTurn() mid-way through a later listen — after enough
   idle re-listens this wedged the loop into a non-re-arming state (the
   'voice chat deactivates after ~a minute' report). Clear before re-arm.

Tests: 64 wake tests (added onnx-coercion / intel-kept / tflite-kept /
empty-default cases; updated the stale 'explicit onnx kept on ARM64' test
that encoded the old broken behavior), 39 desktop voice/wake vitest, tsc +
eslint clean.
2026-07-28 19:40:25 -07:00
brooklyn!
533d633ab9
Merge pull request #73774 from NousResearch/bb/desktop-messaging-icon-churn
perf(desktop): memoize PlatformAvatar + StatusDot (messaging icon churn)
2026-07-28 21:16:19 -05:00
hermes-seaeye[bot]
8a342a0002
fmt(js): npm run fix on merge (#73780)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 02:10:16 +00:00
brooklyn!
40a53ca031
Merge pull request #73698 from NousResearch/bb/desktop-overlay-perf
perf(desktop): kill sidebar + overlay render churn from hot store subscriptions
2026-07-28 21:01:26 -05:00
Brooklyn Nicholson
d76c7f5409 perf(desktop): memoize PlatformAvatar + StatusDot to kill messaging churn
The sidebar's messaging section renders one PlatformAvatar (labelIcon) and
StatusDot per platform group. The sidebar re-renders on every streaming tick
($sessions/$workingSessionIds/$messagingSessions churn), and both leaves were
unmemoized — so every platform's avatar + dot re-rendered on each delta even
though their props (platformId/tone/className) never changed.

- PlatformAvatar: memo(forwardRef(...)) — pure fn of platformId/name/class/style
- StatusDot: memo() — pure fn of tone/class

Measured (Messaging open, settled, 2 sessions streaming, 3s window):
  before: 96 wasted (PlatformAvatar 32, StatusDot 32, + brand icons)
  after:  0 wasted

Both are shared primitives; the memo also helps every other consumer
(session rows, gateway menu, session tiles) that renders them under a hot
parent.
2026-07-28 20:51:04 -05:00
hermes-seaeye[bot]
ada389004f
fmt(js): npm run fix on merge (#73770)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 01:49:23 +00:00
Brooklyn Nicholson
50d0de0358 fix(desktop): satisfy lint — import order, unused import, exhaustive-deps
- command-center/command-palette/skills: import ordering + drop unused
  HermesGateway type
- settings: wrap openSubView/openProviderView/openKeysView in useCallback so
  the navGroups memo deps are honest and stable
- command-center: add setSection to navGroups memo deps
2026-07-28 20:38:04 -05:00
Brooklyn Nicholson
64bdbd7f3c fix(desktop): stop message age falling back to the 1970 epoch ("20663d ago") 2026-07-28 20:33:25 -05:00
Brooklyn Nicholson
4acc162712 chore: overlay churn measurement probes (sweep + median A/B) 2026-07-28 20:26:58 -05:00
Brooklyn Nicholson
6fc01f8ab8 perf(desktop): memoize sidebar rows + status dots + artifact cells
The sidebar stays mounted beneath every overlay/page, and it subscribes to
$sessions + $workingSessionIds — both tick on every streaming token. An
unmemoized SidebarSessionRow re-rendered the whole list (Codicon, labels,
status dots) on each delta, and that churn bled into every overlay opened on
top: Cron, Profiles, Agents, Starmap, Webhooks, Command Center, Settings.

- SidebarSessionRow: memo() with a custom comparator that ignores the pure
  id-forwarding callbacks (fresh closures by design) and compares only the
  data that changes what the row paints. Rows bail out while siblings stream.
- SessionStatusDot: the 5 $...SessionIds arrays now read via useStoreSelector
  returning this session's boolean, so a dot repaints only when ITS OWN
  membership flips, not on every array tick.
- Artifacts: stable cellCtx (useMemo) + memoized Primary/Location/Session
  cells so a link-title fetch on one row stops re-rendering the whole table.

Measured before/after (2s idle, sessions streaming), sidebar-fed overlays:
  Cron 407->~30 wasted, Profiles 732->~80, Agents 132->9, Starmap 188->56,
  Webhooks 154->22.
2026-07-28 20:23:10 -05:00
Teknium
9044c4b836 style: eslint perfectionist import order in session-source test 2026-07-28 18:19:12 -07:00
Bounty13
f2954945be test(desktop): add regression test for Photon messaging source (#46761)
Assert isMessagingSource('photon') is true and that Photon/iMessage search aliases resolve, so a silent removal of the photon entry from MESSAGING_SESSION_SOURCE_IDS fails CI instead of regressing the sidebar section added in #47395.
2026-07-28 18:19:12 -07:00
Bounty13
560be7dbf2 feat(desktop): add Photon iMessage section to sidebar
Register Photon as a messaging platform in the Desktop sidebar:
- Add Photon brand icon (three-bar mark) to PlatformAvatar catalog
- Add photon to SOURCE_LABELS, SOURCE_ALIASES, and MESSAGING_SESSION_SOURCE_IDS

Closes #46761
2026-07-28 18:19:12 -07:00
Brooklyn Nicholson
efe76378c2 chore: full overlay render-churn measurement probe 2026-07-28 20:04:21 -05: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 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
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
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
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
1073ae2b46 chore: CDP probe for overlay render-churn measurement 2026-07-28 19:03:17 -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 Nicholson
d65b226497 perf(desktop): gate hot store subscriptions + memoize overlay nav + palette rows
Command Center, Settings, Skills, and Command Palette all subscribed to
hot stores unconditionally —  ticks on every streaming token (title
updates),  on every connect/disconnect. Components that only need
that data on one tab were re-rendering on every stream delta while sitting
on an unrelated tab.

Fixes:
- CommandCenterView: gate / to the Sessions tab
  via useStoreSelector returning a stable empty array on other tabs
- SkillsView: gate  to the MCP tab only
- SettingsView: memoize navGroups (was rebuilt inline on every render with
  fresh onSelect closures for every nav item)
- OverlayNavItem: memo() so nav items don't re-render when a sibling's
  active state changes
- CommandPalette: extract memoized PaletteRow so items don't re-render on
  unrelated parent state changes (open/close, theme, etc)
- CommandCenterView: memoize navGroups array (was inline JSX)
2026-07-28 18:51:34 -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
Teknium
f5ff8fa4f8
Merge remote-tracking branch 'origin/main' into wake-toggle-config 2026-07-28 13:24:23 -07: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
Teknium
0cf58de85e
Merge remote-tracking branch 'origin/main' into wake-toggle-config
# Conflicts:
#	tests/test_tui_gateway_server.py
#	tui_gateway/server.py
2026-07-28 12:37:35 -07:00