Commit Graph

44573 Commits

Author SHA1 Message Date
soyelmismo
ad4c40d8ec catalog: pin antigravity-subscription-directsdk v1.0.4 2026-09-26 17:41:41 -07:00
teknium1
1961a158bf chore(catalog): disclosure for antigravity-subscription-directsdk.yaml (merge-time review edits) 2026-09-26 17:41:41 -07:00
soyelmismo
05cfa2cf26 catalog: bump antigravity-subscription-directsdk to v1.0.3 2026-09-26 17:41:41 -07:00
seref
5939ce460f fix(plugin-catalog): pin Index Network at the admission fix
Move the entry to 0.47.4, where the desktop half is opt-in and the declared env matches the plugin.
2026-09-26 17:41:28 -07:00
seref
b7e5b67557 feat(plugin-catalog): add the Index Network plugin
Pin indexnetwork/hermes-plugin at the 0.47.2 commit so Hermes can install it by catalog name.
2026-09-26 17:41:28 -07:00
Apostol Apostolov
77e3e5cc15 fix(plugin-catalog): repin SDK-only session pin interim 2026-09-26 17:41:17 -07:00
Apostol Apostolov
b0f569b09f feat(plugin-catalog): update drag-to-pin-session to SDK-backed pin 2026-09-26 17:41:17 -07:00
Brooklyn Nicholson
9bb2ea1b5c fix(desktop): make the Floating Composer setting findable as a dock lock
Settings search matches lock, peel and pop out, and the description says what off means now that off is the default.

Co-authored-by: networthexplained <300128320+networthexplained@users.noreply.github.com>
2026-09-26 19:00:41 -05:00
Brooklyn Nicholson
4b1da97299 fix(desktop): require a deliberate drag to peel the composer out
Raise the docked peel threshold from 16px to 48px so a brush of the grab ring no longer undocks it.

Co-authored-by: networthexplained <300128320+networthexplained@users.noreply.github.com>
2026-09-26 19:00:41 -05:00
Brooklyn Nicholson
e928574ab3 fix(desktop): lock the docked composer to the dock by default
Drag-to-float gestures now default off on a fresh install. A composer that was already floating before the flip keeps its gestures, and a stored choice wins either way.

Co-authored-by: networthexplained <300128320+networthexplained@users.noreply.github.com>
2026-09-26 19:00:41 -05:00
Brooklyn Nicholson
802ae8544b test(docker): a mounted host dir follows the fallback mount 2026-09-26 18:27:10 -05:00
brooklyn!
b686f1b40b fix(docker): do not invent a workspace mount for a raw Windows override
The mount flag is off in that case. The override must fall back to the
sanitized config cwd, not docker run -w /workspace.
2026-09-26 18:27:10 -05:00
brooklyn!
37933ff662 fix(docker): ignore a non-string host root when translating a mounted path
A MagicMock env attribute is not a workspace. Treating it as one crashed
the Windows search path on startswith.
2026-09-26 18:27:10 -05:00
brooklyn!
1a72042115 fix(docker): bind a Windows workspace when /workspace is already claimed
A volume that already owns /workspace skipped the configured working
directory, so tools treated that host path as unmounted. Bind it at a
second mount, or point tools at the volume that already has it, for any
drive path.
2026-09-26 18:27:10 -05:00
Brooklyn Nicholson
50873d2154 fix: picker/input cluster — vendor casing, variant tags, submenu keyboard path, stale moa pick, CJK search star
Six fixes for the wave-7 picker/input cluster:

DeepSeek -> "Deepseek") and left the gemini- branch's words lowercase
("Gemini 2.5 pro"). Vendor casing + parameter counts now applied after
title-case (GLM, DeepSeek, MiniMax, OpenAI, ERNIE, MiMo, BGE, VL, IT,
FP8, AI; 8b -> 8B, a3b -> A3B), and the gemini branch title-cases like
every other branch.

