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>
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)
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.
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.
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.
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>
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
A Desktop whose registered primary is SSH could finish an update and spawn
a local backend. Remote session tiles then resumed against that local
backend and showed the durable-session retry copy.
After update clearance, select launchMode=primary before any local attach
or spawn. If the active backend identity is not the tile owner, unbind the
persisted tile with a wrong-backend error instead of that retry copy.
The settings action cell shrink-wraps to content, so with a blank value
the timezone SearchableSelect trigger collapsed to the 'Search…'
placeholder + chevron (~70px), and the popover — whose width floors at
the trigger's --radix-popover-trigger-width — inherited the squish
(#99751). Give the shared trigger a min-w-44 floor, the same
min-width-floor convention the model-settings selects already use; the
popover's IANA-truncation half was fixed on main earlier.
No live candidate PR (both were deleted); implements the issue's own
proposed floor, with a width-contract test (jsdom cannot compute
Tailwind layout, so the rendered class floor is the assertable unit).
The transparent, frameless Quick Entry window was created with
hasShadow: true. macOS derives a transparent window's native shadow
from its alpha content, but the boot HTML paints an opaque background
before the renderer forces transparency, so the OS caches a full-frame
shadow that renders as a stray grey rounded outline behind the card
(#99172). The other transparent overlays (pet, HUD) already run with
hasShadow: false; other platforms keep the native shadow.
Also fit the card's own CSS box-shadow inside the 12px transparent
padding budget (0 18px 48px clipped to a hard edge at the fixed window
bounds; 2+8 = 10px stays inside and fades cleanly).
Mirrors #83704.
Co-authored-by: Guangtong li <64474753+Marcus112-CS@users.noreply.github.com>
The sidebar's Archived view reuses the shared row action menu, whose
archive item was built unconditionally (label Archive, icon archive,
always the archive path), so an already-archived row offered a no-op
re-Archive instead of a restore (#98813).
Pass the row's archived state through to the menu, flip the verb to
Unarchive (with the ArchiveOff glyph the Settings restore button
already uses), and add unarchiveSession to useSessionActions — the
restore path the Settings → Archived Chats flow already has: flip
setSessionArchived false with the row's owning profile, drop the
archived-view row, lift the eviction tombstone, and re-list through
restoreListedSession so messaging/cron rows land back in their own
slice. The wiring's onArchiveSession dispatches by state: an id in
$archivedSessions (or listed archived) restores, everything else
archives as before.
New copy: sidebar.row.unarchive + desktop.restored/unarchiveFailed in
all locales (en/ar/de/es/fr/ja/ru/zh/zh-hant).
Mirrors and extends #98823 (locales the PR missed, slice routing,
restore icon).
Co-authored-by: liuhao1024 <11816344+liuhao1024@users.noreply.github.com>
The ::preview iframe's theme prelude carried tokens, font, and a
transparent background but no color-scheme, so the frame document fell
back to the UA default color-scheme:light — form controls, scrollbars,
the canvas beneath the transparent body, and prefers-color-scheme
inside the frame ignored the app's dark theme (#95814). A transparent
background alone does not fix this.
Inject :root{color-scheme:<app scheme>} as part of the prelude (first,
so a preview page's own color-scheme declaration still wins) and key
the framedDoc memo on the scheme so a theme switch rebuilds the srcdoc.
Mirrors #97463.
Co-authored-by: 墨綠BG <103036558+BlackishGreen33@users.noreply.github.com>
Clicking a bot whose chat is not already a tab waits on source prep and
the registry open before anything changes, so on a cold backend the
click looked dead. openRosterBot now publishes $pendingBotOpen after the
fronted-tab miss, and the row shows a GlyphSpinner with aria-busy and a
translated "Opening chat…" label. The highlight still follows the chat
on screen. Every exit settles the mark behind a generation guard, and
bumpBotOpenGeneration clears it, so a group open, a return to Sessions,
or the next click drops it.
Closes#120277
Co-authored-by: finn763 <165816600+finn763@users.noreply.github.com>
Co-authored-by: Vaibhav Arora <varora1406@gmail.com>
A submenu always portaled to document.body at z-50, even when its parent
menu lived inside a dialog. In the Kanban task dialog the model catalog's
thinking-effort submenu landed under the modal overlay: blurred and not
clickable.
SubContent now reads the parent Content's resolved portal container from
context (dialog content, or an explicit portalContainer). When it has one
it steps up to z-(--z-modal-popover) and restores pointer-events, which
the modal parent menu sets to none for a submenu that registers first.
Outside a dialog nothing changes.
Co-authored-by: Chen Jin <Enough1122@users.noreply.github.com>
The workspace | right-rail seam resizes the rail's inner review zone,
but the drag preview wrote that zone's px as the flex-basis of the whole
section wrapper, so the rail snapped narrower mid-drag and jumped on
release. The preview now grows each fixed track's wrapper by the zone's
delta from its own pointerdown width, sizes the inner zone item to the
planned px, and restores both on release. This applies per track, so a
cascade that reaches a nested section past the seam partner previews
correctly too.
Co-authored-by: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com>
0401e08884 made the session.info cwd write require the event to describe
the selected stored session, but the branch write a few lines below
stayed unconditional. A background Kanban worker's runtime update that
reached the pane's active-runtime path could still flip the composer's
coding-rail branch to the worker's PR worktree while the default chat
stayed selected. cwd and branch now share one identity check.
Co-authored-by: ClintonEmok <54935030+ClintonEmok@users.noreply.github.com>
The resize frame was unconditionally pointer-events: auto, so a
collapsed HUD kept an invisible ring of window — nothing painted,
every click and drag aimed at the app underneath eaten. The frame now
rides the band's engagement gates: the caret in the composer
(data-hud-typing, stamped live by useHudGlass), a held band, or a
solid-input host re-arm it; an in-flight drag keeps its own handle
live via data-hud-grabbing (pointer capture routes the moves).
Direction from #108796 (liuhao1024); behavioral test instead of that
PR's source-reading one.
Co-authored-by: liuhao1024 <noreply@github.com>
Boot-time pruning recorded share transitions while panes were still
hydrating, and recalled seam weights didn't validate the partner pane,
so a reload could rewrite tile widths. beginLayoutHydration gates
contributed-pane pruning until hydration ends; paneSharePartners.v1
records the seam partner and recalledEdgeWeights falls back to even on
partner mismatch; parseTileList drops self-anchored entries.
Salvages the pin-clear half of PR #109059 (salch-cred) and goes further:
- The current child's 'error' and 'exit' handlers now clear the pin
after the clearForCurrentProcess guard, so an unexpected exit cannot
leave routing pinned to a dead backend while the respawn reads the
new --profile. A stale exit (older child) still returns before the
clear and never touches a newer primary's pin.
- The connection IIFE's catch clears the pin behind the attempt guard:
a failed startup releases its routing identity; a superseded
attempt's failure never clears the newer attempt's pin.
- resolveLaunchProfile(readPreference) makes the launch decision ONE
read per attempt: startHermes() now derives routingProfile (pin,
setActiveGatewayProfile, remote resolve, child env identity) and
argvProfile (the --profile flag) from the same decision, instead of
pinning primaryProfileKey() up top and re-reading
readActiveDesktopProfile() deep inside the IIFE — the split that let
a mid-startup hermes:profile:remember produce 'routing alpha,
--profile beta'. Unset preference keeps the legacy flag-less launch.
- #108118's invariant is preserved: a live primary still answers
primaryProfileKey() from the pin while a preference change lands, so
no duplicate backend spawns mid-life.
A window born show:false is revealed only by success-shaped events
(ready-to-show, did-finish-load). When the HUD's main frame fails to
load or its render process dies before first paint, neither fires, the
4s fallback is never even scheduled, and the transparent window stays
hidden forever while broadcastHudState(true) keeps every toggle reading
open. wireWindowReveal now grows a failure branch (onRevealFailed) that
fires exactly once for a main-frame did-fail-load or a pre-reveal
render-process-gone, cancels a pending fallback, and disarms the reveal.
The HUD passes a handler that tears the window down through the bounded
requestHudClose, so the existing 'closed' handler owns the one teardown
path (snap shortcut, main-window restore, broadcastHudState(false)) and
the toggles converge to closed. The log-only post-reveal lifecycle
(#81290) is untouched: a crash after a successful reveal is still
diagnosable, not resurrected.
wireWindowReveal moves from main.ts into window-reveal.ts (same
signature, event wiring now unit-testable against fake emitters).
Preview/plugin fs.watch change events were always sent to mainWindow, so
secondary windows never live-reloaded. Capture event.sender, store the owner
WebContents on each watch, and deliver hermes:preview-file-changed there
(tearing the watch down if the owner is gone).
* fix(desktop): reload live windows when renderer is killed
External SIGTERM/OOM watchdogs report reason=killed with isDestroyed=false,
leaving a white window because only crashed/oom were recoverable. Treat
live-window killed as recoverable; user close still exits early via
isDestroyed expected-teardown.
Closes#85048
* fix(desktop): end a killed-renderer reload loop on the recovery page, skip it on quit
Live-window 'killed' now reloads under the shared crash-loop budget. Once
that budget is spent the window hands off to onRendererTerminated so it
lands on the #116472 recovery page instead of a dead window, and a quit or
update handoff (which can kill renderers before their windows report
destroyed) is treated as expected teardown for every reloading window.
---------
Co-authored-by: doresa0 <doresa0@users.noreply.github.com>
Co-authored-by: Hermes Agent <agent@hermes.local>
* fix(desktop): confirm before switching to This device
The at-rest This device pill used the same home glyph as a gateway's
default profile, and clicking it went straight into selectConnection,
which can start a local install or silently replace the center with a
fresh session.
- Give the local default a device-desktop glyph and a label that says
what the click does, on the rail pill and both condensed dropdowns.
- Add a probe-only hermes:local-backend:probe IPC (resolveHermesBackend,
no ensureRuntime) and a useLocalDeviceSwitch dialog: Install locally /
Connect to existing instead / Cancel when bootstrap is needed, or a
Switch / Cancel fresh-session cue when Hermes is already installed.
- Wire it into ProfileRail, ProfileDropdown, the statusbar
ProfileSwitcher and ConnectionSwitcher.
Fixes#102826
Co-authored-by: chelsealong <chelsealong@126.com>
* fix(desktop): retain profile rail status indicators
* fix(desktop): preserve local device confirmation in rail
---------
Co-authored-by: Hermes Agent <agent@hermes.local>
Co-authored-by: chelsealong <chelsealong@126.com>
* fix(desktop): wait for the last mic meter to close and report a dead one
The voice recorder's level meter fired AudioContext.close() without awaiting
it and opened a new context on every take, so back-to-back takes (and the
barge-in monitor) could hold two capture contexts at once. That is what makes
Chromium raise "The AudioContext encountered an error from the audio device",
and the error was never handled: the analyser went flat and the take read as
silence.
Meter closes now go through mic-meter-context, and both the recorder and the
barge monitor wait for pending closes (capped at 1s) before opening a new
context. The recorder watches its context for 'error' and an unexpected
'closed', treats a context it can't build as failed, calls
onMeterFailure, and marks the take meterFailed.
* fix(desktop): transcribe voice turns whose level meter died
Continuous voice dropped any take whose meter-only heardSpeech stayed false,
so once the AudioContext errored every later utterance was discarded before
STT with no error shown. A meter failure now ends the turn, and the take goes
to STT unless it is under 750ms (STT decides whether there was speech).
Back-to-back meter failures show "Microphone failed" and end the conversation
instead of re-arming forever.
Fixes#75329
---------
Co-authored-by: Hermes Agent <agent@hermes.local>
The attachment row under a sent user message had -mt-3, which pulled it
up into the sticky prompt's box. The sticky-prompt clip then cut off the
top of folder/file chips and image thumbnails, even with the thread at
rest. Drop the negative margin so the row starts below the bubble.
Fixes#78847
Co-authored-by: Hermes Agent <agent@hermes.local>
Co-authored-by: Sami Rusani <samgithub@pm.me>
A project id names a row in one backend's projects.db, but $projectScope
survived profile and connection switches. The fresh draft resolved its cwd
from the stale scope against the still-loaded old project tree, so a new
chat in profile B started in profile A's project folder.
Fixes#54990
Co-authored-by: Robin Rademacher <robinradx@gmail.com>
SshConnection.open() handed the classified error to its caller and
logged nothing about the failure. desktop.log showed only "connecting"
and then "connection closed", and the renderer shows only friendly copy
for the error kind. A connect that dies right after TCP setup (#80836)
therefore left no exit code, close signal or stderr anywhere. Log them,
redacted, for both the mux and no-mux connect paths.
findPythonForRoot ended in findSystemPython(), so a checkout with no
in-tree venv/.venv produced a PATH Python. Two designed refusals were
therefore unreachable: readSourceUpdate's `!managed && !probe.python`
guard (checkout-source.ts) and StateDbPreflight's `string | null` python
plus its "Python not found" throw (state-db-preflight.ts). Both were
written expecting null and could never see it.
A PATH Python can import a checkout while lacking its selected
dependencies, which is the failure the checkout-source comment already
names, so a failed read became a wrong answer instead of a refusal:
the update probe, the state.db pre-flight and the source backend all
ran under an interpreter nothing selected. PM deletes the in-tree
venv/.venv once a generation is committed, making null the ordinary
answer for a managed install — its callers resolve the installation
launcher instead, and the backend ladder falls through to its next rung.
resolveSourcePython owns the decision (override, then the checkout's own
venv, else null) and findPythonForRoot keeps its signature, so the four
call sites are unchanged. findSystemPython stays for the uninstaller,
whose interpreter choice is a separate, deliberate one for a locked venv.
isSessionRemote only trusted route.mode. A connection-tagged session row
(the unified Sessions list) and a bare-profile owner carry no mode, so the
check fell back to the window's ambient connection. A local-ambient window
then sent a client path via image.attach to a remote backend that can't
read it ("image not found: /Users/...").
Resolve the owner's mode in this order: route.mode, the registry
connection's kind, then the mode of the socket already dialed for that
owner (the primary for its profile, or the owner's own secondary). Fall
back to ambient only when none of those is known. registryConnectionKind
moves into connection-registry-state so profile.ts and session-states.ts
share one lookup.
021950ac81 stopped indexing arbitrary dotted paths, but terminal output
is still scanned for bare URLs and looksLikeArtifact accepts any
http(s) value. Installing from a PyPI mirror logs every download with
its full URL (the .whl, its .whl.metadata, sdists under /packages/),
so each one landed in the Artifacts menu; sdists pip reports under its
cache dir (AppData\Local\pip\Cache, ~/.cache/pip, Library/Caches/pip)
were indexed too. Heuristic candidates that are wheels, package-index
dist downloads, or files under pip's cache are now dropped; explicit
MEDIA deliveries are unaffected.
Co-authored-by: yungchentang <46495124+yungchentang@users.noreply.github.com>
Compression rotates a chat's live id while the project snapshot still holds
the older segment. The live overlay matched rows by id only, so the tip was
added next to the snapshot row (a ghost duplicate until the next tree
refresh), and the owner map missed the tip, letting an umbrella project claim
it by cwd.
Match rows and owners by every id the conversation has had (id, lineage root,
lineage chain) in the lane overlay, the overview previews and the drill-in
preview backfill.
Every sessions.changed and window focus refreshes projects.tree, and each
refresh handed the sidebar a new array. The entered project keyed its
projects.project_sessions refetch on that array, so every refresh paid for a
full hydrated tree build on the backend (seconds over a remote gateway) and
rebuilt the lanes.
The tree now keeps unchanged nodes by reference, the drill-in refetches only
when its own overview node changes, an unchanged answer keeps its reference,
and a background refetch no longer reports loading.
Holding W through a browser's own Ctrl+W keeps sending auto-repeat keyDowns
once Hermes inherits focus, and each one closed a tab; a keydown synthesized
on window activation could do the same. Close Tab and Reload now swallow
auto-repeats and anything within 200ms of the window gaining focus. They are
still claimed so the renderer's mod+w keybind can't act on them. Zoom keeps
repeating.
Fixes#105498
Co-authored-by: kokhlo <konstantin.khlopkov93@gmail.com>