Commit Graph

44559 Commits

Author SHA1 Message Date
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
Jean-Dominique Vidjanagni
7772e7a332 fix(desktop): re-home session tiles on compression tip rotation — one surface per conversation (#98622)
Salvage of PR #99467 by Jean-Dominique Vidjanagni (DomVidja, the issue
reporter), rebased onto current main.

After auto-compression rotates a session's stored id, tiles stayed
keyed on the pre-rotation segment while the rest of the app (selection,
route, composer) moved to the new tip — the same conversation rendered
as two tabs (identical or differently titled, since each tip is titled
independently), while state.db holds one row per lineage.

- rekeySessionTile(previousId, nextId, runtimeId?): re-home any open
  tile keyed on the pre-rotation id to the new tip, preserving dir/
  anchor/before placement so the pane mirror re-docks in the same slot.
  Main-vs-tile reconciliation is scoped to the same Sessions workspace/
  owner; Bot tiles and distinct-backend-route tiles stay independent
  surfaces. When the new tip is already the main selection the stale
  tile is dropped (main OR tile — never both). A rotation for a
  background runtime re-keys the persisted profile bucket owning it
  without replacing the active profile's atom.
- Called from handleTransition AND the cache-path rotation in
  useSessionStateCache (ungated on the active runtime — a background
  tile's conversation rotates too).

Rebase notes: main's stricter #97511 owner-provenance semantics
(sessionOwnerByRuntimeId as the LAST rung of knownOwnerForSession,
secondaryProfileOwnerForEvent fail-closed) are kept; the PR's runtime-
owner-first chain was dropped in favor of it. Regression coverage
covers the background-runtime re-home, the cross-profile persisted
bucket, main-selection drop, detach preservation, and cache-vs-
route-follow ordering.

Closes #98622.
2026-09-26 17:25:51 -05:00
zqy1-1
9e81bed009 fix(desktop): address review feedback on Command Center load-more
- Gate `$sessionProfilesTruncated` subscription behind the Sessions tab

  with `useStoreSelector` and a stable empty object, matching the

  existing re-render discipline for `$sessions` / `$pinnedSessionIds`.

- Show the real page size in `SidebarLoadMoreRow` so the label reads

  "load 50 more", consistent with the sidebar.

- Hide the load-more affordance when `onLoadMoreSessions` is not wired.
2026-09-26 17:25:51 -05:00
zqy1-1
ee73ba46b6 fix(desktop): add 'load more' to Command Center sessions list
The Command Center Sessions tab renders the shared $sessions store, which
is paginated by the sidebar with a default window of 50 rows. The sidebar
exposes a 'load more' control (SidebarLoadMoreRow + bumpSessionsLimit) to
grow that window, but the Command Center has no equivalent, so sessions
beyond the first page are unreachable from it.

Reuse the existing sidebar affordance: accept an optional onLoadMoreSessions
prop (wired to loadMoreSessions at the call site), subscribe to
$sessionProfilesTruncated, and render SidebarLoadMoreRow in a fixed bar
below the scrollable Sessions list (bottom-right, outside the scroll
container so it stays put while scrolling). Clicking it bumps the shared
limit and refetches, so both surfaces stay in sync.
2026-09-26 17:25:51 -05:00
686f6c61
b986aabcce fix(desktop): render /new sessions as siblings not branches (#99648)
Salvage of PR #99679 by 686f6c61, rebased onto current main.

/new and idle/daily resets keep parent_session_id for durable lineage,
and the sidebar's flattenSessionsWithBranches nested on that field
alone — so a platform's chats collapsed into one growing nested chain
of branches even though none of them were /branch forks. The backend
records the distinction on disk already (model_config._reset_from vs
_branched_from, gateway/session_recovery.py:433) but list payloads
strip model_config before any UI can read it.

- forkParentId(): nest only genuine forks — _branched_from wins, a
  _reset_from parent means top-level sibling, legacy/optimistic rows
  with only parent_session_id keep nesting.
- _session_row_dict lifts _reset_from/_branched_from out of
  model_config so compact list rows (which strip that heavy field)
  carry the distinction; tui_gateway project-tree rows project the two
  markers too.
- Optimistic desktop /branch rows stamp _branched_from so the flat
  render stays correct before the authoritative row arrives.

Disk lineage is unchanged. Tests: a _reset_from chain renders flat,
a genuine fork still nests beside a reset sharing the same parent,
and list_sessions_rich promotes both markers (also under
compact_rows=True).

Closes #99648.
2026-09-26 17:25:51 -05:00
Konstantin Khlopkov
aaaf924a5c fix(desktop): stop re-persisting an exhausted session after cleanup (#98467)
Salvage of PR #98475 by kokhlo, rebased onto current main (deps array
re-merged after display.resume_last_session / disk-plugins landed).

The cleanup effect in useDesktopIntegrations drops the remembered
navigation once a bounded resume retry exhausts, but the persistence
effect re-runs on ordinary session-list refreshes with no idea the
session is stranded — it writes the dead routed id straight back into
hermes.desktop.lastSessionId/lastRoute, so every relaunch reopens the
resume-error screen until the user manually hits New chat.

Treat resumeExhaustedSessionId as a write barrier in the persistence
effect: skip the setRememberedSessionId/setRememberedRoute writes while
the routed id is the exhausted one, and add the latch to the effect's
deps. Regression test covers the routed-at-the-dead-session shape plus
a later session-list refresh that re-runs only the persistence effect.

Closes #98467.
2026-09-26 17:25:51 -05:00
teamster22
5c3172005d fix(desktop): resolve tab titles from the cron and messaging slices (#95096)
Salvage of PR #102346 by teamster22, rebased onto current main.

The sidebar fetch splits its rows into three mutually exclusive
source-scoped slices ($sessions recents / $cronSessions /
$messagingSessions), and recents excludes every messaging and cron
source. All three tab-title resolution sites (tileStoredRow,
syncWorkspaceTitle, workspaceDragPayload) searched recents only, so a
telegram/matrix/discord-origin session tab read "New session" forever
even while its sidebar row showed the real title — a partition miss,
not a timing miss, so no activity could ever fix it.

Route the three resolvers through ownerLookupSessionRows() (the
three-slice concat added for this bug class in #95633, which preserves
$sessions' array identity when the other slices are empty) and add
$cronSessions / $messagingSessions to watchSessionTiles' `also` array
and syncWorkspaceTitle's listeners so the late-arriving slices re-sync
the label.

Closes #95096.
2026-09-26 17:25:51 -05:00
Hermes Agent
627bf49baa fix(process): kill a re-adopted PID whose start time is unreadable 2026-09-26 17:19:34 -05:00
brooklyn!
7550800d8b fix: re-attach a live recovered PID instead of inventing an exit
Checkpoint refresh treated a failed start-time check as a collected exit
and queued a completion. A live PID whose start time matches, or whose
start time cannot be read, stays running. A reused PID is closed without
being signalled. A gone PID is pruned. A completion is emitted only when
an exit status was collected.
2026-09-26 17:19:34 -05:00
Hermes Agent
17c6a566f5 fix(cron): reconcile run history per execution and read it in the owner's scope
Review of the run-history fallback:

- Merge agent sessions with script-only output docs per execution instead of
  branching on 'any sessions exist': a job converted to no_agent keeps its id
  (update_job supports it), so a surviving historical agent session must not
  hide newer script-only fires. Same-execution rows dedupe by session span.
- Decode output-doc filenames and read the execution ledger inside the owner
  profile's home scope: hermes_time resolves the configured zone through the
  current HERMES_HOME, so a cross-profile request decoded with the
  dashboard's zone and read the dashboard's executions.db.
- Attach per-run status from the execution ledger (one row per fire) matched
  by claim window instead of last_run_at proximity, and surface terminal
  attempts with no surviving doc as their own rows — the latest failed run
  no longer disappears behind (or relabels) an older successful document.
- Read output docs with encoding='utf-8-sig' (Windows footgun: PowerShell and
  some editors BOM files; plain utf-8 breaks on BOM-prefixed docs).

Three regressions cover the converted-job merge, the per-profile timezone
decode, and the missing-newest-doc case.
2026-09-26 17:19:04 -05:00
Hermes Agent
4f11ac246c fix(desktop): render script-only cron jobs in the detail view and sidebar
The cron detail rendered only the prompt, so script-only (no_agent) jobs —
which have prompt: "" — showed a blank description; the script field, the one
meaningful detail, was never displayed. The detail now shows a muted 'script'
badge next to the state pill and renders the script under a Script label when
the prompt is empty. Output-doc run rows (source='cron_output') have no backing
session, so they render as plain rows without a chat-navigation affordance in
both the detail run history and the sidebar quick-peek.

Fixes #42433 (blank-detail and script-badge facets)

Salvaged from #77382 by @andrexibiza (jobDescription model + detail badge/label
and tests), adapted onto current main and extended to the sidebar peek.

Co-authored-by: andrexibiza <84248988+andrexibiza@users.noreply.github.com>
2026-09-26 17:19:04 -05:00
Hermes Agent
c2182f60c1 fix(cron): show run history for script-only jobs via per-fire output docs
Script-only (no_agent) jobs deliberately skip SessionDB, so their run history
was permanently empty — the desktop showed "No runs" next to hundreds of
completed fires. When no session rows exist, the runs endpoint now falls back to
the job's output docs under cron/output/<job_id>/, one row per fire, with the
latest status prefixed; with no surviving docs but a recorded last_run_at, a
single metadata row is surfaced instead. Rows mirror /api/sessions shape with
source='cron_output' and a cron_output: id prefix that cannot collide with
SessionDB cron_{job_id}_* session ids.

Filename timestamps are read back with hermes_time.get_timezone() — the same
configured zone save_job_output writes them with — not a fixed-offset snapshot
of today's local offset (wrong by hours when HERMES_TIMEZONE differs from the
server zone, and by an hour across DST).

Fixes #42433 (run-history facet)

Salvaged from #61403 by @LeonSGP43 (fallback design, metadata-only row, tests),
reworked per its review: timezone handling uses the configured zone and the
tests pin started_at under a configured-zone mismatch.

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
2026-09-26 17:19:04 -05:00
brooklyn!
3dbc0246b9 fix(tui): do not ok-reply a skill banner from the slash worker
Skill slashes that miss the 4018 gate print the loading banner and park
the prompt on unread _pending_input. Refuse before process_command, and
return command.dispatch's skill payload or a hard error when the skill
scan raises so fail-open cannot drop the turn.
2026-09-26 17:18:48 -05:00
Hermes Agent
890db53396 fix(agent): clamp displayed context usage to the model window 2026-09-26 17:15:30 -05:00
Ahmett101
9f27e75a77 fix(agent): invalidate usage anchors after prefix rewrites 2026-09-26 17:15:30 -05:00