Commit Graph

862 Commits

Author SHA1 Message Date
hermes-seaeye[bot] bcef556b00 fmt(js): npm run fix on merge (#101481)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-02 17:18:47 +00:00
Brooklyn Nicholson 195c3e5a3b fix(desktop): refuse every resume for a chat the user is deleting
The leftover-4001 fix stopped the dispatcher from CREATING a rebind
after a tombstone, but a request queued before it still fired, and the
push path (markRuntimeGone) never checked at all. Both funnel through
requestSessionResume, so guard there: a removal-pending id never gets
queued, and no consumer has to re-derive whether an id is doomed.

isSessionRemovalPending is now the single predicate. resumeSession keeps
its entry guard for requests queued before the tombstone; the session
tile gets the same guard via shouldResumeSessionTile, closing the tile
twin where a 4001 racing a delete unbound the runtime and re-armed the
resume effect against a dead id.

Drops the !freshDraftReady clause on explicitlyRequested: a gateway
switch also stages a fresh draft while leaving the URL on /:sid, where
an explicit request is the only remaining resume lever, so that clause
silently dropped plugin and SDK reselects after a connection apply.

Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
2026-09-02 11:23:50 -05:00
Brooklyn Nicholson 86acfee949 refactor(desktop): give session tombstones their own store
The delete/archive tombstone atoms lived in store/projects.ts, which
imports store/session. Resume lives on the other side of that edge, so
consulting the tombstones from the resume path would have closed an
import cycle. Move the atoms and their mutators to store/session-removal
and repoint every consumer; no behavior change.
2026-09-02 11:23:50 -05:00
Teknium d9b051a9d6 fix(desktop): pin the project '+' new chat to the profile its tree is shown under
workspace-session-target.ts never set $newChatProfile, so a new session
started from a project's "+" reached desktopSessionCreateParams with no
intent and fell back to $activeGatewayProfile — which an in-flight profile
swap can move between the click and Send, landing session.create on the
wrong backend (#79005 flaw 3, second path). Pin the tree's profile
(projectProfile()) via the same intent write newSessionInProfile uses.

Fixes #79005
2026-09-02 06:47:55 -07:00
Ayush Nangia 9189e42842 fix(desktop): reconnect an active profile whose gateway is closed 2026-09-02 06:47:55 -07:00
Teknium dfa74b5815 fix(desktop): boot session pop-out/watch windows against the session's owning profile
`openSessionInNewWindow` → IPC `hermes:window:openSession` →
`buildSessionWindowUrl` emitted no `profile`, so a secondary window (⇧⌘-click
pop-out, subagent watch) was a full renderer that adopted the PRIMARY
backend's profile and resolved the session id against the wrong store —
blank/wrong session for any non-primary profile (#82768, #61286).

The owning profile now rides the URL as `&profile=`, exactly the carry the
HUD already does (buildHudWindowUrl / windowProfileOverride in
use-gateway-boot); the renderer picks it with the same ladder openHud uses:
the session's stamped owner wins, an unstamped/uncached id (a brand-new
subagent child) inherits the profile the user is looking at.

Diagnosis credit: @DomGrieco (#82794).

Co-authored-by: DomGrieco <6556434+DomGrieco@users.noreply.github.com>
2026-09-02 06:47:55 -07:00
liguoyu 1592e48ac9 fix(desktop): a split-dragged Bot tab keeps its Bot workspace scope
Dragging a session tab to split in the Bot workspace committed through
openSessionTile with no scope, whose default { workspaceMode: 'sessions' }
was written onto the already-open tile before the pane move — so the tile
re-bucketed into the Sessions workspace and vanished from the Bot strip.

A scope-less open of an already-open tile is a MOVE, not a re-scope:
default the scope to the tile's current workspaceMode and only rewrite
scope when the caller passed one explicitly (sidebar/bot openers still
win). Brand-new tiles keep the 'sessions' default.

Closes #96865
Supersedes #96998

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-09-02 05:54:10 -07:00
Teknium 209de12d5f fix(desktop): Bot Mode tabs caption a Bot Chat with the bot's name, not "Bot Chat"
Every bot's canonical chat is stored under the same title ("Bot Chat" — the
name the gateway resolves it by, and an invariant roster-actions.ts's stale-tile
probe and #90102 rely on), so the main tab strip captioned every open bot chat
identically and two bots' tabs were indistinguishable (#99152).

Fix at the presentation layer, leaving the stored title and tabTitle untouched:
- workspace-scope.ts gains `$workspaceOwnerLabels` + `workspaceOwnerTitle()`:
  a bots-mode tab whose resolved title still equals its registered placeholder
  reads its owner's label instead. Side threads / Sessions tabs are untouched.
- session-tile.tsx captions tiles through it (and the drag payload); the main
  `workspace` tab (controller.tsx) does the same via `$botChatScopes`, the
  bot-mode scope the main tab was last opened under (it has no tile).
- The hermes-bots roster publishes displayName() per owner key through the new
  `host.setWorkspaceOwnerLabel` (feature-detected), so renames follow.

Supersedes #99177, which set tabTitle at open time — that reverts after mount
because tileTitle() prefers the stored row's title once the hidden row is
upserted, and breaks the `workspaceTabTitle === 'Bot Chat'` invariant.

Tests: one unit test on workspaceOwnerTitle() (bot chat → bot name; side
thread / sessions tab / unlabeled owner untouched) and one Electron e2e
(tab strip reads "Alpha", not "Bot Chat"); both fail on main, pass here.

Closes #99152
Supersedes #99177

Co-authored-by: twotnguyen <nguyenngoctinh011258@gmail.com>
2026-09-02 05:38:10 -07:00
Teknium 7b951e46ab fix(desktop): route a shared-id unpin to the row the pull adopted
With every slice feeding the pin sync, two profiles can legitimately hold
the same session id. The pull already tie-breaks toward the active gateway's
row (rowsByPinId), but the push resolved the profile by first match — so an
unpin PATCHed the other profile and the next page re-adopted the pin.
Resolve the write's row with the same active-gateway preference.

Case and test from #92609.

Co-authored-by: Jake Vincent <45184202+jakewvincent@users.noreply.github.com>
2026-09-02 05:37:58 -07:00
fangliquanflq d417aee387 fix(desktop): sync pins across all session slices 2026-09-02 05:37:58 -07:00
Teknium 9b84a98e29 test: trim salvaged #99763 to the two invariant-pinning tests
Drop the no-field fallback vitest case and the get_compression_chain
unit test: the fallback is implied by the predicate (Boolean(undefined))
and the chain walk is covered by test_list_serves_full_lineage_ids_for_projected_rows
through the real list projection.
2026-09-02 05:37:05 -07:00
Alonso 202997b51d fix(desktop): one conversation never opens as two tabs after compaction
Compression rotates a conversation's tip id while tiles stay keyed by
whichever segment id they were opened with. focusOpenSession and
openSessionTile tested exact ids, so right after a rotation the same
chat read as 'not open' and opened again in a second tab — and a tile
keyed to a MIDDLE segment (the tip when it was opened) could no longer
prove it names the conversation at all, rendering as an untitled ghost.

The projected list row now carries the full chain
(SessionDB.get_compression_chain, served as _lineage_ids by
list_sessions_rich and the sidebar tree row), lineageAliases indexes
every segment, sessionMatchesStoredId accepts membership, and the tab
focus/open paths dedupe through the lineage instead of the exact id.
Older gateways omit the field and degrade to today's root/tip pairing.
2026-09-02 05:37:05 -07:00
Teknium 6e7c7c7da9 fix(desktop): a bot row click always lands on the Bot Chat the row previews
A plain roster click fronted whatever bots-workspace tab the user last had
active for that bot (#96649). A '+' side thread persists in Local Storage
across restarts, so it won every click forever while the row kept previewing
the canonical Bot Chat (profiles.list canonical_session) — sidebar and center
described two different conversations; a message typed there landed in the
side thread and the row never moved. Support thread "[Bots] - Sessions is not
in sync again" (bundle 7dfff039), reproduced live on origin/main.

- roster-actions: the open-tab shortcut may front only the canonical chat
  (registry id or lineage tip, via a new onlyStoredIds allowlist on
  focusWorkspaceOwnerSessionTile); anything else resolves the registry and
  opens in place. Side tabs stay open beside it. "Open Bot Chat" in the row
  menu is the same action; the `canonical` option goes away.
- roster-actions: when the FOCUSED Bot Chat's canonical session advances on
  the gateway (cron bot-chat delivery, message_agent, group round, CLI turn —
  none reach this window's stream), re-open it in place so the transcript
  refreshes instead of waiting for an app restart (#99393 class).

Tests: the fronting-shortcut unit file and its e2e spec pinned the reversed
behavior; replaced by one unit file (5 tests) and one e2e spec that fails on
main and passes here. group-to-local-bot-handoff e2e still passes.
2026-09-02 03:41:44 -07:00
Teknium 3e63367175 feat(desktop): status bar can show live cache-hit rate and tokens/sec (off by default)
Two new right-click-toggleable status bar items, mirroring the CLI/TUI
Pantheon status bar upgrades: prompt-cache hit rate ("87%") and rolling
output throughput ("42 t/s"). Both are hidden by default and enabled from
the bar's existing 'Show in status bar' context menu, like the context meter.

Renderer-only: the tui_gateway already emits cache_hit_pct and avg_tps in
every session.usage tick and message.complete payload, so the items ride
the same UsageStats the context meter reads — no new RPC, no polling.
Labels show a placeholder until the backend has data, never self-hide.
2026-09-02 03:06:20 -07:00
hermes-seaeye[bot] 6840bb02e8 fmt(js): npm run fix on merge (#101102)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-02 08:42:10 +00:00
Teknium a2600740e8 feat(delegate): tag every subagent progress line with its batch id
Concurrent or nested delegation batches (a parent's 9-way fan-out plus a
child's own 3-way fan-out) printed interleaved `✓ [3/3]` / `✓ [3/9]` lines
with nothing identifying which batch each belongs to.

- CLI: batch header `🔀 [6a66] delegating 9 tasks`; completion lines and
  child tree-view lines become `[6a66 3/9]`; spinner remaining-count tagged.
- Relay: `delegation_id` rides on every `subagent.*` event (TUI gateway
  payload, api_server SSE subagent.start/complete).
- TUI: `[6a66 3/9]` prefix on /agents rows; Desktop Agents pane groups
  workers by exact delegation_id (heuristic shape/time grouping kept for
  older backends) and shows the tag on the group header.
- Tag = last 4 hex of the deleg_xxxxxxxx id (format_batch_tag), same id
  returned by the dispatch and used for cache/delegation/live/<id>/.
2026-09-02 01:06:24 -07:00
Teknium 55d8c054eb test(desktop): pin sole-local registry approval routing through the event owner (#96394)
Regression for the single-connection/single-profile report: hasRegistryTopology()
is true on every modern Desktop, so the ambient escape hatch stays closed; the
approval.request event's own (connectionId, profile) stamp is what routes
approval.respond back to the primary socket.
2026-09-02 00:37:00 -07:00
liuhao1024 7f53ad1741 fix(desktop): route approval responses through the runtime event's exact owner
recordSessionEventScope already captures the exact (connectionId, profile) a
runtime's inbound events proved, but knownOwnerForSession never consulted it:
with no tile/hint/row binding for the runtime id, approval.respond failed
owner resolution (SessionOwnerResolutionError) even though the event source
itself named the owner.

Add a structured owner twin of the scope ledger, written and cleared with it,
consumed as the LAST rung of knownOwnerForSession so durable stored identity
still outranks it and untagged/unknown runtimes keep failing closed.
2026-09-02 00:37:00 -07:00
kshitijk4poor 1df78c2da8 refactor(desktop): review follow-ups for the stale-RPC salvage
- ambientRequestFor(gateway): one adapter for the six copy-pasted
  `<T>(method, params) => gateway.request(method, params ?? {})` lambdas
  the pollers built to call requestForOwnedSession.
- resetBackgroundPollingGuard() with no argument (primary reconnect,
  use-gateway-boot.ts) now also clears healsByStoredId. The latch and the
  heal budget share a lifetime: a respawned backend re-mints every runtime
  id, so a stored session that had exhausted its 3 heals must be healable
  again. Previously only the latch was cleared there.
- Leaf module comment states the actual import convention.
2026-09-02 12:31:33 +05:30
kshitijk4poor 9ed8331ced style(desktop): eslint --fix + prettier on the salvaged files; null-guard respondToApprovalAction latch 2026-09-02 12:31:33 +05:30
kshitijk4poor 3f87a8090c fix(desktop): fold the stale-RPC guard into runtime-gone and refund heal budget on rebind
The salvaged #95647 commits predate runtime-gone.ts and shipped their own
gone-latch (session-rpc-guard.ts) beside the one main already had. Fold
them: one latch, one classifier, one clear seam.

- session-gone-latch.ts is a dependency-free leaf holding the latch, the
  4001 classifier (now also rejecting "mentions session not found" tool
  strings and unwrapping IPC bridge prefixes), and the rebind seam. It
  exists because session-request-router — imported by every store — must
  clear the latch after a successful session.resume/activate without
  pulling the session/tile stores into its import graph.
- runtime-gone.ts re-exports the leaf and keeps the heal logic.
- A successful rebind now also refunds the stored session's heal budget.
  markRuntimeGone caps consecutive heals at 3 per stored id and only a
  successful process.list refunded it, so a backend that reaps a detached
  runtime a few times left the view stuck on a phantom id with every
  poller latched and the socket-reconnect global clear (removed by the
  salvaged commit) re-arming the storm. #100639: 1,230 approval.pending
  4001s on one runtime id in 42 minutes, zero recovery.

Refs #100639
2026-09-02 12:31:33 +05:30
Shinji Kaneko c37181a4ec fix(desktop): preserve transient approval errors 2026-09-02 12:31:33 +05:30
Shinji Kaneko 0a80adc127 fix(desktop): harden stale session RPC guard 2026-09-02 12:31:33 +05:30
Shinji Kaneko 16021395f9 fix(desktop): stop stale session RPC retries 2026-09-02 12:31:33 +05:30
Zeus-Deus 0cb3e4cfd8 fix(desktop): pin a branch child's owning socket for the tile's lifetime
Routing the create and stamping the optimistic row still left a
remote-owned branch child flickering into "Couldn't open this session —
Session keeps losing its backend runtime right after resuming". The RPCs
were right and every resume succeeded; the OWNING SOCKET was the
casualty. Three gaps, one cause — nothing durable named the owner:

- openSessionTile persisted ownerRoute only for workspaceMode==='bots',
  so the branch tile pinned nothing in the gateway keep-set
  (openTileGatewayScopes / foregroundSessionScopes). The pruner closed
  the owner socket, the backend orphan-reaped the draft runtime,
  session.reclaimed unbound the tile, resume re-armed and succeeded on a
  fresh socket the next recompute closed again — until the resume-storm
  breaker (#93892) latched the error card at TILE_RESUME_STORM_LIMIT.
  Persist the route for sessions-mode tiles whose opener knows the exact
  owner, and stop a route-less re-scope from clobbering it.

- forkBranch, unlike both sibling routed creates in the same file, never
  called setSessionOwnerHint/holdSessionOwnerUntilForeground — so in the
  gap between session.branch returning and the tile publication landing,
  no keep-set rung named the owner and a prune could reap the just-minted
  draft runtime before the first prompt. Add both, mirroring the
  siblings; the hold retires once the tile's own route covers the scope.

- resetTileRuntimeBindings preserved cross-connection runtimes only for
  bot tabs, so a flapping sibling connection (an SSH source re-dialing)
  dropped the branch tile's healthy binding on every reconnect, re-arming
  resume each time — the same storm by another path. Preserve any
  owner-routed tile; the owner's own reconnect still rebinds.

Also keep open tiles' rows in sessionsToKeep: a branch child is a draft
the aggregator cannot return until its first turn persists it, so the
next background refresh silently dropped the optimistic "draft: branch
#N" row and the sidebar showed no trace of the branch until first send.

Each fix verified RED by reverting its line. Live acceptance against a
remote-owned parent branched from another connection: draft row visible
immediately and stable across refreshes, tile carries the owner route,
first turn accepted and completed by the owning backend, and no
storm/error card through the full 120s storm window — before the fixes
the card latched at ~20s.
2026-09-01 22:58:48 -05:00
Brooklyn Nicholson 3d81650c2f fix(desktop): a failed sidebar scan keeps the rows it could not re-read
The sidebar reports a profile it could not scan as HTTP 200 with an empty
page and errors=[{profile}]. The renderer merges that page keeping only
working, pinned, and selected rows, so every idle Yesterday / This-week
session disappears until a later scan succeeds — and the 5s coalescing cache
then serves the same empty payload back for the rest of its TTL.

Carry the previous rows forward for exactly the profiles named in errors[],
keyed by profile::id so a twin id in another profile is never stitched in.
Profiles that scanned cleanly are still authoritative, so a genuinely empty
page with no errors still clears the list. Per-profile usage and truncation
flags follow the same rule rather than zeroing under a list that was kept.

The legacy per-slice fallback stamps errors on the slice that actually
failed, so a cron read failure can no longer blank recents.

Part of #73847
Part of #88528

Co-authored-by: AKAZIK-py <AKAZIK-py@users.noreply.github.com>
2026-09-01 22:42:19 -05:00
Brooklyn Nicholson 5b0f68b478 fix(desktop): stop a stale cache skipping the app's own update leg
The everything-flow's client leg read `$updateStatus.get() ?? await
checkUpdates()`, so a cached row always won. That row can be up to a
poll interval (30 minutes) old and is captured before the backend leg
runs, so a cached "already current" skipped the client apply entirely —
the stale-GUI gap the flow exists to close.

Re-check first and fall back to the pre-flow snapshot when the live
check can't answer. `checkUpdates()` resolves with an error status
rather than rejecting and overwrites the atom with it, so the snapshot
is taken before any leg runs.

Co-authored-by: Dhana <227747512+whoisdhana@users.noreply.github.com>
2026-09-01 22:33:47 -05:00
Brooklyn Nicholson 79856ba49b fix(desktop): Check for Updates updates this app, not the remote backend
The update entry points chose their target from the connection mode, so
every surface in remote mode acted on the backend — including the ones
showing the client's own status.

The macOS "Check for Updates…" app-menu item is the clearest case: it
sits next to "About Hermes" and is the OS-standard way to update THIS
app, but on a Mac connected to a remote Linux backend it checked the
Linux box. The backend was already current, so the action reported
nothing and did nothing, and the desktop app drifted months behind with
no error and no updater log to explain it. The update-available toast
had the same split: a client check raised it, clicking it opened the
backend's overlay, which has no target switcher and no way back.

Surfaces bound to one target now name it; only genuinely generic
commands still take the connection-mode default, so the command
palette's remote-mode backend target and the everything-flow are
unchanged.

Co-authored-by: BerneYue <14088768+yuexiongHNU@users.noreply.github.com>
Co-authored-by: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com>
Co-authored-by: clayduncan <234173110+clayduncan@users.noreply.github.com>
Co-authored-by: Dhana <227747512+whoisdhana@users.noreply.github.com>
2026-09-01 22:33:47 -05:00
Jeffrey Quesnelle c56f8cdd48 Merge pull request #100667 from NousResearch/feat/local-models-squash
feat: local models — managed llama.cpp runtime with one-click desktop  setup
2026-09-01 17:53:28 -04:00
kshitijk4poor 5f048000d7 test(desktop): write remembered-cwd fixtures via setCurrentCwd, not hand-rolled keys
The two #57911 regression tests hand-encoded the remote workspaceCwdKey()
localStorage literal; seed it through the exported setCurrentCwd() like the
sibling isolation test so a key-shape change can't silently strand them.
(/simplify-code reuse finding)
2026-09-02 02:10:44 +05:30
kshitijk4poor 968ff47ca8 docs(desktop): update resolveNewSessionCwd priority comment for detached-everywhere
The remote remembered cwd is no longer a bare-new-session default after
the #57911 fix; the comment listed it as priority 2. Follow-up from
salvage self-review of PR #57926.
2026-09-02 02:10:44 +05:30
Ahmett101 3420c9facb fix(desktop): make workspaceCwdForNewSession behave identically in local and remote mode (#57911)
A bare-new-session (Cmd+N without a project scope) in remote mode used to
inherit the remembered cwd under workspaceCwdKey()'s remote variant — i.e.
the last project the user attached to on that backend. Symptom: Cmd+N landed
on the wrong project's workspace (e.g. a configuration discussion thread
opened in the tradingview directory).

The remote branch in workspaceCwdForNewSession is the entire bug class:
- It overrides the configured-default-pre-attaches rule, so users who
  configured an explicit default didn't get it under remote mode either.
- The bare-new-session path has no notion of "remember where I was" —
  that's startSessionInWorktree's job. The remembered cwd is only meant
  to assist resume/restore (ensureDefaultWorkspaceCwd, which keeps its
  remote-keyed sticky seed and is unaffected by this change).

Drop the mode === 'remote' early return and route both modes through
getConfiguredDefaultProjectDir(), matching the proposed fix in the issue.

Test: the existing 'keeps remote workspace memory separate from local and
other remotes' case set the local key then switched connection to remote,
so under the buggy code it returned '' regardless — it never gated the
regression. Add a remote-key repro and a symmetric explicit-default test
so the fix is observable from the suite.
2026-09-02 02:10:44 +05:30
emozilla 43e67d872f feat: local models — managed llama.cpp runtime with one-click desktop setup
Run models locally as a first-class provider. The CLI grows a managed
llama.cpp runtime (engine install, model download, server supervision);
the desktop app grows the full setup and management story on top of it.
GUI surfaces ship behind the desktop --local launch flag (hermes desktop
--local, or the flag on the packaged app); backend routes and the CLI
are always live.

Runtime (hermes_cli/local_runtime/):
- curated GGUF catalog with per-machine variant selection: hardware
  probe (VRAM/RAM/UMA), fit planning with spill accounting, quant choice
  by context window
- derived recommendation: quality-ranked picks gated by a predicted
  decode-speed floor, bandwidth-aware on unified memory; the decision
  table is pinned as a test (pick AND reason per memory class), and the
  Recommended badge explains its pick in a tooltip fed by the resolver's
  actual branch
- engine install + model download with resumable split parts, cumulative
  plan-level progress, and staged-model integrity (a split GGUF counts
  only when every part is present)
- server supervision: spawn/adopt/stop, router mode with per-model load
  progress relayed over SSE, abandoned-request cleanup

Desktop:
- Settings -> Providers -> Local models: one-click quickstart (install
  engine, download the recommended model, boot) plus per-model download/
  activate/eject, fit-ranked catalog with context pills
- model pickers (composer dropdown + Cmd+K) show staged local models,
  in-flight downloads as live progress rows, and load-into-memory bars
- local-setup campaign tip for eligible hardware; System resources
  statusbar widget (GPU/VRAM/RAM); in-chat load progress during sends
- friendly dead-server errors, and failed agent builds retry on the next
  send instead of wedging the session

Co-developed with NVIDIA field feedback on RTX 5090 and DGX Spark.
2026-09-01 16:01:53 -04:00
kokhlo 55181a7d79 fix(oauth): surface actionable guidance when sign-in windows lapse
The local expires_in countdown killed OAuth sessions with a bare
"Session expired" before the backend poller's enriched message (Portal
sign-in stalled in the opened tab, retry/API-key fallback) could reach
the UI, and the desktop onboarding poller had no local expiry at all —
a dead session polled forever. Both surfaces now lapse with guidance
naming the common cause, prefer the backend error_message when it has
one, and keep polling when the backend still reports pending (clock
skew).
2026-09-01 09:28:17 -07:00
Teknium a1c25d393a feat(desktop): built-in optional-skills catalog in Capabilities → Skills with one-click install
The Skills tab now lists the entire official optional-skills catalog
(optional-skills/ shipped with the repo) below the installed skills.
Each catalog row has an Install button that routes through the standard
hub action pipeline; once the install finishes the row flips into the
installed list with the normal enabled/disabled toggle.

- backend: GET /api/skills/hub/official — OptionalSkillSource.list_local()
  scan (no network) + per-profile installed flags from the hub lock
- desktop: catalog section in SkillsView with search/scope integration,
  install-state spinners off $hubActions, and an OfficialSkillDetail pane
  (hub preview: frontmatter + full SKILL.md + Install)
- CapRow gains an optional action slot (button instead of the Switch)
- electron: route the new endpoint with the skills family (primary backend)
- i18n: officialCatalog/officialPill keys across en/ja/zh/zh-hant
2026-09-01 01:39:59 -07:00
Teknium ada7213cca feat(desktop): first-open consent prompt for real-profile browsing
Opening a Browser pane (docked tab or ?win=browser popout) while
browser.use_real_profile is off now offers to turn it on: a one-time
dialog explaining the cookie/login snapshot, with 'Use my profile'
(writes the config key through the same PUT /api/config + shared query
cache the Capabilities toggle reads, so the existing toggle flips on
the spot), 'Not now' (mutes for this app run), and 'Don't show again'
(persists the opt-out across launches).

A claim atom ensures only one mounted Browser pane renders the prompt,
so split zones never stack duplicate dialogs. Prompt copy added to all
five locale catalogs.
2026-09-01 00:36:47 -07:00
Brooklyn Nicholson 21b2095d00 fix(desktop): type the config.set mock so display-toggles tests typecheck 2026-08-31 21:11:11 -05:00
Brooklyn Nicholson c42daab67b fix(desktop): mirror the Appearance switches on connect, not just on change
A gateway the app has never spoken to holds no answer at all, and for a
switch that defaults on, silence reads as consent to exactly the thing
the user turned off. Mirroring on connect as well as on change closes
that, and the connect push is narrow on purpose: only settings the user
has actually touched are re-sent, so a fresh install can't broadcast its
defaults over a config.yaml somebody hand-edited on the server.
2026-08-31 21:11:11 -05:00
hermes-seaeye[bot] de590cc3cb fmt(js): npm run fix on merge (#99917)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 02:09:50 +00:00
Brooklyn Nicholson 9052ce76c4 feat(desktop): collapse Yesterday / Last week groups in the sessions sidebar
Date and status labels were separators only. Clicking one now hides the
sessions under it, same as project and profile rows, and Collapse all
covers those buckets too.
2026-08-31 21:03:25 -05:00
xxxigm bcecd675f7 fix(desktop): unwrap Models-page code-skew 503 and recycle the owned backend (#97046)
Show a Restart backend action that kills the SSH serve before the local child
so reconnect cannot reuse a stale lockfile, instead of dumping raw IPC JSON.
2026-08-31 20:43:36 -05:00
Brooklyn Nicholson 60ba6024b5 fix(desktop): isolate keep-alive panes from the visible transcript
Hidden session panes published stick-to-bottom into window-global atoms
and every mounted list subscribed to the same jump broadcast, so a buried
tab yanked the reader and flashed the composer. Only the visible pane
may publish, scroll requests are keyed by session, and a run start or
same-session refresh leaves a scrolled-up reader where they were.

Co-authored-by: gamewocao <gamewocao@users.noreply.github.com>
Co-authored-by: mor44-AI <andrefmontemor@gmail.com>
Co-authored-by: d4rk pr10r <darkpriorlabs@gmail.com>
Co-authored-by: Jackal991 <lawrence@hydra-flow.co.uk>
2026-08-31 20:41:08 -05:00
Brooklyn Nicholson d72ce5e434 fix(desktop): rebind the visible chat after a reaped runtime
session.reclaimed now heals through markRuntimeGone before dropping
cache. A 4001 on the visible session's dispatcher request asks for a
durable resume. prompt.submit uses the window dispatcher so the turn
lease survives the ACK, and a primary route no longer registers a
phantom turn lease.

Co-authored-by: Lester Liang <153183032+lesterlxt@users.noreply.github.com>
Co-authored-by: wz-heng <68931789+wz-heng@users.noreply.github.com>
2026-08-31 20:40:31 -05:00
Brooklyn Nicholson 36bea50139 fix(desktop): latch approval and goal polls off a dead runtime
process.list already stopped hammering a reaped id. approval.pending and
goal status did not, and session.info heartbeats republished an equivalent
state object so every tab rerendered. Share the gone-latch from
runtime-gone and keep heartbeat identity when nothing changed.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
2026-08-31 20:40:31 -05:00
Brooklyn Nicholson f08666d42a fix(desktop): persist composer model/provider per remote connection and profile
Sticky composer keys were global, so a provider picked on one remote profile could ride into session.create on another. Persistence now follows an explicit (connectionId, profile) owner published before the active-profile reseed; unresolved legacy owners fail closed.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-08-31 20:40:09 -05:00
hermes-seaeye[bot] e22f8a7fbd fmt(js): npm run fix on merge (#99862)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-01 00:28:40 +00:00
Oliver Mee 61534aac45 fix(desktop): keep primary SSH session resumes remote
Untagged session rows come from the ambient primary backend. Do not synthesize a local owner for them, and clear stale explicit hints before issuing an id-only resume.
2026-08-31 11:58:54 -07:00
joaomarcos c631b483f1 fix(desktop): satisfy import ordering 2026-08-31 11:53:43 -07:00
joaomarcos 48deb73ebd fix(desktop): latch dead runtime recovery across remounts 2026-08-31 11:53:43 -07:00
Teknium 714930f256 fix(desktop): a bot roster click no longer opens a stale finished session (#90102)
The roster click's fronted-tab shortcut trusted the persisted session-tile
bucket (Local Storage 'hermes.desktop.sessionTiles.v2') unconditionally: a
persisted 'Bot Chat' tile naming a session the canonical registry no longer
resolves to — a superseded row from the retired ui_meta pointer design, a
re-minted canonical chat, a stale finished (often hidden) session — was
fronted on every click and remembered as the zone's active pane, so the row's
click target stuck to that stale session forever while its preview/age
described the live one, and clearing Local Storage only healed until the next
click re-persisted the same tile.

Per the Desktop guide the backend is authoritative for session state and the
renderer copy is a cache that must reconcile. focusWorkspaceOwnerSessionTile
now takes an optional staleness probe: tiles the probe rejects are discarded
(same no-undo rationale as discardSessionTile — resurrecting one would just
front the stale session again) and never fronted. The roster click supplies
the probe: a canonical-titled tile whose stored id matches neither the
server-resolved canonical_session registry row nor its compression-lineage
tip is stale, so the click falls through to the authoritative name-registry
open. Side-chat tabs carry no registry identity and are never judged; older
gateways without canonical_session (and shells without the probe) keep the
previous behavior unchanged.
2026-08-31 07:28:07 -07:00