and was unreachable by keyboard (rows are highlighted, never DOM-focused,
so Radix's own ArrowRight never fires). The chevron is now visible on
every model row and ArrowRight (caret parked at query end) hands focus
to the highlighted trigger and opens its sub; ArrowLeft returns focus
to the search field. Consolidates #86968 + #104532.

-fast/-thinking/-preview ids to the base label. The tag now rides the
display name on every surface, and formatModelPillLabel no longer
doubles Fast for a -fast variant id.

all MoA presets were disabled: manual picks are sticky by design
(d595e636c8), but the virtual moa provider's catalog row disappears
entirely once no preset is enabled, so that one absence is
authoritative (moaPickRemoved) and the pick reseeds from the profile
default. Narrow moa-only exception — no general catalog diff.

token (nimb -> nimb*); none of the CJK routes can honour it (bigram and
trigram routes quote tokens so the star matches literally; LIKE has no
star wildcard at all), so CJK searches returned zero results. The star
is now stripped per token on the CJK path only.

measure() effect deps (stale measurements after toggling Inbox style)
and the card estimate undershot the four-line/wrapped-title worst case
(74px), painting rows over their neighbours on cold start. Card
estimate raised to the worst-case-covering 96px and the deps fixed.
2026-09-26 18:16:44 -05:00
hermes-seaeye[bot]
13f6b46daa fmt(js): npm run fix on merge (#124529)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-26 23:12:07 +00:00
Hermes Agent
b24b8149dd fix(icons): give the dev Dock its own mac-grid png
Linux uses apple-touch-icon.png as the window icon and Nix requires it to
match the full-bleed launcher icon, so keep it full-bleed and point the
dev-only app.dock.setIcon at assets/icon-mac.png instead.
2026-09-26 18:10:54 -05:00
Hermes Agent
c8094dc399 fix(icons): put the dev Dock icon on the mac grid and enlarge the mac art
Dev runs replace the Dock icon with public/apple-touch-icon.png, which the
generator rendered full-bleed, so it drew ~24% larger than its Dock
neighbors. Render it from the mac-grid master like the icns targets.

On the 824 grid the plate matches peers, but the girl inside a white tile
with a ring read small; scale her 1.12x about the plate center for every
mac target.
2026-09-26 18:10:54 -05:00
Hermes Agent
f4795c922c fix(desktop): tell the user when a clarify request never reached the app
A pending clarify card waits on its gateway clarify.request. When that
frame is lost the card sat as a disabled preview until the 300s timeout
with no hint of what went wrong.

After a 4s grace the card now asks the owner socket for
session.events.since, which re-delivers the session's open requests, so a
request the backend still holds parks and the card goes live. If nothing
turns up, single and batch cards show an inline notice (all locales)
pointing at Stop, and drop the dead Skip/Continue actions.
2026-09-26 18:00:36 -05:00
liuhao1024
00c8e5ca48 test(desktop): cover the one-entry batch clarify shape end to end
Since the questions[]-only schema (#95907) every single question is a
one-entry batch on both the tool args and the gateway wire, yet no test
exercised that shape (called out in #98645). Lock the behavior down at
both layers:

- unit: a one-entry batch renders the batch card (not a blank/spinner
  single card) both with the wire already parked and when the request
  lands after the tool row, and answers with the qid-keyed lock
- e2e: a SINGLE_BATCH trigger drives the real chain (composer -> gateway
  -> agent -> clarify tool -> clarify.request -> renderer) through mount,
  pick, confirm, and settle for questions.length === 1

The e2e mock's trigger routing also learns to scope its has-tool-result
guard to the answering turn's own question text: the existing any-tool-
result check would false-positive once a second scripted clarify shares
the conversation history.

Adapted to main: the mock server now lives in tests-js/scripts, and a batch
confirm answers with clarify.lock.
2026-09-26 18:00:36 -05:00
Hermes Agent
2f86fae3be fix(desktop): forward --profile on the bundled-install launch too 2026-09-26 18:00:30 -05:00
Brooklyn Nicholson
fd9350a941 fix(desktop): sort the launch-profile import 2026-09-26 18:00:30 -05:00
brooklyn!
8684bf3ddc fix(desktop): forward --profile into the packaged desktop launch
Electron booted from active-profile.json and ignored argv. hermes
desktop and hermes -p <name> desktop never appended --profile, so
the packaged app kept the stored profile.

Parse both --profile spellings before startHermes, persist that
name, and append the same flag on the packaged launch. A missing
flag does not change the stored profile.
2026-09-26 18:00:30 -05:00
Hermes Agent
6d94896c78 chore: map contributor emails 2026-09-26 18:00:17 -05:00
finn763
a164569429 fix(agent): admit and sync gateway-staged attachments on remote execution backends (#110174)
Desktop paste/file attachments land in Hermes-managed staging dirs on the
GATEWAY (composer-pastes/ for large text pastes, attachments/ for dropped
files), but on the Remote SSH topology the workspace root (TERMINAL_CWD) is a
path on the SSH HOST - the two filesystems are fully disjoint, as the issue
thread confirms. Two gaps combined to reject every staged attachment with
"path is outside the allowed workspace":

- _resolve_path admitted only allowed_root + composer-paste roots, so a
  gateway-staged attachments/ path was refused outright. Admit the
  _CACHE_DIRS staging roots (attachments/, images/, cache/*, composer-pastes/)
  via a helper that asks get_cache_directory_mounts - the gateway's OWN
  payload is never a workspace escape, and the path-traversal and
  credential-deny guards in _ensure_reference_path_allowed still run after.
  Anything else outside the workspace stays blocked.
- composer-pastes/ was missing from _CACHE_DIRS, so its bytes never reached
  the remote: ssh/daytona/vercel_sandbox sync via iter_sync_files ->
  iter_cache_files, and to_agent_visible_cache_path only translates mounted
  dirs - a paste attached on a fresh session dangled on the remote host.

Tests cover the disjoint-filesystem SSH topology end-to-end (text inlines,
binary renders the synced ~/.hermes path), the still-refused stranger path,
local-backend unchanged, and the composer-pastes mount+sync enumeration.

Consolidates PR #110387 by Finn763 (the _agent_staged_path guard widening and
the SSH-topology tests, adapted to the current _ensure_reference_path_allowed
ordering) with PR #103412 by ericmaddox (whose mapping insight is subsumed by
the _CACHE_DIRS entry, which fixes both the sync and the translation).

Co-authored-by: ericmaddox <ericmaddox@users.noreply.github.com>
2026-09-26 18:00:17 -05:00
Muhammed Emin Boydak
a723194c1e fix(desktop): reserve the window-controls band on the narrow sidebar overlay (#110033)
The narrow-viewport overlay for a collapsed zone is `absolute inset-y-0` —
it starts at the viewport's top edge with its tab strip (SESSIONS | BOTS) as
the first child, so on macOS the strip slides under the traffic lights.
Docked zones already reserve that band (TreeGroup: useWindowControlsOverlap
-> paddingTop plus an absolute [-webkit-app-region:drag] spacer; top-edge
zones use usePanelTitlebar), but the overlay used neither.

Reserve it the same way: measure the native controls rect against the
overlay element and pad the strip below it, keeping the band a window-drag
target via the drag-region spacer. The overlay's data attribute now carries
the revealed pane id (it was a constant empty string), which the regression
test uses as its selector.

Salvaged from PR #110034 by Muhammed Emin Boydak (the overlap hookup, the
paddingTop + drag spacer, and the 34px-reservation test), adapted to the
current file (NO_PANE_GROUP import, per-pane data attribute).

Fixes #110033.
2026-09-26 18:00:17 -05:00
Hermes Agent
a6dadb83ba fix(desktop): strip citeturn web-citation transport markers from prose (#120587)
Model output can arrive carrying Gemini-style grounding citation markers:
private-use delimiters U+E200/U+E201 wrap a `citeturn<n>search<m>` id list
(U+E202 separates ids) - e.g. `\uE200citeturn0search11\uE202turn2search0\uE201`.
Desktop had no rule for that shape (CITATION_MARKER_RE only strips bare
numeric `[n]` markers), so the private-use code points painted as replacement
glyphs - the reported "triple bars" - and the `turn…search…` ids rendered as
literal prose, wrapping across table cells and obscuring the answer.

Add CITATION_TRANSPORT_MARKER_RE and strip it in rewriteProseSegment, the
same shielded path the numeric marker rule rides: inline code and math spans
split out first, so `$\sqrt[3]{8}$` and quoted marker text are untouched, and
the bare no-delimiter alternative only fires on the `cite` head so plain prose
and stray private-use characters (icon fonts) are left alone. A marker that
cannot be resolved to a source is dropped, never invented into a link -
matching the reporter's own expectation. Mid-stream flushes (closing U+E201
not yet arrived) are covered by the optional-tail shape.

Backend-side emission (which search provider leaks the markers into model
text) remains unisolated, as the report itself notes; the display-layer strip
is justified regardless.

Fixes #120587. No external PR existed (the catalog's linked PR #120592 is a
dead reference).
2026-09-26 18:00:17 -05:00
Phantomthedog
d38d2a8f0a fix(desktop): keep a bare URL's full text visible in chat markdown (#121007)
MarkdownLink nulled fallbackLabel whenever the link text matched the target
URL — exactly the bare-autolink case — so PrettyLink fell through to
urlSlugTitleLabel and rendered a host-only label (`ncpssd.org` for
https://www.ncpssd.org/), with the address readable only via hover/inspect.
The user could not read an address sent in chat.

The link's own text is always a legitimate fallback label; pass it through.
Labeled links are unchanged: their authored label already wins display, and
that shape (a label hiding the address) is PrettyLink's documented contract,
not a bug.

Salvaged from PR #38213 by Phantomthedog (the fallbackLabel change and the
localhost/example.com render tests), reworked to keep the change scoped to
the bare-autolink shape and reshaped into an end-to-end
MarkdownTextContent test alongside the existing session/filelinks suites.
2026-09-26 18:00:17 -05:00
finn763
71b79122ca fix(desktop): hand the popped-out Browser its tab from shared storage (#119850)
A popped-out Browser window is a fresh renderer: no in-memory atoms cross
the window boundary, and no session ever pushes a rail scope into it (the
controller skips session/preview watching there), so its scoped previewTabs
view started empty and PreviewTilePane rendered null — the shell spawned but
never painted, matching the reported black window. Root cause is the
per-profile rail scoping from c996d1c088, not the GHSA window-open policy
the reporter suspected: the window/IPC handoff itself is fine.

Three coordinated moves in the store:

- adoptPersistedBrowserTab now reads every profile bucket (plus the
  pre-scoping single-array shape; the old decode returned [] for bucketed
  storage, so even the sibling URL sync was dead) and, when the tab is not
  in this renderer's view, re-homes the view onto the OWNING bucket instead
  of splicing the tab into 'default' — a splice would duplicate the popped
  tab into the primary profile's rail. When the tab IS present (the docked
  mirror on pop-out close), it adopts the newer URL/label as before.
- PreviewTilePane calls that adoption from a browser window when its tab is
  missing from the view.
- The persist subscriber no longer echoes module-init emissions back over
  storage: nanostores fires subscribe immediately, so every renderer used to
  clobber its un-adopted record (a legacy single-array store was wiped
  before pendingLegacyTabs could adopt it; a 'default'-only bucket store was
  removed the same way), and the view is now seeded from the renderer's own
  bucket — which also restores the primary profile's rail at boot, since
  setPreviewScope('default') early-returns on the initial viewKey.

Salvaged from PR #120110 by finn763 (diagnosis + adoption mechanism +
creation-emission guard, re-homed onto the owner bucket to avoid the
duplicate-bucket write.
EOF
)
2026-09-26 18:00:17 -05:00
Hermes Agent
b85406d5f3 fix(update): remove the npm logs dir even when the retry fails 2026-09-26 17:59:59 -05:00
Hermes Agent
625f1d8926 fix(update): retry node dependency preparation after ENOTEMPTY 2026-09-26 17:59:59 -05:00
Hermes Agent
b42474b0d1 style(desktop): blank lines around statements this PR adds (eslint warnings) 2026-09-26 17:59:52 -05:00
Hermes Agent
3836710cfa fix(desktop): give a pre-save sign-in the same jar its saved connection reads
The unknown-id shortcut gave any unsaved draft a private OAuth jar, so a
pre-save cloud sign-in wrote persist:hermes-remote-oauth-conn-<id> while the
saved cloud connection reads the shared persist:hermes-remote-oauth. Send the
draft's kind/authMode with the request and only grant a private jar to remote
OAuth drafts; everything else falls back to the legacy jar.
2026-09-26 17:59:52 -05:00
Hermes Agent
9d2eca0560 chore(contributors): map BorgWrightpcalac@outlook.com to DevEverything01 2026-09-26 17:59:52 -05:00
Brooklyn Nicholson
1e1e47373c fix(desktop): assert the draft identity in the cloud sign-in IPC contract test
The #99989 salvage threads {connectionId, label} through
oauthLoginConnectionConfig; the pre-existing #89529 test still asserted
the single-argument call shape. Update it to the two-argument contract.

Co-authored-by: Together <BorgWrightpcalac@outlook.com>
2026-09-26 17:59:52 -05:00
Brooklyn Nicholson
3d96cdc875 fix(desktop): expect the current per-connection partition suffix in the pre-save login test
The cherry-picked test expected 'conn:<id>' but main's partition prefix is
'persist:hermes-remote-oauth-conn-<id>' (PR #99992 was written against an
older naming). Align the assertion with the shipped prefix.

Co-authored-by: Together <BorgWrightpcalac@outlook.com>
2026-09-26 17:59:52 -05:00
Together
67d39cc75d fix(desktop): write pre-save gateway sign-in sessions to the connection's own jar
Settings → Connections lets a draft remote gateway be signed in BEFORE it
is saved. The login IPC carried only the URL, so resolveOauthPartition()
matched against the on-disk registry, found no entry, and fell back to the
legacy shared jar: the fresh session was invisible to the saved connection
(which reads its own per-connection jar, #92183), and in the same-host
setup it also evicted the other gateway's cookie in the shared jar.

The login IPC now carries the draft's identity. An explicit connectionId
wins; otherwise the main process mints the id the save will use
(connectionIdForPendingLogin, same derivation as normalizeConnectionInput)
and returns it so the editor pins the id into the draft. The resolver
resolves a named connection by identity: a known entry follows the
existing rules; an unknown id is a pending non-primary oauth remote —
editor saves never promote a fresh entry to primary — and gets its own
partition up front.

Fixes #99989

(cherry picked from commit 054a7af5e49403660c29c152440c0d3bb46d3871)
2026-09-26 17:59:52 -05:00
Brooklyn Nicholson
8862284209 fix(desktop): refresh window state on connection republish
A republished connection reply carried only the cached backend descriptor,
whose getWindowState() spread was baked in at process cold start
(startHermes caches the backend for its lifetime). A window that entered
fullscreen after the first dial got a stale isFullscreen: false on every
republish — reconnect, sleep/wake, gateway switch, backend restart, ⌘R
reload — so the renderer's live fullscreen flag was overwritten and the
titlebar's traffic-light inset reverted to the windowed offset while still
fullscreen. Toggling fullscreen fired a live push again and "fixed" it,
which is exactly the intermittent behavior reported (#102451).

Read the calling window's state when the reply is built, via the new
connection-window-state helpers (liveWindowState/overlayWindowState), so
every shape connectDesktopProfileRoute returns — registry-scoped,
primary-resolved and bare — carries live values consistent with the
hermes:window-state-changed live-push path. Both connection IPC handlers
now pass their sender through; without it the lookup silently falls back
to mainWindow, so a secondary window would be told the primary's chrome —
the same defect for secondary windows that the primary had for stale
snapshots.

The secondary-window fullscreen leak (bindWindowChromeEvents) is already
fixed on main (window-chrome-events.ts threads the bound window through
sendWindowStateChanged), so this is the remaining surface.

Tests: connection-window-state.test.ts covers sender-window state reads,
primary fallback, destroyed/no-window degradation, and the overlay
behavior on all three reply shapes (DI over BrowserWindow.fromWebContents
and getWindowState — no source-text assertions).

Fixes #102451

Co-authored-by: ryrenz <163799701+ryrenz@users.noreply.github.com>
2026-09-26 17:59:52 -05:00
chelsealong
b6b003c14f fix(desktop): stop runtime model/provider heartbeats from overwriting the composer selection
session.info heartbeats mirror the runtime's resolved model/provider (e.g.
the generic `custom` billing class a named provider resolves to) into the
view through setCurrentModel/setCurrentProvider, which also persist to the
composer's sticky localStorage selection. Every heartbeat therefore
overwrote the user's actual pick, so a later new chat followed the
last-seen runtime class instead of the selection or the Settings default.

Add setCurrentModelTransient/setCurrentProviderTransient (mirroring the
existing setCurrentCwdTransient pattern) and use them in
syncRuntimeMetadataToView, which is the only caller reached from periodic
heartbeats rather than an explicit user pick.

Fixes #102793

(cherry picked from commit d8049b4ba40a59becac59a8f8ccbddcf8b9d58cc)
2026-09-26 17:59:52 -05:00
chelsealong
7e08c4524a fix(desktop): key the failed-install Escape effect on a boolean, not the error string
(cherry picked from commit 3a548702f24bd68e44d94850cd3f141bcf8c3b51)
2026-09-26 17:59:52 -05:00
chelsealong
6f8dd59620 fix(desktop): add dismiss affordance to the failed install footer
The failed/cancelled state of the first-launch install overlay only
offered "Copy output" and "Reload and retry" -- retry clears the
latched failure and reloads, re-entering bootstrap. There was no way
to back out of a deliberately cancelled install short of quitting.

Add a Close button (and Escape) that dismisses the overlay locally,
without touching main's bootstrap state or reloading, so the app
falls through to whatever view sits behind it.

(cherry picked from commit 68dffe3db385735cedc26559f0bac695b20bf42a)
2026-09-26 17:59:52 -05:00
Andrex Ibiza, MBA
1498ae0233 fix(desktop): remove deprecated console-message arguments
Use Electron 40's event-object console-message contract, retain one-argument listener arity, and report malformed signature drift once so renderer logging cannot fail silently.

Co-authored-by: CupaJ12 <108900676+CupaJ12@users.noreply.github.com>
(cherry picked from commit 378701b2b7af1e0a3accfaec7b8daa46c30832d6)
2026-09-26 17:59:52 -05:00
Brooklyn Nicholson
ac80df1410 fix(credits): stop desktop reap/resume from re-announcing the same usage band
Desktop rebuilds the AIAgent per turn (idle reap -> next message re-mint),
and agent_init.py gives every rebuilt agent a brand-new, empty _credits_latch
via new_credits_latch(). seed_credits_at_session_start() -> _hydrate_seed_state()
then unconditionally primes latch["seen_below_90"] on that fresh latch and
evaluates once -- correct for a genuinely new session opening mid-band, but on
a reap/resume rebuild it makes evaluate_credits_notices() see
shown_band=None vs. current_band=<the same band as before>, so it re-fires
"You've used $X of your $Y cap" on every message even though the user already
saw that exact notice moments ago on the previous incarnation of the same
session (#101578).

agent.session_id is stable across these rebuilds even though the agent object
and its latch are not, so add a small process-lifetime cache
(_seen_usage_bands, bounded to 500 entries, MRU eviction) keyed by session_id
that remembers the last usage_band actually shown. _hydrate_seed_state()
restores it into the fresh latch before evaluating, so a rebuild with unchanged
usage stays quiet, while a rebuild after a genuine crossing (recorded via the
same warm-path write in rate_limit_credits._emit_credits_notices, the single
chokepoint both the seed and live-header paths share) still fires normally.
Deliberately not persisted anywhere durable -- a real process restart is a real
"session open" and should still warn immediately, matching the existing
cold-start seed behavior for a session that opens already in a band. An agent
with no session_id (plain CLI, never rebuilt) falls back to the pre-fix
behavior unchanged.

Tests (tests/agent/test_credits_cold_start.py): a rebuild with the same
session_id and unchanged usage does not re-fire; a rebuild after a genuine
band change still fires (and clears the old key); a different session_id is
never suppressed by another session's history; an agent without a session_id
degrades to the old always-prime behavior without raising.

Fixes #101578

Co-authored-by: Edizzier <umit.ediz@hotmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 17:59:52 -05:00
brooklyn!
b9d5e4d17f fix(docker): remap a mounted host session cwd to /workspace
A Desktop Docker session that registers an absolute host directory
such as /mnt/... or /srv/... as its cwd skipped the /Users|/home/
drive-letter heuristic, so the mount check never ran and commands
were wrapped with cd to that host path. Classify the mounted host
directory as unusable before that heuristic, and remap it to
/workspace in the live-env write and the per-command resolver.
2026-09-26 17:34:46 -05:00
Hermes Agent
26780d55ae fix(desktop): evict the whole subtree on a scoped clearProjectDirCache (#122083 review)
The scoped clear deleted only the root's own key from each cache, but this
PR's listing pass now keys gitRootCache on every listed directory and
nestedRepoCache on every entry the ignore rules probe — all strict
descendants of the root, never the root key itself. The refresh paths in
use-project-tree therefore served stale nested-repo and git-root answers:
git init inside a parent-ignored directory stayed hidden until a full
no-arg clear (connection change or relaunch).

Evict every key at or under the root, for all three caches, matched on a
path boundary via isUnderPath so /repo does not evict /repo2, and only
within the current connection's cache keys. Regression test covers the
refresh-makes-it-visible case and the sibling-root boundary.
2026-09-26 17:25:51 -05:00
Hermes Agent
f0288f2272 fix(desktop): require proof of absence before dropping a drained queue entry (#122083 review)
The drain-exhaustion drop treated 'hint lookup returned undefined' as
'the session is gone', but getSessionOwnerHint also returns undefined for
TWO OR MORE routes (cloud gateway plus local backend) — a positive
liveness signal misread as absence. Use the plural getSessionOwnerHints
(≥1 route keeps the entry), and refuse to drop while the loaded list page
cannot prove absence either ($sessionsLoadError, or any profile in
$sessionProfilesTruncated — the sidebar list is one page, so a session
below the fold is unknown, not deleted). 'Maybe' never deletes; the
queued prompt stays for a manual send.
2026-09-26 17:25:51 -05:00
Hermes Agent
80f9c698b9 chore: map contributor emails 2026-09-26 17:25:51 -05:00
Hermes Agent
3d9040c188 fix(desktop): fold i18n queueDropped keys + dedupe isSessionOwnerRoute into their fixes 2026-09-26 17:25:51 -05:00
Hermes Agent
aac12dfda5 fix(desktop): stop the queue-drain exhaustion banner from replaying after restart (#98015)
A prompt left in the composer queue when the app exits is drained after
relaunch against a session runtime that no longer exists. The submit
path already resumes by stored id (#106986 gated the drain on session
discovery for exactly this), but when the stored resume itself refuses
(deleted from another surface, backend restart rejecting), the drain
retried MAX_AUTO_DRAIN_ATTEMPTS times, showed the queueStuck ERROR
banner ('message not sent' — reading as data loss), and because the
failure counter was process-local while the queue persists in
localStorage, every subsequent launch replayed the whole cycle.

- Persist the per-entry drain-failure budget with the queue
  (QueuedPromptEntry.drainFailures): the retry ladder is unchanged
  within a process, but an exhausted entry is skipped after restart —
  no attempts, no banner. Queueing a fresh prompt lifts the budget off
  the session's entries, and a manual send from the queue panel clears
  it (clearQueuedPromptDrainFailures), mirroring the composer's
  in-process counter reset on the same gestures.
- At exhaustion, distinguish gone from known (discovery has settled,
  so the loaded list plus owner hints are authoritative): a session no
  row or hint answers to can never accept the queued prompt — drop the
  entry and notify quietly (info, new queueDropped strings). A session
  that still exists keeps its entry (real data, manually sendable) and
  gets the queueStuck notice downgraded from error to info.

Regression tests: gone-session drop with a quiet notice, known-session
retention, and a restored-after-exhaustion queue that neither attempts
nor notifies. i18n: queueDroppedTitle/Body added to all nine locales.

Closes #98015.
2026-09-26 17:25:51 -05:00
MichaelZelbel
ae29183cad fix(desktop): keep ignored nested repositories visible (#99861)
Implementation from the issue reporter's live-verified v3 patch
(MichaelZelbel), posted on the tracker after their first patch (and PR
#101115) proved unshippable: the readDir bridge strips `.git` entries
(FS_READDIR_HIDDEN in electron/fs-read-dir.ts), so any implementation
that probes directory listings for a `.git` entry passes mocked tests
and does nothing in production.

Two changes to filterIgnored:

- Detect nested repo/worktree roots via the existing gitRoot bridge
  (git rev-parse --show-toplevel): a directory is its own repo's root
  when gitRoot(dir) resolves to dir itself. Covers worktrees (a .git
  file) for free, works even though listings never show .git, and only
  probes entries the parent's ignore rules would actually hide.
- Re-anchor the ignore-rule chain at the nearest git root of the LISTED
  directory (falling back to the project root): git applies ignore
  rules only inside their own repository, so a parent's `dev/*` must
  not empty a nested repo one level down — only that repo's own
  .gitignore chain governs.

Probe results are cached like the gitignore reads and cleared with the
same cache-invalidation path. Coexists with the per-project
showIgnoredFiles opt-in (checked first — it skips the whole pass).
Tests: nested clone, worktree-style .git file, browsing inside a
nested repo, plus the existing suite.

Closes #99861.
2026-09-26 17:25:51 -05:00