Commit Graph

6527 Commits

Author SHA1 Message Date
Gille
8fe53200c7 fix(desktop): recover Cloud cookie before gateway ticket mint (#110308) 2026-09-25 16:42:44 -04:00
Hermes Agent
4c286ae7a0 test(desktop): load @hermes/shared once in the connection-registry contract test
A cold import inside the test body can outlast vitest's 5s per-test timeout
under CI load (seen on main 9df628615c and #121523).
2026-09-25 15:20:11 -05:00
Brooklyn Nicholson
067fa1a257 fix(accounts): drop the native title on the terminal disconnect button
The button already has an aria-label. title= fails the no-native-title scan.
2026-09-25 14:25:49 -05:00
brooklyn!
331a230da6 fix(accounts): stop stale Claude Code connections and false removal success
Accounts Connected now follows token validity instead of access-token
presence. Windows removal uses unambiguous PowerShell, a clear that
removes nothing is an error instead of a success toast, and the
connected-row terminal control runs disconnect.
2026-09-25 14:25:49 -05:00
Austin Pickett
fc4d0e616d fix(desktop): restore folder-pick affordance and filter sidebar to session-bearing projects
PR #49037 (projects paradigm) broke the folder->session->sidebar flow (#53004):

- The right sidebar's Files pane gated the tree on $currentCwd, which is ''
  for global/detached sessions, so those sessions hit a dead-end 'No project
  open' pane with no way back into a folder. Restore an affordance in the
  empty state: 'Open folder' runs the existing open-folder-as-project flow
  (upsert + enter project + fresh session anchored at the picked folder),
  decoupled from $currentCwd.
- The sidebar overview listed every auto-promoted repo on disk, session or
  not. Auto projects with zero sessions now stay out of the sidebar until
  they own a session (they reappear the moment work lands there); explicit
  projects and the Home bucket always render.

UI-affecting: coordinator should hold auto-merge for visual review.

Fixes #53004
2026-09-25 14:11:57 -04:00
Austin Pickett
7262ab2d9a fix(desktop): route ANGLE through SwiftShader on NVIDIA 580+ Linux drivers
NVIDIA driver 580+ breaks ANGLE's EGL probing (Invalid visual ID requested),
killing the GPU process at startup on Ubuntu 24.04 and similar hosts. Detect
the driver major from /proc/driver/nvidia/version and, on Linux (not WSLg,
not a remote display), pre-launch appendSwitch('use-angle', 'swiftshader').

Deliberately avoids app.disableHardwareAcceleration(): on 580.173.02 +
Electron 40 that path SIGKILLs the renderer, so the closed #40119 approach
is unsafe here.

Overrides: HERMES_DESKTOP_NVIDIA_SWIFTSHADER=1 forces the fallback on,
=0 opts out; HERMES_DESKTOP_DISABLE_GPU=0 keeps the GPU untouched.

Fixes #40077
2026-09-25 14:04:23 -04:00
Austin Pickett
9d2975398f fix(desktop): sync the composer queue across windows
Every desktop window boots $queuedPromptsBySession from the same
localStorage key and then never syncs again: no storage-event listener,
and every save writes the window's whole snapshot back, so windows
clobber and resurrect each other's queued prompts (#46732).

- listen for storage events on the queue key (and key===null full clear)
  and reload the atom; the event only fires in non-writer windows, so
  there is no self-echo
- writeSession/migrateQueuedPrompts merge over the live persisted map
  instead of the in-memory atom, closing the same-frame race where a
  save reverts a write that landed between sync events

The session-switch half of #46194 (drain routing to the wrong session)
is already covered on main by the per-session queue keys, the
isBackgroundQueueDrain guard and the stored/runtime binding verification
in submit.ts; this PR removes the remaining shared-storage leak in the
same state cluster.

Based on #57516 by @furancis (closed unmerged) — the storage-listener and
merge-over-live-storage approach is reused and reworked onto current main.

Fixes #46732
Fixes #46194
2026-09-25 14:04:14 -04:00
Austin Pickett
71225cd832 fix(gateway): broadcast projects.changed so CLI-created projects surface in the desktop UI
project_create (desktop_project tool / CLI) persists to the per-profile
projects.db but writes nothing to state.db, so sessions.changed never fires
and the desktop Projects sidebar goes stale until a manual refresh.

The gateway change watcher now watches projects.db and broadcasts
projects.changed when it moves; the desktop live-sync routes the event to a
new $projectsChangeTick that refetches the project list + tree.

Fixes #56757
2026-09-25 14:02:30 -04:00
Austin Pickett
3be17b1d5c fix(desktop): stop infinite re-download when opening downloaded artifacts on Windows
Fixes #53170
2026-09-25 13:36:54 -04:00
Hermes Agent
37d062b2fa test(desktop): keep rebased checks green 2026-09-25 12:10:14 -05:00
brooklyn!
78e4e0cc6c fix(desktop): read the preview zone the user is looking at
read_preview followed the global right-rail tab. follow() copied the
interacted zone into that id, so one group overwrote an explicit open
in another. Resolve from the hovered or focused zone, and skip that
copy when the explicit open lives in a different group. When more
than one preview is mounted, include active_tab_id and the open tabs.
2026-09-25 12:10:14 -05:00
brooklyn!
476ff4ddc5 fix(shared): add curly braces and sort catalog-browse before catalog-install export 2026-09-25 12:09:55 -05:00
brooklyn!
0150fb3a85 fix(skills): a same-named catalog skill can no longer be offered for install beside the installed one
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-25 12:09:55 -05:00
brooklyn!
78e2bcfaa9 refactor(catalog): browse-only catalog with clickable cards, category reels, and plugin enable/disable switches
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-25 12:09:55 -05:00
brooklyn!
8cc66a7923 feat(ui): shared Reel, Masonry and Button chip variant for catalog surfaces
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-25 12:09:55 -05:00
brooklyn!
26201480a9 feat(desktop): restore approved catalog cards in Capabilities 2026-09-25 12:09:55 -05:00
brooklyn!
59c54c7892 fix(catalog): restore confirmed skill installs without weakening plugin links 2026-09-25 12:09:55 -05:00
calvinnwq
2e0243b158 fix(desktop): never attach to an isolated serve found in the spawn ledger
hermes serve --isolated (the backend another machine's Desktop spawns
over SSH) opts out of the host singleton on the CLI side, but its spawn
ledger row carried no structured marker, so the local Desktop's
attach-first discovery adopted it. Nothing on this host owns that
process, so a Desktop-driven update left the local app on stale code.

Record isolated=True in the ledger row and skip such rows in
parseSpawnLedger. An ordinary serve is still attached even when an
isolated record is newer.
2026-09-25 12:09:37 -05:00
brooklyn!
296ec08dcf fix(models): keep generation models out of chat
Chat catalogs and the session switch treated image and video generation
models as chat. Exclude them by the capability type and name shape the
catalog already publishes, reject selecting one as the session model, and
do not restore a primary already known to be non-chat. Desktop shows the
fallback switch in the transcript.
2026-09-25 12:07:32 -05:00
Hermes Agent
28545254dd fix(desktop): preserve the active chat across a sleep/wake gateway reconnect
After a Windows/macOS sleep, the desktop's WS connections drop and the
auto-reconnect reopens the gateway. The route-resume hook treated every
closed->open transition on a new-chat route as 'the user navigated to
/new' and called startFreshSessionDraft, discarding the active runtime
session and parking the previous chat in the sidebar as a detached
session. Keep the active chat when the gateway reopens mid-chat: no
navigation happened, only the transport came back.

Co-authored-by: 0disoft <rodisoft1@gmail.com>
2026-09-25 11:51:22 -05:00
Hermes Agent
42d9d05f49 fix(desktop): name real interrupt controls in busy-guard copy, not /interrupt
There is no /interrupt slash command on any surface: Desktop has a Stop
button plus Esc, the terminal TUI uses Ctrl+C. The desktop busy guards
still demanded /interrupt — a dead end for users mid-turn, and on Desktop
there is no Enter-to-interrupt fallback either.

The Python gateway side already speaks plainly (user_messages.busy_message);
this fixes the two desktop copy sites left: the slash busy refusal in
use-prompt-actions and the goal-continuationBusy string across all nine
locales.

Fixes #42093
Fixes #51576

Copy fix for the slash.ts site salvaged from #51569 (David Metcalfe).

Co-authored-by: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com>
2026-09-25 11:40:09 -05:00
Hermes Agent
722a02f967 test(desktop): import settings panels once so first-test imports can't time out
Several settings suites imported their component inside each test body.
The first in-test `await import` paid the whole jsdom env init + module
transform + import graph inside test one's 15s budget, which times out
under CI runner load; the late-finishing import could also leak its first
render into the next test ("multiple Honcho settings buttons").

Load each panel once at module scope (same pattern as
profile-scope.test.tsx / config-settings.test.tsx), where the cost is
outside every test timeout. No behavior change: hoisted vi.mock factories
still intercept these imports identically.

Seen on #122304, #122269, #122368, #121523.
2026-09-25 11:39:19 -05:00
Hermes Agent
ba1c129692 fix(desktop): add "Use Settings default" to the composer model menu
A composer model pick is sticky: every new chat ships it as a session.create
override instead of the Settings → Model default, and the only way back was
re-applying the main model in Settings. The menu now shows a "Use Settings
default" row while a draft carries a manual pick (the same condition as the
pill's pin dot). It resets the model source to default and reseeds the
composer from the profile model. Strings added in all locales.
2026-09-25 11:31:32 -05:00
Brooklyn Nicholson
2896297189 test(kanban): type the events-cursor mocks
PluginStorage.get is generic, and an untyped vi.fn() call tuple is empty under strict tsc.
2026-09-25 11:26:40 -05:00
brooklyn!
bfb1d952c0 fix(kanban): start the events stream at the board tail
Desktop opened /events with no since, and a missing cursor was read as
0, so every open replayed task_events history. Seed the socket from the
snapshot or this connection's last frame, and start a cursorless stream
at MAX(id). An explicit since still replays from there.

Fixes #81537
2026-09-25 11:26:40 -05:00
Hermes Agent
58497395d3 fix(desktop): declare macOS Contacts and Apple Events usage strings
Signed, hardened-runtime builds without an NSContactsUsageDescription /
NSAppleEventsUsageDescription in mac.extendInfo are denied Contacts and
Apple Events access by TCC without ever showing a prompt — macOS requires
the usage string before it will even ask (#59482). Declare both next to
the sibling mic/camera/calendar strings, with a packaging test that holds
the contract at the config seam so the strings can't be lost in the next
config move.

Fixes #59482

Co-authored-by: Shashwat Gokhe <shashwatgokhe2@gmail.com>
2026-09-25 11:26:22 -05:00
kshitijk4poor
c2915ec41b style(desktop): prettier on transcript backfill salvage files 2026-09-25 21:28:42 +05:30
kshitijk4poor
3ffb1f04be fix(desktop): extend activate and resume refresh pages to overlap before grafting
The REST hydration on activate and the prefetched resume both graft a
newest-tail page onto the previously rendered transcript. A long turn can
push every rendered row off that page, so read older pages with
extendRefreshPageToOverlap first, as the tile delegate and background sync
already do. Re-check ownership / resume currency after the extra reads.
2026-09-25 21:28:42 +05:30
kshitijk4poor
31504e7a04 fix(desktop): extend tile resume refresh page to overlap before grafting 2026-09-25 21:28:42 +05:30
kshitijk4poor
b230517b5d fix(desktop): share one older-page reader for refresh overlap reads
The overlap extension had the same older-page closure pasted at every
refresh site. It ignored pagination.offset and fetched again after a
short page. olderPageReader follows tailStateFromPage instead. The
extension also returns early when the rendered transcript has no stored
ids, and stops once a page reaches below the oldest rendered id. The
background-sync refreshes hoist their stale-read guard into stale().
2026-09-25 21:28:42 +05:30
kshitijk4poor
96ea97d422 fix(desktop): keep unstored rows anchored in the stored-id refresh merge
A page that starts mid-turn opens with a tool fold that has no stored
id. The stored-id merge dropped it and moved the window's unstored rows
to the end. Carry each unstored row with the next stored row after it,
and reuse sharesDurableRow/durableRowIds for the overlap test.
2026-09-25 21:28:42 +05:30
kshitijk4poor
5e25167ec3 test(desktop): trim transcript backfill salvage tests to two invariants per pick 2026-09-25 21:28:42 +05:30
josephsellers
454d1b6d07 fix(desktop): sort an overlapping history page by stored id
A post-turn refresh that overlaps the window was splicing at the first
shared row, so an older row glued after the live tail stayed there.
When that splice is wrong, merge by stored id, keep the fresh page's
copy of a shared id, and leave a row with no stored id at the end. A
page that already covers the window replaces it. A page that shares no
stored id still replaces the window, which is how a compaction rewrite
reaches the screen.

[Used Grok Build 🤖]

(cherry picked from commit 484cb2c1b0595f8df9bf19c5d1a698b8bb5a60f8)
2026-09-25 21:28:42 +05:30
KoNit-K
efc3e6f419 fix(desktop): preserve transcript across long refreshes
(cherry picked from commit fcc28a2bd453c4d3ff2e8930448c124b0cbe6709)
2026-09-25 21:28:42 +05:30
Hermes Agent
7b761da2de style(desktop): sort backend-start-failure named imports
Some checks are pending
Live provider canaries / Live provider canaries (push) Waiting to run
2026-09-25 01:28:42 -05:00
Hermes Agent
22a7acd810 fix(desktop): latch SSH auth failures so the boot overlay stays clickable
An SSH auth-failed boot error carried none of the local, host-key or
reauth latch tags, so it stayed retryable: every getConnection/api call
re-ran startHermes, re-emitted running: true and hid the boot-failure
overlay before its Gateway settings button could be clicked. Classify
the rejection (kind/sshError tag, or the message once stringified),
latch it like a host-key change, and keep it out of the renderer's
auto-retry loop. reset/repair/apply-config still release the latch.

Co-authored-by: x7peeps <xtpeeps@qq.com>
2026-09-25 01:28:42 -05:00
KoNit-K
085d9ee608 fix(desktop): pass --disable-gpu on 0xC0000409 relaunch so marker write failure cannot loop
Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit ecaf213873ca00654d1b3cf543eb56e5bf3279c3)
2026-09-25 01:09:23 -05:00
KoNit-K
2e36513ef9 fix(desktop): recover Windows 0xC0000409 renderer crash loops by disabling GPU
After the crash-loop budget trips on STATUS_STACK_BUFFER_OVERRUN, relaunch
once with GPU off instead of leaving a blank window. Sandbox stays intact.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 02c096d5b2f0e0872b7dc35f408b70e1e51ead2e)
2026-09-25 01:09:23 -05:00
Hermes Agent
d7f06c964b style(desktop): format no-console-git and pad new spawn statements 2026-09-25 01:08:37 -05:00
Brooklyn Nicholson
4fd73cc0f3 fix(desktop): read the git close code instead of assigning exitCode 2026-09-25 01:08:37 -05:00
brooklyn!
e4a2c105b9 fix(desktop): hide Electron git spawns with CREATE_NO_WINDOW
windowsHide on a GUI-subsystem Electron parent does not stop git.exe from
allocating a console. Route those spawns through a console-subsystem
python.exe host that starts git with CREATE_NO_WINDOW (0x08000000) and
forwards the git argv unchanged, including simple-git review probes.
2026-09-25 01:08:37 -05:00
Hermes Agent
4b2429624a fix(desktop): verify close/stop after the backend teardown, not before it
The Windows tree-kill check ran at the top of backendShutdown and threw
before the graceful teardown, pool stop and straggler reap. It now takes
the owned child handles up front, runs after the reap on the children that
are still running, and surfaces a failure only once cleanup is done. A lock
whose delete fails is kept and logged instead of throwing out of close, and
exitAfterBackendShutdown still exits when shutdown reports a failure.
2026-09-25 01:08:23 -05:00
Hermes Agent
354d499511 style(desktop): format close-stop handling 2026-09-25 01:08:23 -05:00
Hermes Agent
de16db2b6b fix(desktop): sort close-stop-kill after the cli-provision import 2026-09-25 01:08:23 -05:00
brooklyn!
5cfd387741 fix(desktop): sort the close-stop imports
Lint wants backend-serve-support before close-stop-kill, and a blank
line between the node and vitest imports.
2026-09-25 01:08:23 -05:00
brooklyn!
8b17394cc6 fix: abort reasoning-field length stops and surface close taskkill failures
finish_reason=length with empty visible content and a non-empty reasoning
or reasoning_content field uses the existing thinking-budget abort. No
model id is consulted. Empty content with no side channel still continues.

Desktop close/stop no longer discards Windows taskkill failures. After the
same tree-kill, owned PIDs are inventoried and only unheld gateway locks
are cleared.
2026-09-25 01:08:23 -05:00
Hermes Agent
fa58e4ab75 fix(desktop): let the boot-failure overlay be dismissed
The recovery modal rendered with no close control and ignored Escape, so a
latched boot error trapped the user. Add a Close button to both the recovery
card and the embedded Gateway settings view, and route Escape through the
same onOpenChange path. Dismissal hides the modal only: the boot error stays
latched, and the overlay comes back when the error changes, clears and
recurs, or a retry starts and fails again.

Co-authored-by: giggling-ginger <110955495+giggling-ginger@users.noreply.github.com>
2026-09-25 01:07:48 -05:00
brooklyn!
e726b79803 fix(desktop): stop macOS launch from showing two Dock icons
The setup launcher had no LSUIElement, and its already-installed hand-off
ran after Tauri/AppKit, whose default activation policy is Regular. That
registered the setup bundle as a second Dock app beside the real desktop.
Hand off before constructing Tauri, and restore Regular activation only
when the installer UI is actually shown.

On macOS the Electron single-instance lock also ran before deep-link
registration. setAsDefaultProtocolClient relaunches the app through Launch
Services, so the loser already had a Dock icon by the time the lock failed
and app.exit(0) ran. Register the protocol first. The lock-losing instance
still hard-exits before ready.

Fixes #73151
2026-09-25 01:01:42 -05:00
brooklyn!
08887d9f86 fix(desktop): sort the primary-boot route import
registryPrimaryBootRoute comes before resolveDesktopRemoteRoute.
2026-09-25 01:01:04 -05:00
brooklyn!
1beed32449 fix(desktop): keep primary-boot fixture overrides on the startup options type
The new select and attach mocks were spread in as unknown, so the
assertions that read them failed typecheck.
2026-09-25 01:01:04 -05:00