Commit Graph

173 Commits

Author SHA1 Message Date
hermes-seaeye[bot] 254158f453 fmt(js): npm run fix on merge (#101150)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-02 10:12:08 +00: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] fbd40c907d fmt(js): npm run fix on merge (#101107)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-02 08:51:02 +00: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
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
Brooklyn Nicholson 308454b372 fix(desktop): route model catalogs and picker reseeds through the focused owner
requestModelOptions now sends the owner profile on the RPC, forced reseeds call getGlobalModelInfo(profile), and picker/cache keys include the registry connection so a tile cannot fall back through the ambient socket.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-08-31 20:40:09 -05:00
Brooklyn Nicholson 36620578f0 test(desktop): follow the model and path menu labels to sentence case
These query by visible text, so the casing pass moved the labels out
from under them.
2026-08-28 22:38:01 -05:00
Brooklyn Nicholson 46512ee1d6 feat(desktop): give the app's main surfaces durable handles
Tours and tips both address elements by selector, and everything they most want
to point at — the composer, the model pill, the nav rows, the profile rail, the
right-pane toggle — was reachable only by icon, position, or a translated
aria-label. None of those survive a re-render, a theme, or a locale change.

Two handles are needed per surface, not one, because where an arrow points and
what an outline wraps are different questions: a nav row's label carries the
`data-tour` handle so an arrow lands at the end of the word, and defers the
outline to the row via `data-tip-arrow-only`.

The collector also has to skip panes hidden by the keep-alive stack. An inactive
tab stays mounted under `visibility: hidden` to keep its scroll position, so its
rect is identical to the live tab's and no geometry test separates them — which
is how a tour could spotlight a background tab's composer.
2026-08-27 21:50:18 -05:00
Teknium ff03d46eef test(desktop): method-aware gateway mock for the refresh-reconcile confirm interaction test
The reconcile-to-guarded-model interaction test's requestGateway mock
must only answer config.set with the confirm handshake — the panel's
model.options read rides the same dispatcher and was eating the
first mocked response.
2026-08-26 21:38:41 -07:00
Teknium 61b6788dd4 fix(desktop): Bots-mode picker routes guarded model switches through the shared confirm handler
The Bots editor's model write (profiles.configure) was the one switch
surface that bypassed the data-policy / expensive-model selection guard:
a guarded pick (e.g. muse-spark contributor tier) was applied silently,
with no confirm flow anywhere — the #95293 remainder after the core
picker's confirm handshake landed in use-model-controls.

Gateway: profiles.configure now answers confirm_required +
confirm_message for a guarded model (same handshake as config.set
model) and writes NOTHING until the client resends with
confirm_expensive_model: true. Other sections still apply; the pending
model section is not reported as failed.

Desktop: the confirm flow is extracted out of use-model-controls into
one shared applier (lib/guarded-model-switch.ts, exported through the
plugin SDK) — warning toast, staleness-guarded Confirm, single
confirmed resend, never a retry loop. The core picker and the Bots
editor now consume the SAME handler; the Bots editor's Confirm resends
only the model section with confirm_expensive_model: true.

Fixes #95293 (Bots surface remainder).
2026-08-26 21:38:41 -07:00
rainbowgits 0fa98d6a1b fix(desktop): switch model after refresh when it leaves the catalog
Refresh Models only updated the catalog cache, so the composer kept showing a model that was no longer in the new group list.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 21:38:41 -07:00
hermes-seaeye[bot] 1a19c52dd2 fmt(js): npm run fix on merge (#95365)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-26 07:45:59 +00:00
LovePlayCode 1808d33a24 fix(desktop): keep owner-routed tile gateways out of idle prune
Bot chats stay on a secondary while chrome stays on the launch profile.
The keep-set only counted busy sessions, so idle prune closed the tile
socket and resume spun forever. Keep open tiles, route catalog reads to
the owner, and hydrate model/provider from resume.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 00:21:29 -07:00
fangliquanflq c2090ba6b4 fix(desktop): distinguish provider quota exhaustion 2026-08-23 18:43:12 -07:00
Brooklyn Nicholson bf4f5e17f8 feat(desktop): show unread count on the sessions sidebar toggle
A small overlay on the left sidebar icon (right when panes are flipped) so a
closed sessions list still reports unfinished-unread chats.
2026-08-20 00:42:21 -05:00
Brooklyn Nicholson bfcfdb30d1 test(desktop): prove the status bar keeps its own right-click menu
The unit test covers the primitive contract — the marker survives Radix's
asChild Slot merge. This adds the end-to-end half: mount the real coordinator
next to the real status bar, right-click it, and assert the customize menu
opens while the app fallback stays shut. That is the assertion that fails on
a build where the two halves drift apart, and it holds regardless of how the
ownership marker is spelled.

Drops the hand-stamped DOM fixture that asserted the coordinator honors an
attribute the test itself wrote.

Co-authored-by: huklaa <huklaa@users.noreply.github.com>
2026-08-19 23:51:10 -05:00
Brooklyn Nicholson d15cd18fa1 feat(desktop): hide the Thinking toggle where a disable is rejected
The picker offered an off switch for every reasoning model, including routes
whose upstream answers a disable with HTTP 400 — so "thinking off" was a
control that could not work. Carry the catalog's mandatory verdict through
model.options as can_disable_reasoning and hide the toggle when it is false.

Effort levels are left alone. The catalog's supported_efforts under-reports
what the Portal serves (z-ai/glm-5.3 publishes max, high, low yet honors
minimal at its lowest thinking), so filtering the scale by it would hide
levels that work.
2026-08-19 22:14:56 -05:00
Teknium b2ea0f3810 fix(desktop): gateway restart no longer clickable-by-mistake next to reconnect
Community report (X @Cobalt_Peak): Reconnect and Restart gateway in the
statusbar gateway popover rendered the same RefreshCw icon side by side,
so users triggered full gateway restarts when they meant to reconnect.

- Restart now uses a Power icon with a destructive hover tint
- Moved restart to the end of the row, after the system-panel button,
  behind a visual divider separating it from the benign actions
2026-08-19 19:25:37 -07:00
Brooklyn Nicholson 808fde106d test(desktop): share the duplicated fixtures
The same AST sweep over specs found fixtures maintained in parallel
across suites that have no reason to know about each other.

Twenty-one specs each mounted useMessageStream themselves and ten of the
harnesses were byte-identical; twenty now take renderMessageStream, with
overrides for the seams that genuinely vary. The SessionInfo builder was
spelled out field-by-field in seven specs, so a new backend field broke
seven files instead of one. Twenty specs carried their own inert
ResizeObserver and eleven repeated the animation-frame, CSS.escape,
scrollTo and WAAPI stubs the transcript needs to mount at all — split by
scope into src/test/jsdom for what any component might need and the
assistant-ui folder's own kit for the transcript. Plus the window-state
bridge, deferred, the external-store thread runtime, the manual
createRoot harness, and the per-folder caret, env-var, provider and
session fixtures.

Left alone on purpose: the store suites' makePrimary, where the vi.mock
harness around it is the actual duplication and cannot be hoisted out of
a hoisted factory; electron's deferred, where reaching into src/ from
the main process would invert the layering for eight lines; and the two
suites that compose another hook alongside the stream.
2026-08-19 10:51:12 -05:00
David Dudok de Wit c41ec9c588 refactor(desktop): scope gateway context to Sessions 2026-08-18 22:03:25 -07:00
David Dudok de Wit 6e9e12075c refactor(desktop): keep gateway details contextual 2026-08-18 22:03:25 -07:00
David Dudok de Wit fa73b45d34 fix(desktop): isolate state across source switches 2026-08-18 22:03:25 -07:00
ethernet 1314a539c2 refactor(desktop): custom translated context menus across the app
One renderer coordinator owns every right-click and replaces the
native Electron menus. Menus are assembled from what the click
landed on:

- Links and images get open/copy/save sections; chat links add the
  reach-aware resolved-URL copy on remote gateways.
- Editables get spell-check suggestions (async-appended when
  Chromium's facts arrive from main), cut/copy/paste, and select
  all. Cut and copy need a selection; paste needs a non-empty
  clipboard; select all needs field content. The verbs show their
  accelerators instead of icons and dispatch a frame after the menu
  closes, so the radix focus trap cannot steal the target. Select
  all runs renderer-side, scoped to the field, because main's
  selectAll acts on the focused frame and could grab the transcript.
- Terminals answer through registered xterm handles; the read-only
  agent terminal hides paste.
- The in-app browser guest builds the same menus from the webview
  tag's context-menu event: Chromium's editFlags gate the edit
  verbs, spell-check rides the event, and Inspect element closes
  every menu. Coordinates arrive as window-relative device pixels,
  so the handler divides by the window zoom factor; guest edit
  commands focus the webview first, because they act on the focused
  webContents.
- Bare app chrome falls back to the window verbs.

Labels come from the locale files in all five languages. Main keeps
thin IPC verbs: edit commands, copy-image-at-gesture, spell-check
actions, and dictionary-add for guests (the tag has no session API).
The e2e spec exercises the real focus trap; it is blocked today by
the gateway-checking stall that also fails e2e/chat.spec.ts.
2026-08-18 23:53:07 -04:00
fangliquanflq ace85d63de feat(desktop): add status bar reconnect for offline gateways
Expose the existing profile-aware gateway boot reconnect path through a
single-flight renderer action, and surface a Reconnect button in the
gateway status menu panel whenever the socket is not open. Repeated
clicks share one in-flight reconnect; failures surface through the
existing non-destructive notification UI. Localized copy for all
supported Desktop locales.

Salvaged from PR #80694 (net diff re-applied onto current main; panel
code lives in app/shell/gateway-menu-panel.tsx now).
2026-08-16 19:58:37 -07:00
Teknium 8033389662 fix(desktop): match custom provider aliases in model catalog menu
Fixes #87035
2026-08-16 02:08:53 -07:00
273-B_L0 f61fb52f94 fix(desktop): show platform layout modifier 2026-08-15 01:04:06 -07:00
AlexDev_ bee0d45ddf perf(desktop): pause background UI work while unfocused 2026-08-15 00:32:53 -07:00
Teknium b9e03b4d55 fix(desktop): give chat images native right-click and proper save filenames
Three fixes for generated/displayed images in the desktop chat:

- Shell fallback context menu no longer swallows right-clicks on images:
  the guard now yields to Electron's native image menu (Copy Image, Copy
  Image Address, Save Image As...) for img/picture/video/canvas targets,
  matching the existing editable/selection carve-outs.

- Save Image As / download button: generated-image URLs (fal.media etc.)
  end in an extensionless content hash, so saves produced an unopenable
  "All Files" blob. The main-process save dialog and the renderer anchor
  fallback both now append a MIME-derived extension, add image type
  filters, and default to the user's Downloads directory instead of the
  process cwd (win-unpacked on packaged Windows installs).

- New will-download handler routes any Chromium-initiated download
  through the same Downloads-dir + guaranteed-extension policy.

Validation: new unit tests for the filename derivation (6 passing);
npm run check:lint green (tsc x3 + eslint, 0 errors).
2026-08-14 22:29:45 -07:00
hermes-seaeye[bot] bab9a85b67 fmt(js): npm run fix on merge (#86533)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 00:07:02 +00:00
Brooklyn Nicholson c83061ba3d test(desktop): cover the context gauge fetching before a turn runs 2026-08-14 19:01:15 -05:00
Brooklyn Nicholson 25d1c8d740 fix(desktop): context gauge reads the session it is on, not the last turn
The statusbar gauge painted only what the backend reported as measured
occupancy, which a session has none of until a turn runs in this process.
Turning the gauge on mid-conversation, or resuming a chat, therefore showed
nothing until the next message.

Fetch session.context_breakdown as soon as the gauge is on screen instead of
when its popover opens. It is the same read-only estimate the popover already
used (chars/4 over the live prompt, tools and transcript — no provider call),
and it reports the measured figure once the backend has one. The popover
becomes presentational and reads the gauge s merged usage, so the bar and the
panel cannot disagree.
2026-08-14 19:01:15 -05:00
Teknium bf10349ebd chore: lint fixes (blank line, useMemo dep) 2026-08-14 13:09:44 -07:00
Teknium 9442a718da fix(update-check): recover the real behind-count via the GitHub compare API
The honesty half (no fabricated counts) leaves shallow installs permanently
count-less. The compare API knows the full graph regardless of local clone
depth: GET /repos/<o>/<r>/compare/<current>...<target> returns ahead_by —
exactly the behind count the shallow boundary lost.

- hermes_cli/banner.py: _github_compare_behind() (bounded, unauthenticated,
  best-effort); wired into _check_via_rev and the shallow branch of
  _check_via_local_git. ahead_by==0 with differing tips = local-ahead => 0.
- hermes_cli/update_cmd.py: hermes update --check shallow path prints the
  exact count when recoverable, presence-only wording otherwise.
- apps/desktop/electron/update-count.ts: compareApiUrl() +
  parseCompareBehindCount() pure helpers; main.ts fetches the count when
  resolveBehindCount() returns null, and the SSH-official passive path stops
  fabricating behind:1 (uses compare API + updateAvailable flag).
- apps/desktop/src/lib/version-status.ts: updateAvailable now applies to the
  client target too, so a shallow desktop install shows '(update)' instead of
  nothing (or the old frozen '(+1)').

Fixes #84591; CLI siblings of #78253 / #53479 behavior.

E2E: live compare API returned 61/62 for real 61/62-commit gaps and 0 for the
reversed (local-ahead) pair; real shallow-clone fixture (depth-1 clone +
depth-1 fetch, merge-base broken) recovers the exact count with the API and
falls back to the honest sentinel offline.
2026-08-14 13:09:44 -07:00
Brooklyn Nicholson e505ff9777 feat(desktop): add reset-to-defaults to the statusbar context menu
Once you have toggled a few items on and off there is no way to get
back to the shipped layout short of remembering which ids are in
STATUSBAR_HIDDEN_BY_DEFAULT. Add a row to the bar's right-click menu
that restores that set.

The row is disabled rather than hidden when nothing is customized, so
it also advertises that a shipped layout exists. Reset touches item
layout only — whole-bar visibility is a separate preference, and
resetting from the bar's own menu should not make the bar you are
right-clicking disappear.
2026-08-13 14:29:18 -05:00
Brooklyn Nicholson 4d9202b963 feat(desktop): right-click the shell chrome for window-level actions
Right-clicking anywhere the app owns no menu of its own — the titlebar
gutter, an empty pane body, the sidebar background — did nothing at all.
Electron's native handler bails on non-editable, non-selected content by
design, so those surfaces had no menu to fall back to.

Wrap the shell in a fallback context menu carrying the verbs that belong
to the window rather than to a row: new session, new window, command
palette, toggle the status bar, settings, update Hermes. Every row reuses
the store action and the copy its Cmd+K twin already uses, so the two
can't drift.

A guard on an inner element keeps it a fallback: a right-click that lands
inside a surface with its own context menu, on an editable, or on a live
selection stops propagating before Radix's trigger sees it, leaving that
surface's menu — or Electron's native edit menu — in charge.
2026-08-12 22:55:37 -05:00
embwl0x 8edcdd1a80 fix(desktop): isolate plugin render hooks 2026-08-10 10:57:35 -07:00
hermes-seaeye[bot] 03fa32c92d fmt(js): npm run fix on merge (#83143)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 11:07:34 +00:00
Brooklyn Nicholson 4f9d345956 fix(desktop): skip titlebar Y nudge on Tahoe and macOS fullscreen
Tahoe already aligns traffic lights without the optical translate. In
fullscreen, drop windowButtonPosition in the main process and clear the
right-cluster inset so traffic-light dodge chrome goes away on both sides.
2026-08-10 05:45:29 -05:00
hermes-seaeye[bot] 54e33c95a6 fmt(js): npm run fix on merge (#83099)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 09:47:13 +00:00
Brooklyn Nicholson a843bcc940 fix(desktop): sort titlebar import for eslint 2026-08-10 04:30:58 -05:00
Brooklyn Nicholson d1f2e32b13 fix(desktop): titlebar clusters — macOS Y nudge, 24px targets, 13.9px icons
Left cluster gets a macOS-only translate to sit on the traffic-light row.
All titlebar tools use 24×24 hit areas with 13.9px Codicons (inline size
beats unlayered codicon.css). Clusters share one flex shell with no gap —
buttons abut and the hit target is the spacing.
2026-08-10 04:24:42 -05:00
Brooklyn Nicholson 10c1530599 refactor(desktop): put the HUD toggle beside the layout editor
HUD mode is a layout choice, so it belongs with the other one. The
keyboard-shortcuts button goes away with it — it was a second door to a
settings tab that the command palette and the keybind itself already open.
2026-08-08 03:52:02 -05:00
Brooklyn Nicholson a3d57f18c2 fix(desktop): open HUD mode on the tab you're looking at
Both entry points read $selectedStoredSessionId, which is the WORKSPACE pane's
session — so whichever tile was fronted, the main tab went into the HUD. Tabs
exist precisely so those aren't the same question.

`getActiveComposer()` already answers it for the focus bus, healing to the
visible surface when its cached claim is buried, and a tile's routing key is its
stored session id. One resolver now, shared by the titlebar button and ⌘⇧H.

Coming back re-resumes through the tile delegate when the target is an open
tile. The ordinary resume path enforces "a session is either main or a tile,
never both" and would have closed the tile to take it into main, quietly
rearranging tabs the user opened on purpose.
2026-08-08 02:28:58 -05:00
Brooklyn Nicholson 6a01b429d7 feat(desktop): reach HUD mode from the titlebar and a keybind
⌘⇧H plus a titlebar tool, since the whole point is leaving the app without
reaching for it. Keeps the keyboard-shortcuts button it sat next to.
2026-08-08 01:39:06 -05:00
hermes-seaeye[bot] 777512b760 fmt(js): npm run fix on merge (#76498)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-02 01:23:45 +00:00
brooklyn! 97971643ab Merge pull request #76417 from NousResearch/bb/kanban-model-picker
Pick a kanban task's model and thinking depth from the board
2026-08-01 16:55:01 -05:00
Brooklyn Nicholson defee936e9 fix(desktop): keep the composer's model menu pixel- and behaviour-identical
Two regressions from moving Edit Models into the shared catalog:

The row opened its own separator group, so the composer showed two rules in
the trailing block where it had always shown one. Edit Models now renders
inside the host footer's group — same single separator, same order.

The panel also read the catalog with a non-reactive getQueryData. With no
model in the session store yet (a fresh draft), currentPickerSelection falls
back to the catalog's reported current, and a cache peek never repaints once
that resolves — the menu could sit on a stale or empty selection. Back to a
live useQuery on the same key, which React Query dedupes against the menu's
own subscription rather than double-fetching.
2026-08-01 16:25:13 -05:00
Brooklyn Nicholson d5d3086b41 fix(desktop): let the model catalog own curation, not each host
The board listed every model while the composer honoured the user's Edit
Models shortlist, because visibility arrived as a prop each caller opted into.
Curation is one stored preference, so a per-caller opt-in guarantees the two
surfaces eventually disagree about what "my models" means — the exact drift
extracting the menu was meant to end.

ModelCatalogMenu now reads $visibleModels itself and renders the Edit Models
row, so every picker shows the same shortlist and offers the same way to
change it. The composer's footer keeps only Refresh Models. No plugin surface
is involved: the menu is core and the plugin just mounts it.

Also restores the session-scoped catalog fetch the extraction dropped. The
composer's query key must carry its session id — the app invalidates the
session-scoped key on model changes, so a global key would have gone stale
mid-conversation.
2026-08-01 16:19:37 -05:00
Brooklyn Nicholson b35c34d58c refactor(desktop): make the composer's model picker a reusable primitive
The model menu — search, provider grouping, -fast family collapse, keyboard
selection, the per-row thinking/effort submenu — was welded to the chat
composer's session writes, so any other surface wanting a model picker had to
fork it and drift.

Splits rendering from meaning. ModelCatalogMenu owns the catalog and the
navigation; a ModelMenuController decides what a selection DOES. The composer
is now one controller over it, keeping its session scoping, sticky manual
pick, preset restore, MoA presets, and rollback-on-failed-write intact.

ModelEditSubmenu becomes pure: it reports edits instead of performing them.
It previously called setCurrentReasoningEffort and config.set inline, so any
non-composer host would have silently retargeted the user's live chat when
they picked an effort. Its default effort is passed in rather than read from a
store, which is what lets it render outside a session at all.

Exported through the SDK so plugins consume the real component instead of a
copy. The composer's existing behaviour suite passes unchanged against it.
2026-08-01 16:10:13 -05:00
hermes-seaeye[bot] c5be6e7792 fmt(js): npm run fix on merge (#76404)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-01 20:45:40 +00:00