Commit Graph

6802 Commits

Author SHA1 Message Date
Adolan
861893da2f fix(desktop): show provider retry and auto-recovery waits in the status row 2026-09-27 12:50:03 -05:00
Adolanium
d1167fdff7 fix(desktop): match custom:<key> providers in the settings and bot model pickers
model.info and saved profiles report a user-defined provider as custom:<key>, but the catalog row uses the bare key as its slug. Settings > Model and the Bot Mode picker compared the two with ===, so a saved custom provider never found its row. Settings showed a duplicate custom:<key> entry and a Set up provider button, and the bot editor fell back to the manual form.

Both now match rows with catalogProviderMatches, like the composer picker already does. Settings uses a small findCatalogProvider helper for every row lookup, including the aux and MoA slots and the endpoint passed on Set to main. catalogProviderMatches is now exported through the plugin SDK so the bot picker can use it.
2026-09-27 12:49:56 -05:00
Adolan
0559c367ef fix(desktop): offer New cron from the empty scheduled-jobs detail 2026-09-27 12:49:39 -05:00
Adolan
6b512010ec fix(desktop): show one error card when the agent fails to start 2026-09-27 12:49:32 -05:00
Adolan
a65641b2e6 fix(desktop): let messaging credential inputs fill their row 2026-09-27 12:49:26 -05:00
Adolan
42592179ed fix(desktop): confirm when the main model is applied 2026-09-27 12:49:18 -05:00
funky-xamarin
52276088c9 fix(desktop): expire unknown readiness notices and clear on recovery 2026-09-27 12:49:09 -05:00
Adolan
4c3da4fbe5 fix(desktop): stop calling unreachable providers a timed-out reply 2026-09-27 12:49:02 -05:00
funky-xamarin
fd2bf1c15d fix(desktop): measure rail after locale direction is applied 2026-09-27 12:48:54 -05:00
funky-xamarin
44a0fe0359 fix(desktop): respect RTL profile rail edges and wheel direction 2026-09-27 12:48:54 -05:00
funky-xamarin
24bbdab4c7 fix(desktop): reveal clipped profile rail edges and pin actions 2026-09-27 12:48:54 -05:00
Adolan
a88dce6e2d fix(desktop): label the messaging platform enable switch 2026-09-27 12:48:49 -05:00
Cursor Agent
c3b24b74ba test(desktop): pin backslash math and the CJK-variable tradeoff
A span whose body is a backslash command is real math, and a CJK
variable inside one equation must still escape while the next span stays.

Co-authored-by: Yun. <fangyun1008@gmail.com>
2026-09-27 12:48:18 -05:00
Cursor Agent
f77bf679cb fix(desktop): keep inline math spans when CJK prose follows them
escapeCjkProseDollars treated a real closing dollar as the next opener,
so CJK text between two formulas escaped the first equation's closer.

