Commit Graph

2524 Commits

Author SHA1 Message Date
Teknium 9d6c5a920c fix(desktop): collapse Fireworks behind the provider disclosure on first run
The first-run provider picker showed Fireworks AI alongside Nous Portal
before the user opened the 'Other providers' disclosure. Only Nous Portal
should be visible up front; Fireworks now lives inside the expanded list
but keeps its #1 position there (Nous -> Fireworks ordering preserved).
2026-08-10 21:09:27 -07:00
Brooklyn Nicholson 1e6a7b3315 fix(desktop): scope custom provider settings to the active profile
The custom-endpoint REST handlers ran bare load_config/save_config, so
every add/activate/delete landed in the process-level default profile
regardless of which profile the desktop settings UI was targeting. A
provider added under a non-default profile silently went to default:
visible only in default-bound sessions, absent everywhere else, and
un-addable to another profile without hand-editing its config.yaml.

Scope all four handlers (list/upsert/activate/delete) to the requested
profile via _config_profile_scope, matching /api/config, and spread the
active profile into the four hermes.ts wrappers alongside their existing
validateCustomEndpoint sibling.
2026-08-10 22:26:54 -05:00
hermes-seaeye[bot] 6a20383eeb
fmt(js): `npm run fix` on merge (#83609)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-11 02:09:04 +00:00
Teknium 0a60b164f5 fix(desktop): single-owner console capture + HUD lifecycle coverage
Reconcile the salvaged #81533 lifecycle helper with the renderer-log
console pipeline that landed in #83535 (the two PRs raced):

- window-renderer-lifecycle.ts no longer handles console-message —
  renderer-log.ts is the single owner (per-window labels, boundary
  reports). One owner means no double-logged errors on windows wearing
  both, and OAuth/portal windows (lifecycle-wired for process events)
  cannot spill third-party page console output into desktop.log.
- wake indicator window gets attachRendererConsoleCapture, keeping the
  console coverage it previously got from the helper.
- HUD window (added after the PR branched) gets log-only lifecycle
  coverage — it was the one renderer window the PR couldn't have known
  about.
- Tests updated: lifecycle helper asserts it attaches NO console-message
  listener; parser tests live in renderer-log.test.ts.
2026-08-10 19:00:19 -07:00
Chen Jin 0c1a11ada6 fix(desktop): correct import order for window-renderer-lifecycle before window-reveal (#81290)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 19:00:19 -07:00
Chen Jin d966393814 fix(desktop): wire renderer-lifecycle diagnostics into OAuth and portal login windows (#81290 follow-up)
@spfcraze's triage review noted the PR description claimed "every
BrowserWindow" but the OAuth and portal sign-in windows were not wired:
a crashed sign-in renderer leaves the window's promise path never
settling, with no trace in desktop.log.

Wire both with the same log-only lifecycle diagnostics as the overlay
and quick windows — `kind: 'oauth'` and `kind: 'portal'` respectively.
Neither window gets crash-reload treatment (a sign-in window that
reloads itself mid-auth would be surprising); the lifecycle helper's
log-only callback is the exact contract needed here.

window-renderer-lifecycle.test.ts: 17/17 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 19:00:19 -07:00
Chen Jin 11f920b812 fix(desktop): attach renderer-lifecycle diagnostics to all BrowserWindow instances (#81290) 2026-08-10 19:00:19 -07:00
a-espinoza 91de3beb72 fix(desktop-ssh): raise remote backend file limit 2026-08-10 18:04:59 -07:00
hermes-seaeye[bot] a1bfbccc02
fmt(js): `npm run fix` on merge (#83539)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-11 00:20:02 +00:00
Teknium e5e2fb8b2d fix(desktop): persist renderer crashes to desktop.log + finish the render() isolation class
Three-part class closure for the React #310 / lost-renderer-crash family
(#79428, follow-up to #80560 / #82763):

1. Diagnosability (#79428 defect B): error-boundary catches now persist to
   desktop.log with their component stack via a new fire-and-forget
   hermes:logs:renderer-error IPC (synchronous flush — the window may be
   dying). Every renderer-content window (main, secondary session, instance,
   HUD, quick entry, pet overlay) gets the error-level console capture that
   previously only the main window had, labeled per window. 'Open logs' on
   the crash dialog now reveals a file that actually contains the crash.

2. Recurrence guard: eslint no-restricted-syntax rule banning inline
   render() calls in JSX — the mechanism behind #80560. The rule
   immediately caught two live sites #82763's audit missed (floating
   panes, narrow-overlay reveal), both hosting plugin panes.

3. Fix those two missed sites with the same ContribRender mount.

extracted console-capture/report formatting to electron/renderer-log.ts
with unit tests; renderer console lines now carry the window label.
2026-08-10 17:10:53 -07:00
XiaoZAZA a9a0648f49 fix(desktop): reap orphaned serve backends via parent-death watchdog + group-kill
An unclean desktop exit (crash / SIGKILL / update handoff) stranded every
`hermes serve` profile backend as an orphan (ppid=1) still serving, each
holding its MCP child subtree — 31 orphans / ~1.3 GiB RSS on one install.

Root causes + fixes:
- serve had no parent-death watchdog: add _start_parent_death_watchdog() in
  web_server.py (mirrors slash_worker.py), gated on HERMES_PARENT_PID; os._exit
  cascades to MCP watchdogs. No-op for standalone `hermes serve`.
- desktop passes HERMES_PARENT_PID in both serve spawn env blocks (main.ts).
- POSIX teardown now group-kills (process.kill(-pid, ...)) so MCP grandchildren
  die too (backend-child.ts + waitForBackendExit SIGKILL fallback).

Windows path unchanged (forceKillProcessTree). Tests updated + passing.
2026-08-10 17:02:56 -07:00
Brooklyn Nicholson 9e7700e8a3 fix(desktop): title HUD windows Hermes HUD
The floating HUD inherited the default Hermes title from index.html.
Set it explicitly in main and the renderer so the OS window label
matches the mode.
2026-08-10 16:19:50 -05:00
brooklyn! 920beecfa8
perf(desktop): long streaming agent sessions — steady window cut, stable rows, stepped backfill, pane-shared budget (#83446)
* test(desktop): stress long agent sessions in the multitab perf scenario

--tools seeds every transcript with settled tool rounds and drives the live
stream as a working agent turn (tool calls opened and completed between text
chunks), and each tile reveal is timed to next paint (reveal_max_ms) so deep
transcripts report their mount cost.

* perf(desktop): hold the transcript window cut steady while streaming

A fresh weight-walk per store flush slid the cut forward one message at a
time, ~30x/s, and every slide re-indexed the whole windowed transcript —
each row rendered a different message and the runtime repository took its
O(window) rebuild path instead of the one-message update. advanceTranscriptWindow
anchors the cut to a message id and re-cuts once per ~half page of new
content instead of once per flush.

* perf(desktop): stable rows, stepped backfill, pane-shared render budget

Three thread-list fixes for long streaming sessions: memoize the visible-
groups slice and each turn row so a budget-cut advance no longer re-renders
every mounted turn per streamed token; raise the first-paint backfill in
BACKFILL_STEP slices (one bounded commit per frame) instead of a single
20-to-600 transition whose commit landed as a 780ms freeze mid-stream; and
share RENDER_BUDGET across mounted panes so a 4-way grid mounts a quarter
page per pane instead of 4x the fibers.
2026-08-10 21:06:59 +00:00
hermes-seaeye[bot] 1b5da4aacf
fmt(js): `npm run fix` on merge (#83409)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 19:40:25 +00:00
embwl0x 8edcdd1a80 fix(desktop): isolate plugin render hooks 2026-08-10 10:57:35 -07:00
hermes-seaeye[bot] 03fa32c92d
fmt(js): `npm run fix` on merge (#83143)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 11:07:34 +00:00
brooklyn! 6735e6a10a
Merge pull request #83138 from NousResearch/bb/titlebar-tahoe-fullscreen
fix(desktop): skip titlebar Y nudge on Tahoe and macOS fullscreen
2026-08-10 05:56:55 -05:00
hermes-seaeye[bot] 86d747e00d
fmt(js): `npm run fix` on merge (#83139)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 10:55:40 +00:00
Brooklyn Nicholson 4ed6f4f7f1 fix(desktop): satisfy no-extra-boolean-cast in fullscreen guard 2026-08-10 05:50:10 -05:00
brooklyn! 3139a30e52
perf(desktop): multi-tile grids stop lagging — evict leaked session states, index lineage aliases, split the turn journal (#83133)
* fix(desktop): evict settled session states nothing on screen references

Closing a tile never removed its runtime's entry from $sessionStates, so
every tile ever closed parked its full transcript in the map for the life
of the process. Each leftover entry taxes every subsequent stream flush —
the map is spread-copied per delta and the busy/attention/draft projections
walk every entry per publish — so the app got slower the longer it ran,
which users read as "I need to clean my sessions/dbs".

Publish now evicts a settling state when no tile and not the primary view
holds its runtime (transition side effects still fire, so the settle keeps
its unread dot), and closing a tile drops an already-settled state on the
spot. Busy and needs-input states stay: background turns feed the sidebar
dots, and a first publish always lands because a resume can publish a beat
before the surface binds the runtime.

16 tiles streaming in a 2x2 grid with a day's worth of closed-tile residue:
worst-second 34 -> 58 fps, p99 frame 90 -> 28 ms, longtasks 37 -> 0.

* perf(desktop): index lineage aliases per sessions-list reference

lineageAliases scanned the whole recents list per call, and it is called
per cached session state per status projection per message delta — with a
populated sessions DB and a few busy sessions that multiplied out to
millions of row checks a second during streaming. Build the alias index
once per list reference (the list is replaced wholesale, never mutated)
and look aliases up in O(1).

* perf(desktop): journal each in-flight turn under its own storage key

The v1 journal kept every session's tail in one localStorage key, so each
throttled write re-parsed and re-stringified EVERY busy session's snapshot
— a grid of concurrent streams turned that into a whole-store JSON round
trip dozens of times a second, all on the main thread. Per-session keys
make a write O(own tail) no matter how many other sessions are streaming.
A v1 store migrates on first touch; expired/overflow crash residue is
pruned once per renderer.

* perf(desktop): stress the multitab scenario across grid/streaming/DB axes

The one-stack multitab run hid every cost this round of fixes removed: it
drove hook.publish (store only — no journal, no wiring cache), with an
empty recents list and no closed-tile residue. Streaming now routes through
hook.update (the real gateway write path), and the scenario grows axes for
the workloads users actually hit: --zones splits tiles across visible grid
zones, --streaming caps how many sessions are mid-turn (zone leaders
first), --sessions seeds a lived-in recents list, --dead models settled
sessions no surface references. launch.mjs pins HERMES_DESKTOP_CDP_PORT so
a non-default --port survives the app's own dev-CDP flag.
2026-08-10 10:45:43 +00:00
Brooklyn Nicholson 4f9d345956 fix(desktop): skip titlebar Y nudge on Tahoe and macOS fullscreen
Tahoe already aligns traffic lights without the optical translate. In
fullscreen, drop windowButtonPosition in the main process and clear the
right-cluster inset so traffic-light dodge chrome goes away on both sides.
2026-08-10 05:45:29 -05:00
hermes-seaeye[bot] c08b086013
fmt(js): `npm run fix` on merge (#83132)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 10:44:00 +00:00
Brooklyn Nicholson 422b256939 fix(desktop): sort hud snap imports for eslint 2026-08-10 05:26:38 -05:00
Brooklyn Nicholson 440e6fb640 fix(desktop): list HUD snap chord in keyboard shortcuts panel
Document ⌘⇧G as a read-only global shortcut active while HUD mode is up.
2026-08-10 05:21:07 -05:00
Brooklyn Nicholson 9c75e4863f feat(desktop): snap HUD to cursor with global ⌘⇧G
Register CommandOrControl+Shift+G in main while HUD mode is open so the
floating bar can jump under the pointer from any app. Tap-to-snap only —
Electron globalShortcut has no keyup for hold-to-follow.
2026-08-10 05:21:05 -05:00
brooklyn! 5a3920b734
Merge pull request #83115 from NousResearch/bb/browser-pane-resize
fix(desktop): in-app browser kept squishing the chat and jamming its resize sash
2026-08-10 05:07:01 -05:00
brooklyn! 75dad8b15c
fix(desktop): stop HUD window growing on drag; add corner resize handle (#83091)
* fix(desktop): stop HUD window growing on drag; add corner resize handle

The HUD window is created frame:false + transparent:true + resizable:true.
On Windows, a transparent frameless window silently grows ~1px per
setPosition call (worse at >100% DPI scaling) — every drag of the composer
bar accumulated size drift, and the HUD could end up enormous (reported at
1385x1052 against a 620x320 default). Reading the size back mid-drag
compounds the drift because getSize() returns the already-drifted value.

Fix, mirroring the pet overlay's pattern:
- create the HUD window non-resizable (no system edge resize hot-zone)
- moveBy uses setBounds with a size snapshotted on the first move of each
  drag, so the OS can never accumulate drift (verified: 500 moveBy calls
  with zero size change on Electron 40 / Win11 / 175% DPI)
- add a bottom-right corner resize handle (resize-handle.ts) driving a new
  hermes:hud:set-bounds IPC that flips resizable on for the call, restoring
  the ability to resize a window that is otherwise non-resizable

* fix(desktop): pin HUD drag size in renderer, not main-process globals

The superseding pass drops hudDragWidth/hudDragHeight from main: composer
drag snapshots outerWidth/outerHeight when the hold arms (pet overlay
pattern) and passes them on every moveBy. Adds one test for that contract.

Supersedes #82455.

Co-authored-by: Ringo6107 <199014580+Ringo6107@users.noreply.github.com>

* fix(desktop): keep the HUD solid through a corner resize; drop dead handle state

The resize handle's `resizing` flag only fed a CSS rule that restated the
cursor it already had, so nothing pinned the window mid-gesture: click-through
hands the mouse away the moment the growing edge outruns the cursor. Raise the
composer drag's existing `data-hud-grabbing` instead — one flag for "a gesture
owns the window" — and cover it in click-through's tests.

Also drops the hook's always-true `enabled` param and routes teardown through a
`reset` callback, matching composer-drag.ts and clearing the atom-mirrored-ref
lint rule.

---------

Co-authored-by: Ringo6107 <199014580+Ringo6107@users.noreply.github.com>
2026-08-10 10:05:14 +00:00
Brooklyn Nicholson 33ac4bce56 fix(desktop): satisfy eslint on pane-share-memory test 2026-08-10 04:59:26 -05:00
Brooklyn Nicholson 92ed8be923 fix(desktop): reopen docked tiles at their last split share 2026-08-10 04:52:45 -05:00
Brooklyn Nicholson d67337ee1a fix(desktop): keep min-width floors on stacked flex zones 2026-08-10 04:52:44 -05:00
Brooklyn Nicholson 7267adafdf fix(desktop): don't let webview guests swallow drag gestures 2026-08-10 04:52:19 -05:00
hermes-seaeye[bot] 54e33c95a6
fmt(js): `npm run fix` on merge (#83099)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 09:47:13 +00:00
Brooklyn Nicholson a843bcc940 fix(desktop): sort titlebar import for eslint 2026-08-10 04:30:58 -05:00
Brooklyn Nicholson d1f2e32b13 fix(desktop): titlebar clusters — macOS Y nudge, 24px targets, 13.9px icons
Left cluster gets a macOS-only translate to sit on the traffic-light row.
All titlebar tools use 24×24 hit areas with 13.9px Codicons (inline size
beats unlayered codicon.css). Clusters share one flex shell with no gap —
buttons abut and the hit target is the spacing.
2026-08-10 04:24:42 -05:00
hermes-seaeye[bot] 92dd230c8c
fmt(js): `npm run fix` on merge (#83078)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 09:07:07 +00:00
brooklyn! b278dcb2d3
Merge pull request #83052 from NousResearch/bb/sidebar-all-profiles
Sidebar: show every profile at once
2026-08-10 03:57:47 -05:00
Brooklyn Nicholson 7e1f4f6f36 fix(desktop): ship the sidebar grouped by date in every scope
The all-profiles scope defaulted to grouping by profile, so "Reset to defaults"
handed back a grouping the user never picked. Both scopes now ship by date, and
a reset clears the scope you are not looking at too — otherwise flipping the
rail restored the customization the reset was supposed to undo.

Hovering a row's PR chip also holds the kebab back now: the chip is a link, and
the button that covers the end of the trailing slot was taking the click.
2026-08-10 03:50:34 -05:00
Brooklyn Nicholson 8de786c7a7 fix(desktop): give every row's trailing metadata one right-aligned slot
The PR and profile chips rendered in the row body, left of the kebab's own
column: they never sat flush right and never handed their space to the kebab
on hover, so a row showing only a PR left a hole where the age would have been.
Both now join the tokens/cost/age figures in the actions slot, and the kebab
covers the end of it — losing whichever item reads last, not the whole slot.
2026-08-10 03:35:57 -05:00
Brooklyn Nicholson 6c371e944c feat(desktop): fade the sidebar's scrollbars out until you're in the list
A thumb parked on a list you aren't touching is chrome, not information,
and the sidebar stacks several scrollers so it draws several of them at
once. Fade them in on hover instead, sharing the existing scrollbar
colors and the webkit/Firefox split rather than styling a second kind of
bar. Only the thumb's color changes, so the reserved gutter still keeps
rows from shifting sideways.
2026-08-10 03:24:08 -05:00
Brooklyn Nicholson 771b214516 feat(desktop): show every profile's sessions in the sidebar
All-profiles mode listed a flat page of chats and stopped there: the
project tree was the active profile's, grouping and filtering had no
notion of an owner, and each profile lane paged itself against a
separate endpoint. Multi-agent workflows live across profiles, so the
sidebar now treats the owner as a first-class axis.

Group by profile (the default in this scope, with its own persisted
choice so flipping the rail doesn't reset how you read one profile),
filter by profile, and start or import one from the same menu. Profile
groups take the project row's shape rather than a hand-rolled header,
preview the same three sessions a project does, and carry their whole
tokens-and-spend total in the slot the kebab hovers over.

Grouped lanes now rank by the active sort key, before they trim
themselves, so the rows a group hides are the ones the sort ranked last.

Defaults live in one const: the sidebar ships grouped by date, sorted by
recency, with the timestamp pinned — and "Reset to defaults" puts back
exactly that.
2026-08-10 03:13:08 -05:00
Teknium 7e1bfeab88 fix(desktop): read-only keyless plugin rows + backend contract v6
Rework of the salvaged #82828 compatibility layer: keep the crash guards
(optional key, safe filter/search, synthetic React row identity) but drop
the name-addressed toggle fallback — bare names collide across category
dirs (image_gen/fal vs video_gen/fal), which is exactly why the backend
moved to key-addressed toggles (a60b492e07). Keyless rows from a
pre-contract backend now render with a disabled switch and an 'update
your backend' tooltip instead of resurrecting the collision-prone
protocol.

Bump DESKTOP_BACKEND_CONTRACT / REQUIRED_BACKEND_CONTRACT to 6 so the
existing skew toast surfaces the real remedy (one-click backend update)
on session open.
2026-08-10 01:13:08 -07:00
Brooklyn Nicholson 8fdb92f449 fix(desktop): hoist the sidebar's sort key out of the flat list
The sort key was applied where the flat recents list is assembled, so it
did nothing at all once rows moved into groups: picking "cost" while
grouped by project or profile left every lane in the order the backend
sent it. Rank in a store instead, above any one view, so a grouped
surface can order the rows it owns by the same key.
2026-08-10 03:13:08 -05:00
fangliquanflq 03a9c69dc6 fix(desktop): preserve keyless plugin row identity 2026-08-10 01:13:08 -07:00
fangliquanflq 6e19c20d0a fix(desktop): support keyless plugin rows 2026-08-10 01:13:08 -07:00
notkisk 481ccdafb7 fix(desktop): keep react-router in one runtime chunk 2026-08-10 02:04:47 -04:00
hermes-seaeye[bot] e400dca96e
fmt(js): `npm run fix` on merge (#82962)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 05:40:43 +00:00
joaomarcos abd85a94bc fix(gateway): keep the personality pivot out of the truncate ordinal space (#82756)
`truncate_before_user_ordinal` is an index into the list of *real* user
turns. The gateway builds that list with `role == "user" and not
display_kind`, and `test_prompt_submit_truncate_ordinal_skips_display_kind_rows`
already pins why: "Without the filter, a trailing marker shifts the ordinal
so the wrong message is targeted for truncation."

`_apply_personality_to_session` broke that invariant at the producer. Its
pivot marker rides as `role=user` — deliberately, so strict
OpenAI-compatible providers accept it mid-conversation (the same reason
`_append_model_switch_marker` does) — but unlike the model-switch marker it
carried no `display_kind`. The gateway therefore counted it as a real user
turn while no client ever renders it as one.

After a personality change the two sides address different lists: every
later rewind/edit/regenerate resolves one slot too early, and
`replace_messages()` hard-DELETEs the extra span. That is the reported
signature — an in-range, valid ordinal, `confirm_truncate: true`, and a cut
that moved backwards with no user rewind action.

Tag the pivot like the model-switch marker, and teach the desktop to
project the kind as a timeline row so a persisted marker is never rendered
— or counted — as a user turn on the client side either. Both ends must
exclude it; excluding it on only one end just inverts the drift.

The regression test drives the real injection point rather than a
hand-written marker dict. Without the fix it fails with "the pivot shifted
the ordinal: the cut landed at 3 instead of 5", losing a turn the user
never asked to drop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 11:01:15 +05:30
ethernet 3bd844edf1 fix(desktop): make un-highlighted code readable while streaming in light theme
streaming code blocks in the light theme render near-white text on the
white code card until shiki's highlight lands, then snap to normal token
colors. the pale text is @tailwindcss/typography's pre foreground: its
prose theme styles pre as a dark slab (--tw-prose-pre-code = gray-200 on
a gray-800 bg). we strip the bg for our own code card but the near-white
foreground survives on the container. shiki's opaque per-token span
colors normally hide it — it shows through wherever text renders without
spans: the streaming delay window, the lazy-chunk suspense fallback, and
over-budget blocks that never highlight.

traced on the live renderer: computed color on the wrapper of mid-stream
code was oklch(0.928 0.006 264.531) (gray-200), supplied by the
.prose :where(pre) rule.

fix: prose-pre:text-foreground on the markdown container, so every
fenced path inherits the transcript foreground instead. the utility
layer is emitted after typography's base rule in the built css, so the
override wins by order at equal specificity.
2026-08-09 19:29:59 -04:00
hermes-seaeye[bot] e34c4c009a
fmt(js): `npm run fix` on merge (#82771)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-09 22:43:38 +00:00
Teknium aaf0fbfa95 test(desktop-ssh): cover wrapper preservation and explicit-path passthrough in locateHermes
Replaces the canonicalization test (which pinned the behavior #74425
removes) with wrapper-preservation coverage for auto-detection and an
explicit remoteHermesPath, both asserting no python3 -c parser call is
issued. Verified both fail against the pre-fix implementation.
2026-08-09 15:35:25 -07:00