Co-authored-by: Yun. <fangyun1008@gmail.com>
2026-09-27 12:48:18 -05:00
chelsealong
b9e2ddde05 fix(desktop): read inline preview color-scheme from the same source as its tokens
InlineHtmlFrame derived colorScheme from useIsDark(), which reads the
.dark class through React state that only updates a render after
applyTheme() has already mutated the DOM (use-theme-epoch.ts's own
comment documents this ordering: "a child's effect runs before the
provider's applyTheme"). collectThemeBridge() reads the CSS token
values live via getComputedStyle() on every render, so a render caught
between the DOM mutation and the epoch-triggered re-render built a
frame with fresh dark tokens but a stale light color-scheme -- a
transparent iframe with color-scheme:light still paints its canvas
white, so the widget showed near-white text on a white canvas (#123048).

Move the color-scheme read into collectThemeBridge() itself, off
document.documentElement.dataset.hermesMode (the same attribute
applyTheme() sets the token values from, and the pattern
lib/selection-copy-colors.ts's renderedMode() already uses for the
same reason), so the scheme and the tokens it decorates always come
from one synchronous read.
2026-09-27 12:48:11 -05:00
hermes-seaeye[bot]
8c9fe96400 fmt(js): npm run fix on merge (#125322)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-27 14:20:28 +00:00
hermes-seaeye[bot]
e54d550521 fmt(js): npm run fix on merge (#125310)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-27 14:06:23 +00:00
flyer103
e5050a040a fix(desktop): don't announce a reconnect when the app is quitting
Quitting the Desktop app wrote "[boot] Restarting desktop connection" to
desktop.log and pushed the same message to the renderer over
hermes:boot-progress. Nothing was restarting: the quit coordinator called
teardownPrimaryBackendAndWait() with the default soft=false, and soft=false
is what makes resetHermesConnectionState() call
resetBootProgressForReconnect().

Name the two teardown intents in quit-teardown.ts and use them at every
deliberate primary teardown: a teardown that re-homes ('reconnect', update
hand-off and bundle swap) keeps the announcement; one that brings nothing
back ('quit', the quit coordinator and the uninstall teardown) stays silent.
Fixes #123437.
2026-09-27 08:53:09 -05:00
Brooklyn Nicholson
bbf670a98d style(kanban): satisfy the curly and blank-line lint rules in the alias-resolution change
Mechanical eslint --fix over the two files this branch touched; no behavior
change. 35 kanban notify tests still pass.
2026-09-27 08:52:49 -05:00
funky-xamarin
c4700d60de fix(desktop): preserve alias task cache invalidation 2026-09-27 08:52:49 -05:00
funky-xamarin
23d95743a0 fix(desktop): resolve default Kanban board before event subscription 2026-09-27 08:52:49 -05:00
Kevin Rajan
97065356c8 fix(desktop): keep model rows clickable before pointer movement 2026-09-27 08:51:26 -05:00
hermes-seaeye[bot]
7d6da01e98 fmt(js): npm run fix on merge (#125233)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-27 12:41:42 +00:00
finn763
793517a7d4 fix(desktop): anchor project trunk + at first repo root when primary path unset
Clicking the project-group trunk + passed the null wire path through, which
downstream treats as the reserved Home/detached signal, silently creating a
global session. Fall back to the first repo root for path-less explicit
projects; Home keeps null. Covers click and new-session drag cwd.
2026-09-27 07:30:45 -05:00
chelsealong
f9f153ab18 fix(desktop): clamp Bot Screen clipboard paste to the bridge's 256 KiB cap
clipboardPasteFrom was called with unbounded clipboard text; the gateway's
RfbClientFilter closes the display WebSocket on any ClientCutText over
_MAX_CUT_TEXT (256 KiB), turning an oversized paste into a full stream
disconnect instead of a dropped paste. Clamp before forwarding and add a
test pinning the ceiling.
2026-09-27 07:26:01 -05:00
chelsealong
2d6657d72c fix(desktop): forward client clipboard paste into Bot Screen
Take over over a bot's remote screen never sent the client's text
clipboard to noVNC: screen-pane.tsx built the RFB session but had no
paste handler and never called clipboardPasteFrom, so Cmd/Ctrl+V into
a remote input did nothing (#123089). The gateway already gates
ClientCutText as lease-checked input, so the missing leg was purely
client-side.

Add an explicit-gesture `paste` listener on the canvas, forwarded only
while this viewer's RFB client is not view-only (i.e. holds the
lease); no polling, no clipboard logging. Listener is torn down with
every detach so it never outlives its RFB client.
2026-09-27 07:26:01 -05:00
Brooklyn Nicholson
e9c60209a7 fix(desktop): bridge the already-decoded media stream path once
The hermes-media://stream resolveLocalFile boundary ran
resolveMediaRequestPath on the path the protocol handler had already
percent-decoded and slash-stripped (parseMediaProtocolTarget), so the
second decode+strip turned every absolute POSIX media path
(/home/.../.hermes/...) into a cwd-relative one. The resolver then
pinned it under the app's cwd and the file ENOENTed into the handler's
silent 404 — inline TTS/audio playback dead with no log line, which is
the Linux symptom in hermes-agent 123823. A second decodeURIComponent
also threw URIError on filenames containing a literal %.

Restore the single decode at the protocol layer: the boundary now only
bridges (resolveMediaStreamFile), matching what 7722761581 intended as
byte-for-byte behavior but broke when it rewired main.ts.

Fixes https://github.com/NousResearch/hermes-agent/issues/123823
2026-09-27 07:20:20 -05:00
Brooklyn Nicholson
ba5e3bfa31 fix(desktop): say "File not found" when opening a missing file
Opening a deleted/renamed artifact reported "No application found to open
URL": macOS LaunchServices answers a non-existent path with
kLSApplicationNotFoundErr (-10814), and the open route handed resolved
paths to the OS without ever stat-ing. On current main the file route is
reveal-in-folder only, which is worse on a miss — showItemInFolder of a
non-existent path is a silent no-op on macOS, so the click does nothing
at all.

- hardening.ts: assertExistingPathForOpen — pre-open stat wired into
  openExternalFile before the reveal-dedupe window is touched (a miss
  must not poison dedupe against a legitimate retry). Only
  ENOENT/ENOTDIR become an honest missing-file failure.
- external-open.ts: reportPreOpenStatFailure — the guard's
  classification lives in the electron-free module with the rest of the
  open logic, so it unit-tests without electron. A miss is reported and
  the route stops; every other stat failure (EACCES on a locked volume,
  ELOOP, Windows EPERM, ENAMETOOLONG) is logged and the OS is still
  asked, so an existing-but-locked file keeps its real error — a stat
  failure never fabricates a miss and never swallows the click.
- preload.ts: the failure dialog merged in #122023 had no preload
  forwarder for hermes:external-open-failed, so every open failure was
  silently swallowed in every window (the optional-chained bridge is
  invisible to typecheck). Adds the onExternalOpenFailed listener plus a
  test tripwire so the dead wiring cannot come back.
- The failure payload gains code: 'missing-file'; the dialog shows
  localized "File not found" copy for it instead of the no-browser
  text (i18n types + en).

Fixes https://github.com/NousResearch/hermes-agent/issues/122027

Co-authored-by: Andrew Ho <andrewho.sf@gmail.com>
Co-authored-by: Enough1122 <10966420+Enough1122@users.noreply.github.com>
2026-09-27 07:10:01 -05:00
Brooklyn Nicholson
68a9817b57 fix(desktop): background Bot Chat re-resumes never navigate
A session.reclaimed (or roster-activity) wake re-resolves the open bot
chat so the next send doesn't eat a stale runtime id — but it went through
the full navigating open, so with the Kanban board (or any other route)
in the foreground the route flipped to the Bot Chat. Background events
must offer, not hijack.

host.openSession gains refreshInPlace: the wake still consults the
registry, dials the owner backend, and stamps the owner hint, but never
calls the core open (route/tile), never re-publishes the bots workspace
scope, and never flips the all-profiles view. It refreshes through the
same levers the SDK's own hydration probe uses — the tile delegate's
resumeTile(refreshTranscript), or the armed requestSessionResume that
the route-resume effect consumes only while the route already points at
the session. A chat that isn't on screen re-opens nothing.

openBotCanonicalChat threads this as background: true, and a background
re-resume never MINTS a missing registry row. The reclaim listener and
the roster-activity refresh both pass it.

Fixes https://github.com/NousResearch/hermes-agent/issues/121874
2026-09-27 07:05:20 -05:00
Brooklyn Nicholson
db1728d35d fix(desktop): keep authored link labels and identifier casing
MarkdownLink dropped formatted link labels: childrenToText only handled
plain strings, so an inline-code label ([`v1.0.1`](url)) fell through to a
title fetch or a URL-slug fallback that title-cased the identifier.
Flatten element children so the authored label wins with exact casing,
and keep the casing of separator-less slug tokens that look like
identifiers (digits, dots, mixed case) instead of title-casing them.

Fixes https://github.com/NousResearch/hermes-agent/issues/121321
2026-09-27 07:03:56 -05:00
Xipong
e0bc2c1ce9 fix(desktop): retain access to past tasks after turns 2026-09-27 07:03:12 -05:00
Brooklyn Nicholson
10b24064dd fix(desktop): drop backdrop blur when the OS asks for reduced transparency
Add a blanket prefers-reduced-transparency gate to styles.css that sets
backdrop-filter: none everywhere, mirroring the existing blanket
reduced-motion rule. Frosted surfaces (composerDockCard via
composerSurfaceGlass, menus, popovers) re-sample their backdrop every
animation frame; on transparent plugin wallpaper themes a late filter
pass paints unblurred and the whole docked area strobes (issue 121910).

The alpha-mixed fills (--composer-fill at 88%) carry legibility on
their own, so nothing changes visually unless the OS accessibility
signal is on.
2026-09-27 07:01:33 -05:00
Brooklyn Nicholson
461b7f0caf test(desktop): pin the docked widthOverride across pane churn
Invariant companion to the narrow-overlay width fix: the docked tree
path (routines register/unregister across botChatOwnsWorkspace edges,
workspace removeTreePane + re-adopt, bots pane unregister/re-register
with enforced dock) preserves the sash-dragged widthOverride — the
narrow overlay was the only path that discarded it.
2026-09-27 06:55:18 -05:00
Brooklyn Nicholson
351d59be8f fix(desktop): narrow edge overlay honours the dragged pane width
The narrow edge overlay sized itself from the pane's declared data.width
(260px for bots), discarding the persisted widthOverride the sash drag
wrote, so every chat-switch reveal snapped the panel back to its declared
width. Size from the same fixedTrackSize resolution the pane's docked
zone uses, extracted as a pure narrowOverlayWidth helper so the
resolution itself is testable under jsdom.

Co-authored-by: kokhlo <47825603+kokhlo@users.noreply.github.com>
2026-09-27 06:55:18 -05:00
Brooklyn Nicholson
a1dcbc20ad fix(desktop): stop transcript images overhanging the next block 2026-09-27 06:55:15 -05:00
Brooklyn Nicholson
5b39269e1a fix(lint): sort imports 2026-09-27 06:54:56 -05:00
Brooklyn Nicholson
2326900129 fix(desktop): import clearClarifyRequest from the clarify store
Follow-up to the stale-approval fix: clearClarifyRequest lives in
@/store/clarify, not @/store/prompts — the wrong-module import slipped
through because the prompts barrel happens to export similarly named
clears. Caught by the vitest run of the new specs.
2026-09-27 06:54:56 -05:00
BlackishGreen33
55e27e5dbe fix(desktop): explain missing profile SOUL.md files
The backend already reports {content, exists} from GET
/api/profiles/{name}/soul, but both SOUL.md editors read only content —
a profile whose personality lives in config.yaml
(agent.personalities.<name>) rendered a blank CodeEditor with no hint
that anything was missing or where the persona actually lives (#89436).

Both surfaces (Profiles view editor and the sidebar Edit SOUL.md
dialog) now show a localized notice when exists is false: no SOUL.md
exists yet, instructions below will create one on save, and config.yaml
personality presets are managed separately. The notice clears on a
successful save; an existing empty file or a read error is never
misreported as missing.

i18n: soulMissing added to every full locale (de/es/fr are override
locales and fall back to English). Component tests cover the
missing/empty/read-error distinction and cross-profile leakage; an e2e
spec drives both editors end to end.

The config.yaml read-back fallback and the dashboard source indicator
stay deferred to the personality-precedence work (#82801).

Salvaged from PR #105201 (author preserved).

Fixes #89436
2026-09-27 06:54:56 -05:00
pasmud
8b7a1620a1 fix(review): surface real gh stderr in PR-create errors
Every non-zero gh exit collapsed to a generic "is gh installed and
authenticated?" — a lie whenever gh was fine and the real failure was
"no commits between main and feature", a missing upstream, or a refused
push (#87731). The user had to drop to a terminal to learn what gh
already printed.

- apps/desktop git-review-ops.ts runGh() now resolves {ok, stdout,
  stderr} (execFile's err.stderr carries the exit's own stderr), and
  reviewCreatePr() prefixes the surfaced message with gh's reason,
  keeping the generic text only when gh reported nothing.
- hermes_cli web_git.py _gh() keeps (ok, stdout, stderr) through the
  same collapse — _run already captured stderr; it was discarded at the
  tuple boundary — and review_create_pr() surfaces a bounded stderr
  tail (400 chars) plus the gh context.

Tests pin the contract with unique stderr markers and non-zero exits on
both wrappers; successful-path return contracts unchanged.

Electron half salvaged from PR #87751 (author preserved); CLI half added
per the issue's acceptance scope.

Fixes #87731
2026-09-27 06:54:56 -05:00
Brooklyn Nicholson
bda04a699a fix(desktop): stop finished turns from showing stale approval prompts
Two clear paths missed, both leaving a per-session approval entry parked
after the turn ended — the floating "↓ needs approval" bar then
reappeared on a session the sidebar already showed as finished, whenever
scrolling unmounted the inline anchor (#86577):

- session.reclaimed now clears the prompts keyed to the reclaimed
  runtime id. The runtime id rotates on every resume, so the NEW
  runtime's turn-end edges can never remove an entry keyed to the old
  one; a reopened conversation remounted the stale bar.
- a running=false session.info for a session we knew was live (busy or
  awaitingResponse) now clears its prompts. The agent loop's finally
  block emits running=false even when a reconnect gap or crash swallowed
  message.complete — the only existing turn-end clear — so the terminal
  edge doubles as an authoritative prompt clear. Bystander sessions are
  untouched: both clears are scoped per session id.

Tests: the reclaimed-runtime approval retires while a bystander keeps
its prompt; a missed-complete turn retires its approval; an idle
session's running=false heartbeat does not.

Re-implemented on the split gateway-event modules from PR #86616
(author credited); the needsInput sidebar-dot half stays with the closed
sibling #86565.

Fixes #86577

Salvaged from a fix by fangliquanflq (GitHub account since removed).
2026-09-27 06:54:56 -05:00
Brooklyn Nicholson
806fa64fd8 fix(desktop): restore transcript video position across remounts
A remounted <video> restarted its clip from the top, which read as the
attachment reloading mid-playback. Remember the last position and pause state
per source (bounded, process-memory) and restore them on mount.
2026-09-27 06:44:32 -05:00
Brooklyn Nicholson
6013f62b6d fix(desktop): keep the image lightbox open across transcript remounts
The ZoomableImage dialog's open flag was component-local useState, so the
routine transcript remounts while a turn streams (render-budget slice
recycling, markdown re-parse) closed a user-opened preview with no gesture.
Hoist it into a nanostore atom keyed by source identity; the store is cleared
on explicit close and on a session switch, never by a remount.

Fixes https://github.com/NousResearch/hermes-agent/issues/123018
2026-09-27 06:44:32 -05:00
Brooklyn Nicholson
7ef36b5518 fix(desktop): keep bare loopback URLs in assistant prose
A loopback URL the model writes into a sentence is user-facing content:
"the dev server is at http://localhost:3000" rendered with the address
deleted. The prose pass stripped any bare loopback URL that lacked a
path segment, so only the fenced preview hand-off path survived. Drop
the prose-strip regex; the only remaining suppression is a fenced block
whose whole body is the loopback URL (LOCAL_PREVIEW_ONLY_RE), which the
preview widget already paints.

Fixes https://github.com/NousResearch/hermes-agent/issues/121683
2026-09-27 06:44:27 -05:00
686f6c61
f039f028f2 fix(desktop): keep a git-installed desktop half without a local package
The package marker named the temp clone, which install deletes. The next
reconcile treats that missing source as an uninstalled package and prunes
the half. Point the marker at the published folder so a remote backend, or
a Desktop-only install, survives until a local agent package can take over.
2026-09-27 06:30:28 -05:00
chelsealong
0c15aa6d40 fix(desktop): make handoff-marker sandbox cleanup failure-safe
Wrap each sandbox's use in try/finally so cleanupSandbox() still runs
when a mid-test assertion throws, instead of only after all assertions
for that sandbox pass. The existence assertion stays a separate
statement after the try/finally.
2026-09-27 06:28:04 -05:00
chelsealong
0dfc80b2b0 fix(desktop): clean up self-test-marker sandbox dirs after the test run
Each POSIX hand-off marker test spawns three mkdtemp() sandboxes under
the OS temp directory (tagged preserved/refreshed/oversized) and never
removed them, so every test run left three directories behind. Traced
the production scripts/desktop-update/posix.sh --self-test-marker path
itself and confirmed it writes the marker/result files directly under
HERMES_HOME, not into a separate staging directory it creates -- the
leaked "hermes-handoff-marker-<tag>-<rand>" directories come from this
test's own sandbox() helper, not from posix.sh/windows.ps1.

Fixes #122130
2026-09-27 06:28:04 -05:00
Brooklyn Nicholson
69ccfa69e4 fix(desktop): map behind: -1 to null instead of clamping the sentinel to 0
The backend update-check endpoint answers behind: -1
(source_check.UPDATE_AVAILABLE_NO_COUNT) when the checkout is behind but
the count can't be computed — a shallow clone without a merge-base or an
unusable GitHub compare. mapBackendCheck clamped it to 0, making that
state byte-identical to the genuinely-up-to-date answer while
update_available still pitched the install, so every behind-based branch
disagreed with the overlay on the same screen and the changelog rendered
"what changed" over zero rows.

DesktopUpdateStatus already types this state as null ("never render it
as a literal number"), so pass the sentinel through as null. The
check-failed predicate is unchanged: it still keys on behind === null
with can_apply, which -1 correctly does not trigger (the check did run).

Also updates the pre-existing updates.test.ts expectation that encoded
the old clamp, and pins the sentinel in updates-backend-check.test.ts.
2026-09-27 06:26:52 -05:00
Brooklyn Nicholson
f4a86aa135 fix(lint): sort named imports in screen-hero 2026-09-27 06:26:52 -05:00
Brooklyn Nicholson
0f15d80a02 fix(contracts): regenerate gateway contract for hermes_not_connected enum 2026-09-27 06:26:52 -05:00
Brooklyn Nicholson
252402dfa2 fix(desktop): Screen on a managed Cloud backend says the release lacks it, not 'update the bot's Hermes'
Fresh fix for #120852 (PR #120860 was deleted; nothing to salvage).

Any display.status -32601 rendered 'Screen needs a newer Hermes / Update
the bot's Hermes to use Screen' on every surface, with no distinction
between a stale git install the user can update and a Portal-managed
release they cannot: against Hermes Cloud the managed tab reports 'latest
release / Up to date' at the same time, which reads as a contradiction.

The roster already knows which kind of backend a bot runs on: the registry
stamps connectionKind: 'cloud' onto the row (annotateBotSource; Connection
Kind 'cloud' is remote-shaped but kept distinct for exactly this kind of
difference). Add isManagedBackend(bot) and pick the copy accordingly:
managed backends get 'Screen is not available on this managed Hermes
release yet' (portalUnavailableManaged, localized in en/ja/zh/zh-hant);
self-upgradable backends keep the update instruction.

Applies to all three surfaces that render the unavailable state: the
Screen pane's empty state, the sidebar portal subtitle, and the hero
caption. The managed release genuinely lacks display.* (methods_display.py
is absent at v2026.9.21), so hiding the surface stays correct — only the
sentence was wrong.

Fixes #120852
2026-09-27 06:26:52 -05:00